La importancia de verificar las firmas de los webhooks
Marcelo Oliveira
29 de junio de 2023
0 minutos de lecturaLos webhooks son una técnica de integración mediante callbacks para enviar y recibir información, como notificaciones de eventos, casi en tiempo real. Pueden activarse por eventos de una aplicación y transmitir datos por HTTP a otra aplicación o API de terceros. Puedes configurar una URL de webhook y conectar participantes externos para personalizar, ampliar o modificar flujos de trabajo.
Los webhooks pueden estar firmados o no. Sin embargo, por motivos de seguridad, se recomienda incluir una firma verificable para que el receptor pueda confirmar que la solicitud proviene de la fuente de webhook esperada.
Estos son algunos ejemplos de cómo se usan los webhooks actualmente:
Notificaciones en una plataforma de control de versiones basada en Git cuando un desarrollador envía código al repositorio.
Notificaciones cuando un usuario responde a un hilo de mensajes en una plataforma de mensajería.
Notificaciones de un servicio de pagos que confirman que se autorizó un método de pago para una compra minorista.
Según un informe de Verizon, los ataques a la cadena de suministro representaron el 62 % de todos los incidentes de intrusión en sistemas en 2022. A lo largo del ciclo de vida del desarrollo de software, es fundamental mantener la seguridad de todos los componentes y prácticas que intervienen en el desarrollo y la implementación. Si se explotan las vulnerabilidades, pueden poner en riesgo a los socios involucrados y afectar toda la cadena de suministro de software.
Para hacer frente a estas amenazas y mantener la seguridad de la cadena de suministro, los proveedores de la cadena de suministro aplican firmas de webhook. Por ejemplo, con CircleCI puedes usar firmas de webhook para validar un secreto y asegurarte de que solo CircleCI, y no un actor malicioso, invoque tu webhook. Travis CI también proporciona un encabezado HTTP de firma en su proceso de verificación de webhooks.
En este recorrido, implementaremos un webhook de GitHub en Node.js que detecta cuándo los usuarios envían código a un repositorio.
Requisitos previos
Para seguir los pasos, necesitarás hacer lo siguiente:
Descargar e instalar Node.js y npm.
Instalar ngrok, una herramienta de proxy inverso que abre un túnel seguro desde una URL pública hasta tu aplicación Node.js local.
Instalar Postman, una plataforma que te permite crear solicitudes HTTP e inspeccionar fácilmente los resultados.
Verificar firmas de webhook con Node.js
Crea una carpeta nueva en tu equipo y un archivo nuevo llamado webhook.js con el siguiente código. Recuerda reemplazar el valor de la constante repo por la ruta de tu repositorio local:
Ahora, ejecuta la siguiente línea en tu terminal:
Esto es lo que buscamos lograr con el archivo webhook.js:
La constante
repoalmacena la ruta a la carpeta que contiene el repositorio que queremos monitorear.La función
createServerinicia un servidor HTTP en nuestro equipo, en el puerto 8080. La aplicación del webhook ya está en ejecución y lista para recibir solicitudes en el endpoint http://localhost:8080.Una vez que se procesa la solicitud entrante, la constante body almacena la mayor parte de su contenido. Una solicitud a nuestro endpoint significa que recibimos una notificación del webhook de GitHub en nuestra URL local de carga útil, http://localhost:8080, que nos informa que alguien envió código al repositorio que estamos monitoreando.
Queremos asegurarnos de que el envío se haya realizado específicamente a la rama main. Para verificarlo, analizamos los datos JSON de la solicitud en un objeto body y usamos la constante
isMainpara indicar si la referencia coincide con la rama main.Si se acepta la solicitud, ejecutamos un comando para cambiar al repositorio local. Por último, ejecutamos los comandos
git fetchygit pullpara actualizarlo.
Simular una solicitud maliciosa con Postman
Estamos diseñando la aplicación Node.js para que funcione con GitHub. Sin embargo, antes de usar GitHub, simulemos una solicitud maliciosa con Postman para mostrar lo vulnerable que es nuestra aplicación. Esto te ayudará a comprender la importancia de las firmas.
Abre la aplicación Postman y configura una nueva solicitud HTTP POST a nuestra URL local de carga útil: http://localhost:8080.
En la pestaña Body, agrega el siguiente código:

Haz clic en Send. Abre la terminal donde se está ejecutando Node.js y observa qué ocurrió:

Como puedes ver, la aplicación Node.js acepta una solicitud de una fuente que no es GitHub, lo cual es peligroso. Necesitamos implementar firmas de webhook en nuestro código para verificar si quien realiza la solicitud es legítimo.
Configurar ngrok
En este escenario, ejecutamos la aplicación Node.js de forma local. Sin embargo, GitHub no puede acceder al endpoint http://localhost:8080 de tu equipo para enviar una solicitud de webhook. Podemos resolverlo publicando la aplicación Node.js en un servidor público. No obstante, es más fácil ejecutar y depurar Node.js en el equipo de desarrollo que implementarlo en un servidor web o en la nube, así que usaremos ngrok. ngrok es una herramienta de proxy inverso que abre un túnel seguro desde una URL pública aleatoria hasta el endpoint local del webhook.
Abre una ventana de terminal en tu equipo y ejecuta el siguiente comando de ngrok para exponer el puerto local 8080:
ngrok confirmará que expuso el puerto local 8080 mediante un túnel seguro con una URL pública aleatoria:
A continuación, usaremos el subdominio público de ngrok que aparece arriba para configurar un webhook de GitHub.
Configurar un webhook de GitHub
Visita la página de GitHub para crear un repositorio nuevo y agrega un repositorio nuevo para configurar el webhook:

