Verifica tus Webhooks de GitHub con HMAC SHA-256 (X-Hub-Signature-256)
Keyword objetivo: verificar webhook github hmac sha256 x-hub-signature-256
¿Recibes webhooks de GitHub y necesitas asegurarte de que son legítimos? Este artículo te guiará paso a paso para verificar la firma HMAC SHA-256 (X-Hub-Signature-256) en tus webhooks, protegiendo tu aplicación de ataques y asegurando la integridad de los datos. Aprenderás a evitar los errores más comunes y a implementar una verificación robusta en Node.js y PHP.
¿Por qué verificar los webhooks de GitHub?
Los webhooks de GitHub son una herramienta poderosa para automatizar tareas y reaccionar a eventos en tus repositorios. Sin embargo, sin una verificación adecuada, podrías ser vulnerable a ataques. Verificar la firma HMAC SHA-256 te permite:
- Confirmar la autenticidad: Asegúrate de que el webhook proviene de GitHub.
- Proteger la integridad: Verifica que el contenido del webhook no ha sido alterado durante la transmisión.
- Prevenir ataques: Evita que atacantes envíen payloads maliciosos a tu aplicación.
Paso a paso: Verifica la firma X-Hub-Signature-256 de GitHub
Sigue estos pasos para verificar la firma HMAC SHA-256 en tus webhooks de GitHub:
1. Obtén el 'secret' de tu webhook
El 'secret' es una clave compartida que configuras en la configuración del webhook en GitHub. Este valor es crucial y debe mantenerse en secreto.
Dónde encontrarlo: En la configuración del webhook de tu repositorio de GitHub, en la sección "Secret".
2. Accede al 'raw body' del webhook
El 'raw body' es el contenido del webhook tal como lo envía GitHub. Es importante obtener el cuerpo sin que sea modificado por ningún proceso de parseo o middleware.
Ejemplo en Node.js (Express):
// Middleware para obtener el raw body
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const rawBody = req.body.toString(); // Asegúrate de convertir a string
// ... resto de la verificación
});
Ejemplo en PHP:
// Obtener el raw body
$rawBody = file_get_contents("php://input");
3. Extrae la firma 'X-Hub-Signature-256'
La firma se encuentra en el header 'X-Hub-Signature-256' del request HTTP. Este header contiene la firma HMAC-SHA256 del cuerpo del webhook.
Ejemplo:
X-Hub-Signature-256: sha256=a1b2c3d4e5f6...
Extracción en Node.js:
const signature = req.headers['x-hub-signature-256'];
Extracción en PHP:
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? ''; // Usa null coalescing operator
4. Calcula la firma HMAC-SHA256
Utiliza el 'secret' y el 'raw body' para calcular la firma HMAC-SHA256. Asegúrate de usar la misma codificación (encoding) que GitHub.
Ejemplo en Node.js:
const crypto = require('crypto');
function calculateSignature(rawBody, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(rawBody);
return 'sha256=' + hmac.digest('hex');
}
Ejemplo en PHP:
function calculateSignature($rawBody, $secret) {
$hash = hash_hmac('sha256', $rawBody, $secret);
return 'sha256=' . $hash;
}
5. Compara las firmas de forma segura
Compara la firma calculada con la firma recibida en el header. Importante: Usa una comparación de tiempo constante para evitar ataques de timing.
Ejemplo en Node.js:
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const calculatedSignature = calculateSignature(rawBody, secret);
return crypto.timingSafeEqual(Buffer.from(calculatedSignature), Buffer.from(signature));
}
Ejemplo en PHP:
function verifySignature($rawBody, $signature, $secret) {
$calculatedSignature = calculateSignature($rawBody, $secret);
return hash_equals($calculatedSignature, $signature);
}
6. Implementa la lógica completa
Aquí tienes un ejemplo completo de cómo integrar todos los pasos:
Ejemplo completo en Node.js (Express):
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = 3000;
// Middleware para obtener el raw body
app.use(express.raw({ type: 'application/json' }));
function calculateSignature(rawBody, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(rawBody);
return 'sha256=' + hmac.digest('hex');
}
function verifySignature(rawBody, signature, secret) {
const calculatedSignature = calculateSignature(rawBody, secret);
return crypto.timingSafeEqual(Buffer.from(calculatedSignature), Buffer.from(signature));
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const rawBody = req.body.toString();
const secret = 'TU_SECRET'; // Reemplaza con tu secret
if (!signature) {
console.warn('Firma no encontrada');
return res.status(400).send('Firma requerida');
}
if (!rawBody) {
console.warn('Body no encontrado');
return res.status(400).send('Body requerido');
}
if (verifySignature(rawBody, signature, secret)) {
console.log('Webhook verificado correctamente');
// Procesa el webhook
res.status(200).send('OK');
} else {
console.error('Firma inválida');
res.status(400).send('Firma inválida');
}
});
app.listen(port, () => {
console.log(`Servidor escuchando en el puerto ${port}`);
});
Ejemplo completo en PHP:
Checklist para verificar webhooks de GitHub
Utiliza esta lista de verificación para asegurarte de que tu implementación es robusta y segura:
- ✓ Obtén el 'secret' correcto: Verifica que estás usando el 'secret' configurado en GitHub.
- ✓ Accede al 'raw body': Asegúrate de acceder al cuerpo del request sin modificarlo.
- ✓ Extrae la firma correctamente: Extrae el header 'X-Hub-Signature-256' correctamente.
- ✓ Calcula la firma HMAC-SHA256: Usa la función adecuada para calcular la firma con el 'secret' y el 'raw body'.
- ✓ Compara las firmas de forma segura: Utiliza `crypto.timingSafeEqual` en Node.js o `hash_equals` en PHP.
- ✓ Maneja los errores: Implementa la lógica para responder a los errores de verificación (ej. devolver un código de estado 400).
- ✓ Registra la información relevante: Registra la información del webhook (delivery ID) para facilitar la depuración y el seguimiento.
- ✓ Prueba tu implementación: Utiliza una herramienta para simular webhooks de GitHub y probar tu código.
Errores comunes y cómo solucionarlos
Estos son algunos de los errores más frecuentes que pueden causar fallos en la verificación de webhooks, y cómo evitarlos:
1. Secret incorrecto o con espacios
Problema: Usar un 'secret' incorrecto, o que contenga espacios en blanco al principio o al final, es el error más común. Esto causa que la firma calculada no coincida con la firma enviada por GitHub.
Solución: Verifica cuidadosamente el 'secret' en tu configuración del webhook. Asegúrate de que no haya espacios adicionales al copiarlo.
2. Body parseado en lugar del 'raw body'
Problema: Si tu framework o middleware parsea el cuerpo del request antes de que puedas acceder a él, podrías estar calculando la firma sobre datos modificados. Esto resultará en una verificación fallida.
Solución: Asegúrate de acceder al 'raw body' del request antes de que sea procesado por cualquier middleware que pueda modificarlo. En frameworks como Express.js, usa `express.raw()` para acceder al cuerpo sin parsear.
3. Olvidar el prefijo 'sha256='
Problema: La firma en el header 'X-Hub-Signature-256' siempre incluye el prefijo 'sha256='. Si comparas solo la parte hexadecimal de la firma, la verificación fallará.
Solución: Asegúrate de incluir el prefijo 'sha256=' al comparar las firmas.
4. Comparación insegura de strings
Problema: Usar una comparación de strings simple (ej. `==` en PHP o `===` en JavaScript) puede ser vulnerable a ataques de timing. Un atacante puede enviar múltiples solicitudes y medir el tiempo que tarda la comparación en fallar, revelando información sobre la firma.
Solución: Utiliza una comparación de tiempo constante (ej. `crypto.timingSafeEqual` en Node.js o `hash_equals` en PHP).
5. No registrar el Delivery ID
Problema: GitHub incluye un header `X-GitHub-Delivery` con un ID único para cada entrega de webhook. Si no registras este ID, será más difícil depurar problemas y rastrear reintentos.
Solución: Registra el valor del header `X-GitHub-Delivery` junto con cualquier error que ocurra durante el procesamiento del webhook.
6. Permitir requests sin firma
Problema: No verificar la firma o permitir el acceso a los webhooks sin verificar la firma, abre una puerta a posibles ataques.
Solución: Rechaza todos los requests que no incluyan la firma correcta. Devuelve un error 400 (Bad Request) o 401 (Unauthorized) si la verificación falla.
Preguntas frecuentes sobre la verificación de webhooks de GitHub
¿Por qué GitHub envía firmas SHA-1 (X-Hub-Signature) además de SHA-256?
GitHub sigue soportando SHA-1 por compatibilidad con versiones anteriores. Sin embargo, debes priorizar el uso de SHA-256 (X-Hub-Signature-256) ya que es más seguro. Si tu aplicación recibe ambas firmas, elige la SHA-256.
¿Es necesario validar el User-Agent del request?
Validar el User-Agent puede ser una medida adicional de seguridad, pero no es la forma principal de verificar la autenticidad del webhook. La firma HMAC-SHA256 es la medida de seguridad más importante. La validación del User-Agent puede ayudar a detectar requests inesperados, pero no debe ser la única capa de protección.
¿Qué debo hacer si la verificación falla solo con payloads grandes?
Los problemas con payloads grandes suelen estar relacionados con límites de tamaño del request en tu servidor o proxy, o problemas de encoding. Asegúrate de:
- Configurar el límite de tamaño del request: Verifica que tu servidor (ej. Nginx, Apache) y tu framework (ej. Express.js) estén configurados para aceptar payloads del tamaño esperado.
- Verificar el encoding: Asegúrate de que el encoding del cuerpo del request (ej. UTF-8) sea consistente en todas las partes del proceso (envío, recepción, cálculo de la firma).
- Evitar el truncamiento: Verifica que el cuerpo del request no se esté truncando en ningún punto del proceso. Imprime el 'raw body' antes de calcular la firma para verificar su contenido.
¿Cómo puedo probar mi implementación de verificación de webhooks?
Puedes usar herramientas como Webhook.site o Smee.io para simular el envío de webhooks y probar tu implementación. También puedes usar la herramienta Validador Webhooks Hmac Sha256.
Recomendación final
Verificar los webhooks de GitHub con HMAC SHA-256 es crucial para la seguridad de tu aplicación. Sigue los pasos descritos en este artículo, utiliza la lista de verificación y ten en cuenta los errores comunes para asegurar una implementación robusta. Recuerda siempre mantener tu 'secret' en secreto y priorizar las comparaciones seguras de firmas.
¿Eres nuevo en la verificación de webhooks? Empieza con los ejemplos de código proporcionados y utiliza la lista de verificación para asegurarte de cubrir todos los aspectos. Prueba tu implementación con una herramienta de simulación de webhooks.
¿Ya tienes experiencia? Revisa tu implementación actual, prestando especial atención a los errores comunes. Considera la posibilidad de registrar el Delivery ID y utilizar una herramienta de monitoreo para detectar cualquier problema con los webhooks.