Skip to main content

A importância de verificar as assinaturas de webhooks

Escrito por

Marcelo Oliveira

blog feature playwright tests

29 de junho de 2023

0 minutos de leitura

Webhooks são uma técnica de integração por callback usada para enviar e receber informações, como notificações de eventos, quase em tempo real. Eles podem ser acionados por eventos de um aplicativo e transmitir dados por HTTP para outro aplicativo ou API de terceiros. Você pode configurar uma URL de webhook e conectar participantes externos para personalizar, ampliar ou modificar fluxos de trabalho.

Os webhooks podem ou não ser assinados. No entanto, por segurança, é recomendável incluir uma assinatura verificável para que o sistema que recebe a solicitação confirme se ela veio da origem esperada do webhook.

Veja alguns exemplos de como os webhooks são usados atualmente:

  • Notificações em uma plataforma de controle de versão baseada em Git quando um desenvolvedor envia código para o repositório.

  • Notificações quando um usuário responde a uma conversa em uma plataforma de mensagens.

  • Notificações de um serviço de pagamento que confirma a autorização de uma forma de pagamento para uma compra no varejo.

De acordo com um relatório da Verizon, os ataques à cadeia de suprimentos foram responsáveis por 62% de todos os incidentes de invasão de sistemas em 2022. Durante todo o ciclo de vida do desenvolvimento de software, é fundamental manter a segurança de todos os componentes e práticas envolvidos no desenvolvimento e na implantação. Se exploradas, as vulnerabilidades podem comprometer os parceiros envolvidos e interromper toda a cadeia de suprimentos de software.

Para lidar com essas ameaças e manter a segurança da cadeia de suprimentos, os fornecedores da cadeia de suprimentos exigem assinaturas de webhook. Por exemplo, com o CircleCI, você pode usar assinaturas de webhook para validar um segredo e garantir que somente o CircleCI — e não um agente mal-intencionado — acione seu webhook. O Travis CI também fornece um cabeçalho HTTP de assinatura no processo de verificação de webhooks.

Neste passo a passo, vamos implementar um webhook do GitHub em Node.js que detecta quando usuários enviam código para um repositório.

Pré-requisitos

Para acompanhar o passo a passo, você precisará:

  • Baixar e instalar o Node.js e o npm. 

  • Instalar o ngrok, uma ferramenta de proxy reverso que cria um túnel seguro entre uma URL pública e seu aplicativo Node.js local.

  • Instalar o Postman, uma plataforma que permite criar solicitações HTTP e inspecionar os resultados com facilidade.

Como verificar assinaturas de webhook usando Node.js

Crie uma nova pasta na sua máquina e um arquivo chamado webhook.js com o código a seguir. Lembre-se de substituir o valor da constante repo pelo caminho do repositório local:

const http = require('http');
const exec = require('child_process').exec;

const repo = "C:\\Users\\your-user-name\\Documents\\GitHub\\webhook-test";

http
  .createServer((req, res) => {
    req.on('data', chunk => {

      const body = JSON.parse(chunk);

      console.log(body);

      const isMain = body?.ref === 'refs/heads/main';

      if (isMain) {
        try {
            console.log('Push event detected. Pulling repository updates...');
            exec(`cd ${repo} && git fetch && git pull`);
            console.log('Git repository pulled successfully.');
        } catch (error) {
          console.log(error);
        }
      }
      res.end();
    });
  })
  .listen(8080);

Agora, execute a seguinte linha no terminal:

node webhook.js

Veja o que queremos fazer com o arquivo webhook.js:

  1. A constante repo armazena o caminho da pasta que contém o repositório que queremos monitorar.

  2. A função createServer inicia um servidor HTTP na nossa máquina, na porta 8080. O aplicativo de webhook está em execução e pronto para receber solicitações no endpoint http://localhost:8080.

  3. Depois que a solicitação recebida é processada, a constante body armazena a maior parte do conteúdo da solicitação. Uma solicitação ao nosso endpoint significa que estamos recebendo uma notificação do webhook do GitHub na URL de payload local http://localhost:8080, informando que alguém enviou código para o repositório que estamos monitorando.

  4. Queremos garantir especificamente que o envio tenha sido feito para a branch main. Para verificar isso, analisamos os dados JSON da solicitação em um objeto body e usamos a constante isMain para indicar se a referência corresponde à branch main.

  5. Se a solicitação for aceita, executamos um comando para entrar no diretório do repositório local. Por fim, executamos os comandos git fetch e git pull para atualizá-lo.

Simulando uma solicitação mal-intencionada com o Postman

Estamos desenvolvendo o aplicativo Node.js para funcionar com o GitHub. No entanto, antes de usar o GitHub, vamos simular uma solicitação mal-intencionada com o Postman para mostrar como nosso aplicativo é vulnerável. Isso vai ajudar você a entender a importância das assinaturas.

