bacen / pix-api

API Pix: a API do Arranjo de Pagamentos Instantâneos Brasileiro, Pix, criado pelo Banco Central do Brasil.
https://bacen.github.io/pix-api
2.36k stars 268 forks source link

[Segurança] Webhook para URL de terceiros #239

Closed joelemanoel closed 3 years ago

joelemanoel commented 3 years ago

Recentemente @aldemaroc e eu fizemos a implementação da API PIX em nosso sistema de faturamento através da GerenciaNet e observamos que é possível definir a URL de webhook para uma URL de terceiros que já tenham mTLS configurado para o mesmo PSP, o que nos permite efetivamente enviar webhooks para sistemas que não são nossos através da API.

Dessa forma, caso a implementação da API feita pelo cliente não seja segura (ex. não consulta o EndToEndId para validar o webhook e confirmar o pagamento) é possível enviar webhooks para confirmação de pagamentos para qualquer empresa cuja URL de retorno seja conhecida e esteja utilizando o mesmo PSP.

POC (Prova de Conceito):

Uma solução simples seria incluir a chave PIX para onde o pagamento foi enviado no conteúdo do webhook. Já que para o webhook ser feito é necessário mTLS temos certeza que ele é verdadeiro, e dessa forma seria possível validar o pagamento imediatamente sem a necessidade de consultar o EndToEndId (gerando também menos carga na API do PSP).

[UPDATE] Para ficar um pouco mais fácil de entender:

Toda a questão envolvida nesse issue não tem haver com o que é retornado no Webhook (apesar disso, seria interessante vir a chave no objeto do Webhook) e sim em relação a Autenticação e Autorização que o mTLS se propõe.

Se tratando de mTLS entendemos como uma autenticação bidirecional a qual cada um dos lados possam se validar, isto realmente acontece no molde que está hoje, porém como informado acima QUALQUER que seja conta/chave pode setar um Webhook para qualquer URL que esteja com o mTLS do PSP em questão, isso tornando a confiabilidade do Webhook nula, já que você receberá notificações sobre PIXs não recebidos por você, este por si não é o problema, porque o EC poderá validar via /pix o E2EID que foi recebido, todavia, os ECs que não implementarem e acharem que o mTLS está bloqueando esse tipo de situação terão problemas de segurança.

renatofrota commented 3 years ago

Acho que você está sendo humilde em prefixar com "[Dúvida]". Isso é um report de falha, seguramente.

joelemanoel commented 3 years ago

Acho que você está sendo humilde em prefixar com "[Dúvida]". Isso é um report de falha, seguramente.

Atualizado!

renatofrota commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

aldemaroc commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

Pelo que percebi atualmente o PSP valida qualquer certificado válido que minha URL tenha, então para o PSP é indiferente se eu definir o webhook para exemplo.com ou para google.com, desde que mTLS esteja configurado. A gerencianet, por exemplo, não está validando o certificado que me enviaram antes de enviar o webhook, mas sim qualquer certificado válido que minha URL tenha. Não sei se esse é o comportamento esperado nesse caso, mas é o que está ocorrendo agora.

Estamos cientes que a melhor prática seria validar o EndToEndId ao receber um webhook, mas ao mesmo tempo também vejo como muitas empresas irão implementar o uso da API confiando cegamente no mTLS para validar os webhooks, o que pode permitir um ataque com webhook "verdadeiro" como o destacado nesse issue.

joelemanoel commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

Hoje quando se trata de Cliente > PSP o mTLS funciona muito bem. Mas não quando se trata de PSP > Cliente isso é falho.

joelemanoel commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

Pelo que percebi atualmente o PSP valida qualquer certificado válido que minha URL tenha, então para o PSP é indiferente se eu definir o webhook para exemplo.com ou para google.com, desde que mTLS esteja configurado. A gerencianet, por exemplo, não está validando o certificado que me enviaram antes de enviar o webhook, mas sim qualquer certificado válido que minha URL tenha. Não sei se esse é o comportamento esperado nesse caso, mas é o que está ocorrendo agora.

Estamos cientes que a melhor prática seria validar o EndToEndId ao receber um webhook, mas ao mesmo tempo também vejo como muitas empresas irão implementar o uso da API confiando cegamente no mTLS para validar os webhooks, o que pode permitir um ataque com webhook "verdadeiro" como o destacado nesse issue.

O E2EID ainda requer mais 1 solicitação a API do PSP, com a chave sendo informada no Webhook por exemplo, nós podemos validar a transação em um só passo, diminuindo carga útil no PSP.

renatofrota commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

Pelo que percebi atualmente o PSP valida qualquer certificado válido que minha URL tenha, então para o PSP é indiferente se eu definir o webhook para exemplo.com ou para google.com, desde que mTLS esteja configurado. A gerencianet, por exemplo, não está validando o certificado que me enviaram antes de enviar o webhook, mas sim qualquer certificado válido que minha URL tenha. Não sei se esse é o comportamento esperado nesse caso, mas é o que está ocorrendo agora.