Haz clic en Settings en la barra superior. Luego, selecciona la opción Webhooks en la lista de la izquierda y haz clic en Add webhook:

Se abrirá el formulario Add webhook, donde configuraremos un webhook para un evento en el que un desarrollador envía código al repositorio.

Ahora, completa el formulario de la siguiente manera:
En el campo Payload URL, ingresa la dirección de ngrok correspondiente al endpoint de tu aplicación local de Node.js.
En el campo Content type, selecciona «application/json» para indicar el tipo de contenido que GitHub enviará al endpoint.
En el campo Secret, escribe «some-webhook-secret». Recuerda que, en producción, necesitarás contraseñas o frases más seguras.
En Which events would you like to trigger this webhook, selecciona Just the push event. Así configurarás el webhook de GitHub para una acción de envío al repositorio que podrás activar para probar la verificación.
Nota: Siempre se recomienda habilitar la verificación de Secure Socket Layer (SSL) en la configuración del webhook. Al habilitarla, las organizaciones se aseguran de que los datos transmitidos entre sistemas estén protegidos y no puedan ser interceptados por partes no autorizadas.
Cuando hayas completado el formulario, haz clic en Add webhook.

Proteger el webhook
Necesitamos agregar el módulo crypto de Node.js para manejar datos cifrados. Agrega esta línea al primer bloque del archivo webhook.js:
A continuación, declara una constante para almacenar la misma cadena secreta que configuraste en tu webhook de GitHub:
Nota: Nunca se recomienda codificar los secretos directamente. Este método es solo para fines demostrativos.
Al comienzo de la función req.on, declaramos una constante para almacenar el valor de la firma SHA256 calculada con HMAC. Obtén la firma HMAC-SHA256 ejecutando una función hash criptográfica SHA256 sobre la clave secreta compartida y el valor enviado en la solicitud de GitHub (representado aquí por el parámetro chunk):
A continuación, declaramos la constante isAllowed dentro del evento req.on para indicar si el encabezado x-hub-signature-256 coincide con la firma que calculamos. Usa la función crypto.timingSafeEqual, que compara dos variables sin revelar información de tiempo que pudiera ayudar a un atacante a adivinar alguno de los valores:
En el código anterior, si hay una coincidencia, podemos tener la certeza de que la solicitud proviene de GitHub y no de un actor malicioso. Si la solicitud no proviene de GitHub, se devuelve una respuesta HTTP 401 Unauthorized.
Estas son algunas prácticas recomendadas que se aplican en este código y que deberías implementar al desarrollar tus webhooks:
Al realizar operaciones con cadenas para calcular las firmas del webhook, siempre procesa la carga útil como UTF-8, ya que puede contener caracteres Unicode. Además, si tienes expectativas sobre cómo deberían verse tus datos, puedes implementar un método de validación para asegurarte de que los datos recibidos tengan el formato esperado.
Algunos proveedores de webhooks (como GitHub) pueden incluir un encabezado
x-hub-signatureen las solicitudes. El encabezadox-hub-signaturese genera con SHA-1, que se considera un algoritmo hash débil, pero solo se incluye por compatibilidad con versiones anteriores. Siempre que sea posible, usa el encabezadox-hub-signature-256, que se genera con el algoritmo SHA-256, más seguro.Inicia la firma hash con
"sha256="usando el secreto del cuerpo de la carga útil de la solicitud.En lugar de usar un operador de igualdad simple (
"=="o"==="), utiliza un método de comparación seguro que reduzca la probabilidad de sufrir ataques de temporización. Todos los lenguajes y plataformas modernos cuentan con al menos uno de estos métodos seguros. En este proyecto de Node.js, usamos la funcióncrypto.timingSafeEqual.
Puedes obtener el código completo de webhook.js aquí.
Para obtener información más detallada, consulta la documentación de GitHub sobre cómo proteger los webhooks.
Probar el endpoint del webhook protegido
Como modificaste el código, debes reiniciar tu servidor local de Node. En la consola, detén el servidor de Node con Ctrl+C (o Command+C en Mac) y vuelve a ejecutar el comando node webhook.js.
Luego, vuelve a la aplicación Postman y reenvía la solicitud. Ahora, la aplicación Node.js responde con el código HTTP 401 Unauthorized:

Abre GitHub y agrega un archivo README.md si aún no lo tienes, o ábrelo si ya existe. Luego, agrega o modifica el texto como se muestra a continuación:

Por último, haz clic en Commit changes para enviar los cambios a la rama main del repositorio:

Ahora, vuelve a la terminal de Node.js para confirmar que el repositorio se haya obtenido y actualizado correctamente:

Conclusión
Los webhooks permiten la comunicación entre herramientas y proporcionan datos en tiempo real cuando ocurren eventos. Puedes integrarlos en sistemas para crear flujos de trabajo colaborativos flexibles y ampliables, algo que no sería posible con API que tienen parámetros predefinidos.
Como están abiertos por diseño, los webhooks son vulnerables a amenazas de seguridad, por lo que es esencial tomar las medidas adecuadas para prevenir ataques a la cadena de suministro.
Las firmas de webhook son la solución recomendada, ya que los proveedores de webhooks pueden configurar secretos y generar firmas con algoritmos hash seguros. Las firmas son una forma sencilla y segura de garantizar que la comunicación de datos entre webhooks ocurra únicamente entre socios verificados y de confianza.
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.