Abra o aplicativo Postman e configure uma nova solicitação HTTP POST para a URL de payload local http://localhost:8080.

Na aba Body, adicione o seguinte código:

{
    "ref": "refs/heads/main",
    "comment": "hi, I'm a malicious request..."
}
Requisição do Postman mostrando um payload de webhook e uma resposta 401 Unauthorized informando que a assinatura do webhook não corresponde ao hash do payload.

Clique em Send. Abra o terminal onde o Node.js está em execução e observe o que aconteceu:

Saída do terminal mostrando um evento de push do Git detectado, as atualizações do repositório sendo baixadas e a operação de pull do repositório concluída com sucesso.

Como você pode ver, o aplicativo Node.js está aceitando uma solicitação de uma origem diferente do GitHub, o que é perigoso. Precisamos implementar assinaturas de webhook no código para verificar se quem fez a solicitação é legítimo.

Configurando o ngrok

Neste cenário, estamos executando o aplicativo Node.js localmente. No entanto, o GitHub não consegue acessar o endpoint http://localhost:8080 na sua máquina para enviar uma solicitação de webhook. Podemos resolver isso publicando o aplicativo Node.js em um servidor público. Mas é mais fácil executar e depurar o Node.js na máquina de desenvolvimento do que implantá-lo em um servidor web ou na nuvem. Por isso, vamos usar o ngrok. O ngrok é uma ferramenta de proxy reverso que cria um túnel seguro entre uma URL pública aleatória e seu endpoint local de webhook.

Abra uma janela do terminal na sua máquina e execute o seguinte comando do ngrok para expor a porta local 8080:

ngrok http 8080

O ngrok vai confirmar que expôs a porta local 8080 por meio de um túnel seguro com uma URL pública aleatória:

Forwarding https://<<YOUR-RANDOM-SUBDOMAIN>>.sa.ngrok.io -> http://localhost:8080

Em seguida, vamos usar o subdomínio público do ngrok acima para configurar um webhook do GitHub.

Configurando um webhook do GitHub

Acesse a página do GitHub para criar um novo repositório e adicione um repositório para configurar o webhook:

Formulário de criação de repositório com o título “Create a new repository”, um proprietário oculto e o nome do repositório “webhook-test”.

Clique em Settings na barra superior. Em seguida, selecione a opção Webhooks na lista à esquerda e clique em Add webhook:

Página de configurações do repositório do GitHub, com Webhooks selecionado na barra lateral e setas apontando para o título Webhooks e o botão Add webhook.

Isso abrirá o formulário Add webhook, no qual vamos configurar um webhook para um evento em que um desenvolvedor envia código para o repositório.

Formulário de configurações para adicionar um webhook do GitHub, com URL de payload, tipo de conteúdo JSON, campo de segredo, opções de acionamento por evento e caixa de seleção Ativo.

Agora, preencha o formulário da seguinte forma:

  • No campo Payload URL, insira o endereço do ngrok relativo ao endpoint do seu aplicativo Node.js local.

  • No campo Content type, selecione "application/json" para indicar o tipo de conteúdo que o GitHub enviará ao endpoint.

  • No campo Secret, digite "some-webhook-secret". Lembre-se de que, em produção, você precisará usar senhas ou frases mais fortes para garantir a segurança.

  • Em Which events would you like to trigger this webhook, selecione Just the push event. Assim, o webhook do GitHub será configurado para a ação de envio ao repositório que você pode acionar para testar a verificação.

Observação: É sempre recomendável habilitar a verificação de SSL (Secure Socket Layer) na configuração do webhook. Com a verificação de SSL ativada, as organizações garantem que os dados transmitidos entre sistemas estejam seguros e não possam ser interceptados por pessoas não autorizadas.

Depois de preencher o formulário, clique em Add webhook.

Formulário para adicionar webhook do GitHub, com setas destacando o URL da carga útil, o tipo de conteúdo JSON, o segredo e a seleção do evento push.

Protegendo o webhook

Precisamos adicionar o módulo crypto do Node.js para lidar com dados criptografados. Adicione esta linha ao primeiro bloco do arquivo webhook.js:

const crypto = require('crypto');

Em seguida, declare uma constante para armazenar a mesma string secreta que você configurou no webhook do GitHub:

const secret = 'some-webhook-secret';

Observação: Nunca é recomendável inserir segredos diretamente no código. Este método serve apenas para fins de demonstração.