Estamos cientes que a melhor prática seria validar o EndToEndId ao receber um webhook, mas ao mesmo tempo também vejo como muitas empresas irão implementar o uso da API confiando cegamente no mTLS para validar os webhooks, o que pode permitir um ataque com webhook "verdadeiro" como o destacado nesse issue.

Pelo que eu vi na documentação, eles validam o mTLS na hora da definição da URL de webhook (e vi cliente relatando que isso está sendo seguido). Não entendi seu exemplo de definição de URL de webhook no domínio do Google. Pode elaborar melhor?

rubenskuhl commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

joelemanoel commented 3 years ago

Talvez o PSP devesse utilizar certificados diferentes para cada cliente a quem envia os callbacks? Isso talvez não escale fácil. Por outro lado, se não for feito, elimina a "tal" segurança que o mTLS oferece nesse contexto.

Pelo que percebi atualmente o PSP valida qualquer certificado válido que minha URL tenha, então para o PSP é indiferente se eu definir o webhook para exemplo.com ou para google.com, desde que mTLS esteja configurado. A gerencianet, por exemplo, não está validando o certificado que me enviaram antes de enviar o webhook, mas sim qualquer certificado válido que minha URL tenha. Não sei se esse é o comportamento esperado nesse caso, mas é o que está ocorrendo agora.

Estamos cientes que a melhor prática seria validar o EndToEndId ao receber um webhook, mas ao mesmo tempo também vejo como muitas empresas irão implementar o uso da API confiando cegamente no mTLS para validar os webhooks, o que pode permitir um ataque com webhook "verdadeiro" como o destacado nesse issue.

Pelo que eu vi na documentação, eles validam o mTLS na hora da definição da URL de webhook (e vi cliente relatando que isso está sendo seguido). Não entendi seu exemplo de definição de URL de webhook no domínio do Google. Pode elaborar melhor?

O que ele quis dizer é: Se o Google estiver utilizando a Chain do GerenciaNet por exemplo e ele também, ele consegue configurar para o Google receber as notificações.

renatofrota commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Complica desnecessariamente o setup e não agrega nada em segurança. Um terceiro ainda poderia responder da mesma forma que se espera que o recebedor efetivo responda.

rubenskuhl commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Complica desnecessariamente o setup e não agrega nada em segurança. Um terceiro ainda poderia responder da mesma forma que se espera que o recebedor efetivo responda.

Mas aí seria para o webhook dele. O caso aqui é alguém mandar webhooks de uma chave que um correntista tem para a conta de outra empresa no mesmo PSP, e isso é sim mitigado por esse método. Mas proponha outro se preferir... o problema é real.

aldemaroc commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Exatamente, na atual forma a API não valida se a URL é minha ou não. Talvez o correto fosse que o PSP apenas validasse o certificado que foi enviado por ele ao cliente, ao invés de qualquer certificado válido na URL. Dessa forma o PSP teria certeza que estaria "conversando" com o cliente justificando assim o uso de mTLS.

Se o PSP validar qualquer cerificado na URL não vejo motivos para utilizar mTLS já que não poderemos confiar no webhook de qualquer forma.

renatofrota commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Complica desnecessariamente o setup e não agrega nada em segurança. Um terceiro ainda poderia responder da mesma forma que se espera que o recebedor efetivo responda.

Mas aí seria para o webhook dele. O caso aqui é alguém mandar webhooks de uma chave que um correntista tem para a conta de outra empresa no mesmo PSP, e isso é sim mitigado por esse método. Mas proponha outro se preferir... o problema é real.

De fato, me confundi. Me desculpe.

  1. A inclusão da chave na resposta não afeta o processo de setup já existente do webhook e evitaria a necessidade de uma nova consulta ao PSP para conferir o e2eid.

  2. Quanto à mitigação do problema de segurança: o problema não existiria se o PSP simplesmente gerasse certificados individuais para cada cliente e enviasse os requests com mTLS de forma adequada (usando o certificado específico do cliente).

aldemaroc commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Complica desnecessariamente o setup e não agrega nada em segurança. Um terceiro ainda poderia responder da mesma forma que se espera que o recebedor efetivo responda.

Mas aí seria para o webhook dele. O caso aqui é alguém mandar webhooks de uma chave que um correntista tem para a conta de outra empresa no mesmo PSP, e isso é sim mitigado por esse método. Mas proponha outro se preferir... o problema é real.

De fato, me confundi. Me desculpe.

  1. A inclusão da chave na resposta não afeta o processo de setup já existente do webhook e evitaria a necessidade de uma nova consulta ao PSP para conferir o e2eid.
  2. Quanto à mitigação do problema de segurança: o problema não existiria se o PSP simplesmente gerasse certificados individuais para cada cliente e enviasse os requests com mTLS de forma adequada (usando o certificado específico do cliente).

Ao meu ver o PSP está meio correto. Ele inicia o mTLS com o certificado dele, então eu posso valida-lo, porém ele aceita qualquer cerificado da URL, então ele não pode me validar.

rubenskuhl commented 3 years ago

Uma possibilidade seria um método no webhook como /chave que o PSP precisasse chamar com /chave/xxx-xxx-xxx e ter um 200 OK antes de habilitar o webhook para aquela chave. Então eu titular da chave xxx-xxx-xxx vou responder OK para a minha chave e negar o recebimento de outras chaves.

Exatamente, na atual forma a API não valida se a URL é minha ou não. Talvez o correto fosse que o PSP apenas validasse o certificado que foi enviado por ele ao cliente, ao invés de qualquer certificado válido na URL. Dessa forma o PSP teria certeza que estaria "conversando" com o cliente justificando assim o uso de mTLS.

Se o PSP validar qualquer cerificado na URL não vejo motivos para utilizar mTLS já que não poderemos confiar no webhook de qualquer forma.

Uma opção seria o PSP gerar um CSR, enviar para o cliente, e o cliente gerar um certificado com base nesse CSR para o PSP usar. A idéia não é minha mas quando vi isso pela primeira vez era no contexto de ter que aceitar muitas CAs de PSPs, mas aqui tem outra utilidade que é evitar este problema.

rubenskuhl commented 3 years ago

Uma alternativa sem criar um /chave/xxx-xx-xxx no webhook é definir que o webhook é chamado pelo PSP em /xxx-xx-xxx. Aí o cliente cria o path da chave dele e todos os outros vão dar erro 404. Então a ativação indevida do webhook aconteceria, mas o cliente não receberia nenhum webhook exceto para a chave própria dele. Pode só dar problema de escaping em chaves que contem caracteres como + e @ .

aldemaroc commented 3 years ago

Uma alternativa sem criar um /chave/xxx-xx-xxx no webhook é definir que o webhook é chamado pelo PSP em /xxx-xx-xxx. Aí o cliente cria o path da chave dele e todos os outros vão dar erro 404. Então a ativação indevida do webhook aconteceria, mas o cliente não receberia nenhum webhook exceto para a chave própria dele. Pode só dar problema de escaping em chaves que contem caracteres como + e @ .

Nesse caso se alguém souber sua chave poderá também saber a URL do webhook. É necessário ainda que exista algum tipo de validação do lado do PSP.

Para mitigar isso nós estamos enviando nosso webhook para webhook.php?pw=123 com uma string aleatória que nosso backend verifica, porém caso não tivéssemos testado isso nem desconfiaríamos que é possível receber webhook de basicamente qualquer outra pessoa no mesmo PSP.

joelemanoel commented 3 years ago

Uma alternativa sem criar um /chave/xxx-xx-xxx no webhook é definir que o webhook é chamado pelo PSP em /xxx-xx-xxx. Aí o cliente cria o path da chave dele e todos os outros vão dar erro 404. Então a ativação indevida do webhook aconteceria, mas o cliente não receberia nenhum webhook exceto para a chave própria dele.

Pode só dar problema de escaping em chaves que contem caracteres como + e @ .

A solução que eu e @aldemaroc chegamos a implementar por enquanto é enviar uma hash na hora de cadastrar o Webhook e receber essa hash e validar na hora de receber. Talvez seguir isso diretamente na API seria interessante.

rubenskuhl commented 3 years ago

Uma alternativa sem criar um /chave/xxx-xx-xxx no webhook é definir que o webhook é chamado pelo PSP em /xxx-xx-xxx. Aí o cliente cria o path da chave dele e todos os outros vão dar erro 404. Então a ativação indevida do webhook aconteceria, mas o cliente não receberia nenhum webhook exceto para a chave própria dele. Pode só dar problema de escaping em chaves que contem caracteres como + e @ .

Nesse caso se alguém souber sua chave poderá também saber a URL do webhook. É necessário ainda que exista algum tipo de validação do lado do PSP.

Não tem problema alguém saber a chave, pois a sua URL de webhook não vai responder positivamente para outras chaves, que é o que alguém vai testar. Aliás, a chave não dá para imaginar secreta.

Para mitigar isso nós estamos enviando nosso webhook para webhook.php?pw=123 com uma string aleatória que nosso backend verifica, porém caso não tivéssemos testado isso nem desconfiaríamos que é possível receber webhook de basicamente qualquer outra pessoa no mesmo PSP.

É bom também, e equivalente à alternativa sem o /chave que citei. O /chave teria a vantagem de já negar a ativação do webhook.

aldemaroc commented 3 years ago

Não tem problema alguém saber a chave, pois a sua URL de webhook não vai responder positivamente para outras chaves, que é o que alguém vai testar. Aliás, a chave não dá para imaginar secreta.

No caso o PSP iria sempre acrescentar o /chave na URL de webhook que você informasse, seria isso? Se sim, é uma ótima solução também, porém nesse caso creio que seja mais fácil apenas também retornar a chave dentro do conteúdo do webhook e respeitar a URL que o recebedor define. Dessa forma o recebedor poderia validar internamente se o pagamento foi recebido para sua chave.

Aliás, qual o motivo do webhook já não conter a chave que recebeu o pagamento? Essa é uma informação importante se você recebe pagamento de chaves diferentes e tem o mesmo webhook configurado para todas.

rturk commented 3 years ago

@joelemanoel parabéns, bela issue.