No início da função req.on, declaramos uma constante para armazenar o valor da assinatura SHA256 calculada com HMAC. Obtenha a assinatura HMAC-SHA256 executando uma função de hash criptográfica SHA256 sobre a chave secreta compartilhada e o valor enviado pela solicitação do GitHub (representado aqui pelo parâmetro chunk):

    req.on('data', chunk => {

      const hashAlgorithm = 'sha256'
      const signature = Buffer.from(req.headers['x-hub-signature-256'] || '', 'utf8')
      const hmac = crypto.createHmac(hashAlgorithm, secret)
      const digest = Buffer.from(hashAlgorithm + '=' + hmac.update(chunk).digest('hex'), 'utf8')

Em seguida, declaramos a constante isAllowed dentro do evento req.on para indicar se o cabeçalho x-hub-signature-256 corresponde à assinatura que calculamos. Use a função crypto.timingSafeEqual, que compara duas variáveis sem revelar informações de tempo que poderiam ajudar um invasor a adivinhar um dos valores:

      const isAllowed = (signature.length === digest.length && crypto.timingSafeEqual(digest, signature));
      if (!isAllowed) {
        const msg = 'Webhook signature does not match the hash of the payload';
        console.log(msg);
        res.writeHead(401)
        res.end(msg)
        return;
      }

No código acima, se houver correspondência, podemos ter certeza de que a solicitação veio do GitHub, e não de um agente mal-intencionado. Se a solicitação não vier do GitHub, o código retornará uma resposta HTTP 401 Unauthorized.

Veja algumas práticas recomendadas aplicadas neste código, que você deve adotar ao desenvolver seus webhooks:

  • Ao realizar operações com strings para calcular assinaturas de webhook, sempre trate o payload como UTF-8, pois ele pode conter caracteres Unicode. Além disso, se você tiver expectativas sobre como os dados devem aparecer, poderá implementar um método de validação para garantir que os dados recebidos estejam no formato esperado.

  • Alguns provedores de webhook, como o GitHub, podem incluir um cabeçalho x-hub-signature nas solicitações. O cabeçalho x-hub-signature é gerado com SHA-1, considerado um algoritmo de hash fraco, mas é incluído apenas para garantir compatibilidade com versões anteriores. Sempre que possível, use o cabeçalho x-hub-signature-256, que é gerado com o algoritmo forte SHA-256.

  • Comece a assinatura de hash com "sha256=", usando o segredo do corpo do payload da solicitação.

  • Em vez de usar um operador de igualdade simples ("==" ou "==="), use um método de comparação seguro que reduza as chances de ataques de temporização. Todas as linguagens e plataformas modernas têm pelo menos um desses métodos seguros. Neste projeto Node.js, estamos usando a função crypto.timingSafeEqual.

Você pode acessar o código completo de webhook.js aqui.

Para obter mais informações, consulte a documentação do GitHub sobre como proteger webhooks.

Testando o endpoint de webhook protegido

Como você modificou o código, reinicie o servidor Node local. No console, pare o servidor Node com Ctrl+C (ou Command+C no Mac) e execute novamente o comando node webhook.js.

Em seguida, volte ao aplicativo Postman e envie a solicitação novamente. Agora, o aplicativo Node.js responde com o código HTTP 401 Unauthorized:

Solicitação do Postman mostrando um payload JSON malicioso e uma resposta 401 Não autorizado informando que a assinatura do webhook não corresponde ao hash do payload

Abra o GitHub e adicione um arquivo README.md, caso ainda não tenha feito isso, ou abra o arquivo se ele já existir. Em seguida, adicione ou altere o texto conforme abaixo:

Editor do README.md do GitHub exibindo um repositório chamado webhook-test com o texto “My WebHook Test” e “This is a webhook test.”

Por fim, clique em Commit changes para enviar as alterações para a branch main do repositório:

Janela “Commit changes” do GitHub mostrando uma mensagem de atualização do README.md e a opção de fazer commit diretamente na branch principal, destacada por uma seta roxa.

Agora, volte ao terminal do Node.js para confirmar que o repositório foi buscado e atualizado com sucesso:

Saída do terminal mostrando um evento de push de webhook do GitHub, a atualização do repositório e a execução bem-sucedida do Git pull.

Conclusão

Os webhooks permitem a comunicação entre ferramentas e fornecem dados em tempo real quando ocorrem eventos. Você pode integrá-los aos sistemas para criar fluxos de trabalho colaborativos flexíveis e ampliados, algo que não seria possível com APIs que usam parâmetros predefinidos.

Como são abertos por natureza, os webhooks estão sujeitos a ameaças à segurança. Por isso, é essencial tomar as medidas adequadas para prevenir ataques à cadeia de suprimentos.

As assinaturas de webhook são a solução recomendada, pois os provedores de webhook podem configurar segredos e gerar assinaturas usando algoritmos de hash fortes. As assinaturas são uma forma simples e segura de garantir que a comunicação de dados entre webhooks aconteça somente entre parceiros verificados e confiáveis.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está avançando mais rápido do que os fluxos de trabalho de segurança tradicionais. O AppSec agentivo da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

blog feature ai violet
Blog

VulnBump: agentes de IA conseguem corrigir dependências vulneráveis sem quebrar o build?

O VulnBump testa agentes de IA em 307 atualizações reais de dependências. Uma correção só é considerada válida se eliminar a vulnerabilidade e mantiver o build e os testes funcionando.

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.