Nota sobre issues de segurança: _Recomendo a todos que no futuro issues como estas sejam primeiro enviadas, em privado, ao comitê do Pix. Ao invés de serem colocadas em uma issue pública. Vide Responsible disclosure _

Sobre a issue em sí, que acho genial

Acredito este tipo de segurança e a responsabilidade por implementar isto corretamente está do lado de quem consome o webhook, o PSP até pode ajudar, mas no final do dia quem consome um webhook tem que saber o que esperar.

Compartilho como implementamos mTLs. Assim como todos aqui webhooks são importantes para nós!

Basicamente nós usamos a URL de retorno a nosso favor

Ou seja: Como nós que geramos e controlamos a URL de retorno usamos isto à nosso favor. De tal forma que a URL contém componentes da transação que somente nós conhecemos.

Considerando o caso que o @joelemanoel listou nosso setup ficou:

URL de retorno é gerada:

https://gerencianet.webhook.openpix.com.br/api/v1/webhook/YYYYY/XXXXX/BBBBB

Onde YYYYY, XXXX, BBB são itens da transação somente de nosso conhecimento.

flaviolenz commented 3 years ago

De fato, confiar na chamada webhook vai ter essa falha que o mTLS resolve. Vc encontrou o caso que prova isso.

Sempre defendi que o webhook fosse simplesmente um wakeup call e que o payload que interessa mesmo deveria ser buscado pelo cliente.

Hoje vem com isso (melhor que a especificacao antiga que vinha informação do pagador e tudo): {"horario":"2020-12-06T06:13:44.000Z","valor":"0.13","txid":"e433v1525u155f31mDvx01","endToEndId":"E2101818220201206061328598379545"}

Deveria vir so o e2e e o cliente fizesse uma consulta pra pegar o resto. (Inclusive, eh essa a minha abordagem.) {"endToEndId":"E2101818220201206061328598379545"} Se esse e2e nao eh da minha chave, fico sabendo na consulta.

PS...Nao exercam muito a criatividade com coisas do tipo 1 certificado pra cada cliente ou chave na url que isso nao vai ser solucao.

;-)

rubenskuhl commented 3 years ago

A discussão de ter ou não mTLS é superada; goste-se dele ou não, foi o caminho escolhido. E as soluções apontadas resolvem o problema, quer se goste disso ou não.

flaviolenz commented 3 years ago

Usemos o mTLS Rubens... canal de comunicacao seguro, e garante (+/-) que quem ta chamando eh o PSP. Como minha chave eh de qualquer CA conhecida nao tem como o PSP me validar... (daria se fosse ICPBrasil... hehehe.)

Tá aí um caso que mostra que continua sendo necessario fazer a consulta pra garantir que o pix eh meu mesmo.

rubenskuhl commented 3 years ago

Usemos o mTLS Rubens... canal de comunicacao seguro, e garante (+/-) que quem ta chamando eh o PSP. Como minha chave eh de qualquer CA conhecida nao tem como o PSP me validar... (daria se fosse ICPBrasil... hehehe.)

O problema não é validar. O PSP é o PSP mesmo, o EC é o EC mesmo... o uso indevido aqui é desse canal PSP-EC por um outro EC. É o A de Autorização que falhou, não o A de Autenticação.

Tá aí um caso que mostra que continua sendo necessario fazer a consulta pra garantir que o pix eh meu mesmo.

Não se alguma das mitigações já elencadas aqui forem usadas. Outra adicional que pode ser usada é o PSP não permitir que uma URL de webhook já usada por um cliente seja usada por outro cliente, desde que a pedido do cliente; um cliente pode ter várias chaves e usar várias chaves no mesmo webhook, mas se já está ativo no cliente. E isso pode funcionar mesmo em com gateway multi-clientes como cliente da API, pois cada URL pode ser distinta.

renatofrota commented 3 years ago

Pessoal,

o processo de mTLS depende de um certificado que o PSP te envia e você configura no seu web server pra exigir nas requisições ao seu hostname/URL de webhook.

Se o PSP enviar uma requisição para o seu URL de webhook mas a chamada é resultado de uma transação que ocorreu em uma chave que não é a sua (o seu concorrente - ou um mero fraudador tentando "dar baixa" no seu sistema de uma transação que não foi você quem efetivamente recebeu o pagamento), o requisito de mTLS não será atendido se o PSP tiver usado o certificado correspondente do lado dele. Não é questão de validar ou não a cadeia, é questão unicamente do PSP não reutilizar um mesmo certificado para enviar requests para os webhooks de diferentes clientes.

A mesma premissa não existe nos acessos que fazemos ao hostname da API Pix do PSP (devemos usar um certificado que foi configurado pelo PSP exclusivamente para nós)? Se o certificado que está sendo usando em qualquer um dos casos ("consumo da API por parte do EC" e "envio dos requests de webhook por parte do PSP") é reutilizado para diferentes clientes, a segurança do mTLS foi pro beleléu.

aldemaroc commented 3 years ago

Pessoal,

o processo de mTLS depende de um certificado que o PSP te envia e você configura no seu web server pra exigir nas requisições ao seu hostname/URL de webhook.

Se o PSP enviar uma requisição para o seu URL de webhook mas a chamada é resultado de uma transação que ocorreu em uma chave que não é a sua (o seu concorrente - ou um mero fraudador tentando "dar baixa" no seu sistema de uma transação que não foi você quem efetivamente recebeu o pagamento), o requisito de mTLS não será atendido se o PSP tiver usado o certificado correspondente do lado dele. Não é questão de validar ou não a cadeia, é questão unicamente do PSP não reutilizar um mesmo certificado para enviar requests para os webhooks de diferentes clientes.

A mesma premissa não existe nos acessos que fazemos ao hostname da API Pix do PSP (devemos usar um certificado que foi configurado pelo PSP exclusivamente para nós)? Se o certificado que está sendo usando em qualquer um dos casos ("consumo da API por parte do EC" e "envio dos requests de webhook por parte do PSP") é reutilizado para diferentes clientes, a segurança do mTLS foi pro beleléu.

Esse é o exato problema que estou relatando desde o início. Quando o PSP chama minha URL para enviar o webhook ele não exige o certificado que foi configurado do lado deles para mim, mas sim qualquer certificado.

Por exemplo, o @joelemanoel configurou o webhook de uma chave dele para apontar para uma URL minha e recebi corretamente o webhook dele. Eu não deveria ter recebido esse webhook que era destinado à ele.

rubenskuhl commented 3 years ago

Painel de possíveis mitigações já possíveis sem mudança da API:

Painel de possível acréscimo à API que permitiria já não ativar o webhook indevido:

renatofrota commented 3 years ago

Esse é o exato problema que estou relatando desde o início. Quando o PSP chama minha URL para enviar o webhook ele não exige o certificado que foi configurado do lado deles para mim, mas sim qualquer certificado.

Por exemplo, o @joelemanoel configurou o webhook de uma chave dele para apontar para uma URL minha e recebi corretamente o webhook dele. Eu não deveria ter recebido esse webhook que era destinado à ele.

Na verdade a única conclusão a que eu consigo chegar é que o certificado enviado pela GN pra vocês dois configurarem do lado de vocês é o mesmo certificado. Pois se fossem diferentes o servidor de vocês teria barrado e respondido o request com 400 ou 403 (a depender de como está configurado o vhost) - a menos que vocês tenham configurado o mTLS errado.

Estão debatendo como contornar um problema a nível de API, quando o problema é a nível de segurança do lado do PSP.

Obviamente, uma prevenção a esse erro pode ser implementada por parte do EC, definindo uma URL que contenha um "secret". Assim, em casos como o do @joelemanoel, que vende um módulo para WHMCS e tem uma forma "predeterminada" de funcionar e de setar o webhook, a URL não poderá ser deduzida por um terceiro.

aldemaroc commented 3 years ago

Esse é o exato problema que estou relatando desde o início. Quando o PSP chama minha URL para enviar o webhook ele não exige o certificado que foi configurado do lado deles para mim, mas sim qualquer certificado. Por exemplo, o @joelemanoel configurou o webhook de uma chave dele para apontar para uma URL minha e recebi corretamente o webhook dele. Eu não deveria ter recebido esse webhook que era destinado à ele.

Na verdade a única conclusão a que eu consigo chegar é que o certificado enviado pela GN pra vocês dois configurarem do lado de vocês é o mesmo certificado. Pois se fossem diferentes o servidor de vocês teria barrado e respondido o request com 400 ou 403 (a depender de como está configurado o vhost) - a menos que vocês tenham configurado o mTLS errado.

Estão debatendo como contornar um problema a nível de API, quando o problema é a nível de segurança do lado do PSP.

Obviamente, uma prevenção a esse erro pode ser implementada por parte do EC, definindo uma URL que contenha um "secret". Assim, em casos como o do @joelemanoel, que vende um módulo para WHMCS e tem uma forma "predeterminada" de funcionar e de setar o webhook, a URL não poderá ser deduzida por um terceiro.

Você não entendeu...

Quando eu chamo a API, para gerar uma cobrança por exemplo, tenho que usar o certificado que a GN enviou. Esse certificado funciona apenas para mim.

O problema é quando a GN chama minha URL para o webhook. Nesse caso ela aceita qualquer certificado. No meu caso, um certificado do Let's Encrypt. A API não se certifica se a URL é realmente minha antes de enviar o webhook. O Mutual TLS simplesmente não é mutual.

renatofrota commented 3 years ago

Esse é o exato problema que estou relatando desde o início. Quando o PSP chama minha URL para enviar o webhook ele não exige o certificado que foi configurado do lado deles para mim, mas sim qualquer certificado. Por exemplo, o @joelemanoel configurou o webhook de uma chave dele para apontar para uma URL minha e recebi corretamente o webhook dele. Eu não deveria ter recebido esse webhook que era destinado à ele.

Na verdade a única conclusão a que eu consigo chegar é que o certificado enviado pela GN pra vocês dois configurarem do lado de vocês é o mesmo certificado. Pois se fossem diferentes o servidor de vocês teria barrado e respondido o request com 400 ou 403 (a depender de como está configurado o vhost) - a menos que vocês tenham configurado o mTLS errado. Estão debatendo como contornar um problema a nível de API, quando o problema é a nível de segurança do lado do PSP. Obviamente, uma prevenção a esse erro pode ser implementada por parte do EC, definindo uma URL que contenha um "secret". Assim, em casos como o do @joelemanoel, que vende um módulo para WHMCS e tem uma forma "predeterminada" de funcionar e de setar o webhook, a URL não poderá ser deduzida por um terceiro.

Você não entendeu...

Quando eu chamo a API, para gerar uma cobrança por exemplo, tenho que usar o certificado que a GN enviou. Esse certificado funciona apenas para mim.

O problema é quando a GN chama minha URL para o webhook. Nesse caso ela aceita qualquer certificado. No meu caso, um certificado do Let's Encrypt. A API não se certifica se a URL é realmente minha antes de enviar o webhook. O Mutual TLS simplesmente não é mutual.

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

aldemaroc commented 3 years ago

Esse é o exato problema que estou relatando desde o início. Quando o PSP chama minha URL para enviar o webhook ele não exige o certificado que foi configurado do lado deles para mim, mas sim qualquer certificado. Por exemplo, o @joelemanoel configurou o webhook de uma chave dele para apontar para uma URL minha e recebi corretamente o webhook dele. Eu não deveria ter recebido esse webhook que era destinado à ele.

Na verdade a única conclusão a que eu consigo chegar é que o certificado enviado pela GN pra vocês dois configurarem do lado de vocês é o mesmo certificado. Pois se fossem diferentes o servidor de vocês teria barrado e respondido o request com 400 ou 403 (a depender de como está configurado o vhost) - a menos que vocês tenham configurado o mTLS errado. Estão debatendo como contornar um problema a nível de API, quando o problema é a nível de segurança do lado do PSP. Obviamente, uma prevenção a esse erro pode ser implementada por parte do EC, definindo uma URL que contenha um "secret". Assim, em casos como o do @joelemanoel, que vende um módulo para WHMCS e tem uma forma "predeterminada" de funcionar e de setar o webhook, a URL não poderá ser deduzida por um terceiro.

Você não entendeu... Quando eu chamo a API, para gerar uma cobrança por exemplo, tenho que usar o certificado que a GN enviou. Esse certificado funciona apenas para mim. O problema é quando a GN chama minha URL para o webhook. Nesse caso ela aceita qualquer certificado. No meu caso, um certificado do Let's Encrypt. A API não se certifica se a URL é realmente minha antes de enviar o webhook. O Mutual TLS simplesmente não é mutual.

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

Já testamos aqui várias vezes. Se você quiser homologar te passo uma URL minha com mTLS configurado e você vai ver que consegue configurar webhook em uma chave sua para ela.

rubenskuhl commented 3 years ago

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

A documentação da GN especifica um certificado único para configuração no webhook, o que é a raiz do problema.

renatofrota commented 3 years ago

Já testamos aqui várias vezes. Se você quiser homologar te passo uma URL minha com mTLS configurado e você vai ver que consegue configurar webhook em uma chave sua para ela.

Sim, cara, mas aí o mTLS tá sendo fechado (com o certificado da GN configurado no seu vhost). Não tem relação com ter um certificado Let's Encrypt (que também é necessário: a URL precisa ser https:// e o certificado da URL precisa ser de CA reconhecida). O problema é eu conseguir usar minhas credenciais de API para definir um webhook pro seu domínio, onde tá instalado um certificado de webhook que é geral (quando o mais seguro seria ele ser diferente para cada cliente).

aldemaroc commented 3 years ago

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

A documentação da GN especifica um certificado único para configuração no webhook, o que é a raiz do problema.

Esse não é nem o problema, é de fato esperado que o PSP tenha sempre o mesmo certificado do lado dele, porém ele não está verificando meu lado. A URL do webhook pode ter qualquer certificado, não há validação do cliente.

Em termos não técnicos: eu tenho certeza que é o PSP que fala comigo (validei o certificado deles), mas o PSP não tem certeza sobre com quem está falando (validam qualquer certificado).

renatofrota commented 3 years ago

Seria mais seguro a GN aceitar os certificados emitidos pela Cloudflare (diferente para cada cliente - e que pode ser emitido com Private Key fornecida pelo cliente) do que usar um certificado que é emitido pela própria GN mas que é igual pra todos os clientes.

passo 1: https://developers.cloudflare.com/ssl/client-certificates/enable-mtls passo 2: https://developers.cloudflare.com/ssl/client-certificates/create-a-client-certificate passo 3: https://developers.cloudflare.com/firewall/cf-dashboard/create-api-shield-rule#use-the-api-shield-rule-interface

joelemanoel commented 3 years ago

Seria mais seguro a GN aceitar os certificados emitidos pela Cloudflare (diferente para cada cliente - e que pode ser emitido com Private Key fornecida pelo cliente) do que usar um certificado que é emitido pela própria GN mas que é igual pra todos os clientes.

passo 1: https://developers.cloudflare.com/ssl/client-certificates/enable-mtls

passo 2: https://developers.cloudflare.com/ssl/client-certificates/create-a-client-certificate

passo 3: https://developers.cloudflare.com/firewall/cf-dashboard/create-api-shield-rule#use-the-api-shield-rule-interface

Duvido que de alguma forma isso ocorra...

renatofrota commented 3 years ago

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

A documentação da GN especifica um certificado único para configuração no webhook, o que é a raiz do problema.

Esse não é nem o problema, é de fato esperado que o PSP tenha sempre o mesmo certificado do lado dele, porém ele não está verificando meu lado. A URL do webhook pode ter qualquer certificado, não há validação do cliente.

Em termos não técnicos: eu tenho certeza que é o PSP que fala comigo (validei o certificado deles), mas o PSP não tem certeza sobre com quem está falando (validam qualquer certificado).

Mais uma vez: você está confundindo o certificado instalado para autenticar sua URL (https://) com o certificado que vai no seu vhost para fechar o mTLS (que é emitido pela GN). Por favor, não fale essa asneira de novo. Só está funcionando por que você tem o certificado da GN no vhost e a GN usa o mesmo para todos os clientes. Não está aceitando "qualquer" certificado. Troque o certificado no vhost por um outro certificado qualquer (o seu Let's Encrypt, por exemplo) e você vai ver que não vai fechar mTLS.

joelemanoel commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário.

Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional.

Vamos lá:

O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob).

Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

aldemaroc commented 3 years ago

Cara, você tem certeza absoluta do que está dizendo? A GN valida, sim, o certificado do seu lado (o mTLS é fechado com um certificado emitido pela GN). Talvez o @joelemanoel tenha feito a instalação do módulo pra você e você esteja falando abobrinha. (só talvez)

A documentação da GN especifica um certificado único para configuração no webhook, o que é a raiz do problema.

Esse não é nem o problema, é de fato esperado que o PSP tenha sempre o mesmo certificado do lado dele, porém ele não está verificando meu lado. A URL do webhook pode ter qualquer certificado, não há validação do cliente. Em termos não técnicos: eu tenho certeza que é o PSP que fala comigo (validei o certificado deles), mas o PSP não tem certeza sobre com quem está falando (validam qualquer certificado).

Mais uma vez: você está confundindo o certificado instalado para autenticar sua URL (https://) com o certificado que vai no seu vhost para fechar o mTLS (que é emitido pela GN). Por favor, não fale essa asneira de novo. Só está funcionando por que você tem o certificado da GN no vhost e a GN usa o mesmo para todos os clientes. Não está aceitando "qualquer" certificado. Troque o certificado no vhost por um outro certificado qualquer (o seu Let's Encrypt, por exemplo) e você vai ver que não vai fechar mTLS.

Me passa uma URL sua com mTLS configurado para a GN. Vou te enviar uns webhooks.

rubenskuhl commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário.

Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional.

Vamos lá:

O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob).

Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

Enviar a chave já é possível hoje, é só colocar a chave na configuração do webhook. Ao invés de só colocar https://webhook.exemplo.com.br/pix, colocar https://webhook.exemplo.com.br/chave/pix .

renatofrota commented 3 years ago

Seria mais seguro a GN aceitar os certificados emitidos pela Cloudflare (diferente para cada cliente - e que pode ser emitido com Private Key fornecida pelo cliente) do que usar um certificado que é emitido pela própria GN mas que é igual pra todos os clientes. passo 1: https://developers.cloudflare.com/ssl/client-certificates/enable-mtls passo 2: https://developers.cloudflare.com/ssl/client-certificates/create-a-client-certificate passo 3: https://developers.cloudflare.com/firewall/cf-dashboard/create-api-shield-rule#use-the-api-shield-rule-interface

Duvido que de alguma forma isso ocorra...

Por que? É mais seguro que usar 1 certificado só pra todo mundo.

E o BACEN não estipula como deve ser fechado o mTLS, só especifica que deve ser fechado um mTLS:

https://github.com/bacen/pix-api/issues/216#issuecomment-737963012

joelemanoel commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário.

Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional.

Vamos lá:

O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob).

Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

Enviar a chave já é possível hoje, é só colocar a chave na configuração do webhook. Ao invés de só colocar https://webhook.exemplo.com.br/pix, colocar https://webhook.exemplo.com.br/chave/pix .

Digo num contexto mais específico, seria mais interessante o próprio PSP já enviar, não?

renatofrota commented 3 years ago

Me passa uma URL sua com mTLS configurado para a GN. Vou te enviar uns webhooks.

Se você mesmo está dizendo "mTLS configurado para a GN", não fica claro pra você que é a presença do certificado da GN na configuração do vhost do meu servidor que vai garantir que o mTLS funcione quando você acionar o envio dos webhooks? :joy:

rubenskuhl commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário.

Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional.

Vamos lá:

O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob).

Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

Enviar a chave já é possível hoje, é só colocar a chave na configuração do webhook. Ao invés de só colocar https://webhook.exemplo.com.br/pix, colocar https://webhook.exemplo.com.br/chave/pix .

Digo num contexto mais específico, seria mais interessante o próprio PSP já enviar, não?

É o PSP que aciona o webhook, então é o PSP que vai enviar... a questão de segurança é diferente por ser de autorização, por isso as mitigações apontadas. Mas a informação de qual chave um Pix usou já é possível, basta usar a API dessa forma.

renatofrota commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário. Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional. Vamos lá: O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob). Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

Enviar a chave já é possível hoje, é só colocar a chave na configuração do webhook. Ao invés de só colocar https://webhook.exemplo.com.br/pix, colocar https://webhook.exemplo.com.br/chave/pix .

O @joelemanoel quis dizer incluir a chave no corpo da notificação enviada ao webhook (pra saber qual chave recebeu o pagamento). Hoje o corpo da notificação não detalha isso.

E incluir "/chave" na URL não resolve. O concorrente/fraudador continua podendo definir a URL com a chave (que é uma informação pública), com base no domínio + padrão de URL de esperado + chave Pix.

Ou você está falando "chave" aqui querendo dizer um elemento "secret" da URL?

rubenskuhl commented 3 years ago

Pessoal, acho que está tomando um rumo diferente do necessário. Em relação ao que o @renatofrota citou de que o problema está na segurança do PSP e não na API, pode se dizer que tudo é adicional. Vamos lá: O PSP deveria informar a chave e daria pelo menos pra comparar off-line (sem necessidade de uma nova chamada ao /cob). Em relação à segurança, como o @aldemaroc citou, nós validamos quem é o PSP, mas o PSP não nos valida, assim como acontece na hora de criar uma cobrança por exemplo.

Enviar a chave já é possível hoje, é só colocar a chave na configuração do webhook. Ao invés de só colocar https://webhook.exemplo.com.br/pix, colocar https://webhook.exemplo.com.br/chave/pix .

O @joelemanoel quis dizer incluir a chave no corpo da notificação enviada ao webhook (pra saber qual chave recebeu o pagamento). Hoje o corpo da notificação não detalha isso.

Eu sei que foi o que ele sugeriu, mas o mesmo efeito de ter a informação já é conseguido hoje de outra forma, prescindindo mudança na API.

E incluir "/chave" na URL não resolve. O concorrente/fraudador continua podendo definir a URL com a chave (que é uma informação pública), com base no domínio + padrão de URL de esperado + chave Pix.

Não é como solução para a questão de segurança, onde as possibilidades eu listei em https://github.com/bacen/pix-api/issues/239#issuecomment-741343147 .

Ou você está falando "chave" aqui querendo dizer um elemento "secret" da URL?

Era a chave Pix mesmo, destino dos recursos.

aldemaroc commented 3 years ago

Me passa uma URL sua com mTLS configurado para a GN. Vou te enviar uns webhooks.

Se você mesmo está dizendo "mTLS configurado para a GN", não fica claro pra você que é a presença do certificado da GN na configuração do vhost do meu servidor que vai garantir que o mTLS funcione quando você acionar o envio dos webhooks? 😂

Todos aqui no issue já explicaram, mas vou te passar mais uma vez.

No mTLS um lado verifica o outro. O PSP precisa ter apenas um certificado do lado deles para que eu possa valida-los, e é isso que ocorre. Se você não quiser validar isso, tudo bem, só aceitar qualquer certificado do lado deles.

O problema ocorre quando o PSP vai validar o meu certificado. Ele não valida e aceita qualquer certificado que eu envie, então ele envia o webhook para qualquer URL que tenha mTLS configurado.

Como já está bem detalhado no issue, qualquer cliente de um determinado PSP pode enviar webhooks para outros clientes do mesmo PSP, já que o PSP não valida o certificado da URL.

rubenskuhl commented 3 years ago

Como já está bem detalhado no issue, qualquer cliente de um determinado PSP pode enviar webhooks para outros clientes do mesmo PSP, já que o PSP não valida o certificado da URL.

Ele até pode estar validando ser um certificado válido assinado por CA reconhecida. Mas o PSP precisa de um elemento de comparação na conta para saber que o certificado é associado a essa conta. E isso não pode ser feito na API, é parâmetro para o dashboard. Exemplo: para conta X, só aceitar certificados do domínio exemplo.com.br.

renatofrota commented 3 years ago

No mTLS um lado verifica o outro. O PSP precisa ter apenas um certificado do lado deles para que eu possa valida-los, e é isso que ocorre. Se você não quiser validar isso, tudo bem, só aceitar qualquer certificado do lado deles.

O problema ocorre quando o PSP vai validar o meu certificado. Ele não valida e aceita qualquer certificado que eu envie, então ele envia o webhook para qualquer URL que tenha mTLS configurado.

Pra isso ser verdade, o webhook deveria continuar funcionando depois que você retirasse ou substituísse o certificado da GN por outro na configuração de mTLS do seu vhost.

Como já está bem detalhado no issue, qualquer cliente de um determinado PSP pode enviar webhooks para outros clientes do mesmo PSP, já que o PSP não valida o certificado da URL.

Se o teste acima falhar, significa que a GN valida. Se continuar funcionando, aí sim significaria, de fato, que ela não valida.