Skip to main content

Validación de entregas de webhook

Puede usar un secreto de webhook para comprobar que una entrega de webhook procede de GitHub.

Validación de entregas de webhook

Una vez que tu servidor se configure para recibir cargas útiles, éste escuchará a cualquier entrega que se envíe a la terminal que configuró. Para asegurarse de que el servidor solo procesa las entregas de webhook enviadas por GitHub y para asegurarse de que la entrega no se ha alterado, debe validar la firma de webhook antes de procesar la entrega aún más. Esto le ayudará a evitar pasar tiempo del servidor para procesar entregas que no proceden de GitHub y le ayudará a evitar ataques de tipo “Man-in-the-middle”.

Para ello, necesitas lo siguiente:

  1. Crear un token secreto para un webhook.
  2. Almacenar el token de forma segura en el servidor.
  3. Valida las cargas de webhook entrantes con el token para comprobar que proceden de GitHub y no han sido manipuladas.

Creación de un token secreto

Puede crear un nuevo webhook con un token secreto o puede agregar un token secreto a un webhook existente. Al crear un token secreto, debe elegir una cadena aleatoria de texto con alta entropía.

  • Para crear un nuevo webhook con un token secreto, consulta Crear webhooks.
  • Para agregar un token secreto a un webhook existente, edite las configuraciones del webhook. En el campo “Secreto”, escriba una cadena que se usará como clave secret. Para más información, consulta Editar los webhooks.

Almacenamiento seguro del token secreto

Después de crear un token secreto, debe almacenarlo en una ubicación segura a la que pueda acceder el servidor. Nunca codifique de forma rígida un token en una aplicación o inserte un token en cualquier repositorio. Para obtener más información sobre cómo usar las credenciales de autenticación de forma segura en el código, consulta Protección de las credenciales de API.

Validación de entregas de webhook

GitHub utilizará tu token secreto para crear una firma de hash que se te haya enviado con cada carga. La firma del hash aparecerá en cada entrega como valor del encabezado X-Hub-Signature-256. Para más información, consulta Eventos y cargas de webhook.

En el código que controla las entregas de webhook, debe calcular un hash mediante el token secreto. A continuación, compare el hash que GitHub envió con el hash esperado que calculó y asegúrese de que coincidan. Para obtener ejemplos que muestran cómo validar los hashes en varios lenguajes de programación, consulta Ejemplos.

Hay algunas cosas importantes que debe tener en cuenta al validar las cargas de webhook:

  • GitHub usa un código hash hexadecimal HMAC para calcular el hash.
  • La firma del hash siempre empieza con sha256=.
  • La firma hash se genera mediante el token secreto del webhook y el contenido de la carga.
  • Si tu implementación de idioma y servidor especifican un cifrado de caracteres, asegúrate de que estés manejando la carga útil como UTF-8. Las cargas de webhook pueden contener caracteres Unicode.
  • Nunca use un operador sin formato ==. En su lugar, considere el uso de un método como secure_compare o crypto.timingSafeEqual, que realiza una comparación de cadenas de “tiempo constante”, lo que ayuda a mitigar determinados ataques de tiempo contra operadores de igualdad convencionales, o bucles convencionales en lenguajes optimizados para JIT.

Prueba de la validación de la carga del webhook

Puede usar los siguientes valores secret y payload para comprobar que la implementación es correcta:

  • secret: It's a Secret to Everybody
  • payload: Hello, World!

Si la implementación es correcta, las firmas que generes deben coincidir con los siguientes valores de firma:

  • firma: 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
  • X-Hub-Signature-256: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17

Ejemplos

Puede usar el lenguaje de programación que prefiera para implementar la comprobación de HMAC en el código. A continuación encontrará algunos ejemplos que muestran cómo luciría una implementación en varios lenguajes de programación.

Ejemplo de Ruby

Por ejemplo, puedes definir la siguiente función verify_signature:

def verify_signature(payload_body)
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
  return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end

Después, puedes llamarla cuando recibas una carga de webhook:

post '/payload' do
  request.body.rewind
  payload_body = request.body.read
  verify_signature(payload_body)
  push = JSON.parse(payload_body)
  "I got some JSON: #{push.inspect}"
end

Ejemplo de Python

Por ejemplo, puedes definir la siguiente función verify_signature y llamarla cuando recibas una carga de webhook:

import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
    """Verify that the payload was sent from GitHub by validating SHA256.

    Raise and return 403 if not authorized.

    Args:
        payload_body: original request body to verify (request.body())
        secret_token: GitHub app webhook token (WEBHOOK_SECRET)
        signature_header: header received from GitHub (x-hub-signature-256)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
    hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()
    if not hmac.compare_digest(expected_signature, signature_header):
        raise HTTPException(status_code=403, detail="Request signatures didn't match!")

Ejemplo de JavaScript

Por ejemplo, puedes definir la siguiente función verifySignature y llamarla en cualquier entorno de JavaScript cuando recibas una carga de webhook:

let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
    let parts = header.split("=");
    let sigHex = parts[1];

    let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

    let keyBytes = encoder.encode(secret);
    let extractable = false;
    let key = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        algorithm,
        extractable,
        [ "sign", "verify" ],
    );

    let sigBytes = hexToBytes(sigHex);
    let dataBytes = encoder.encode(payload);
    let equal = await crypto.subtle.verify(
        algorithm.name,
        key,
        sigBytes,
        dataBytes,
    );

    return equal;
}

function hexToBytes(hex) {
    let len = hex.length / 2;
    let bytes = new Uint8Array(len);

    let index = 0;
    for (let i = 0; i < hex.length; i += 2) {
        let c = hex.slice(i, i + 2);
        let b = parseInt(c, 16);
        bytes[index] = b;
        index += 1;
    }

    return bytes;
}

Ejemplo de TypeScript

Por ejemplo, puedes definir la siguiente función verify_signature y llamarla cuando recibas una carga de webhook:

JavaScript
import { Webhooks } from "@octokit/webhooks";

const webhooks = new Webhooks({
  secret: process.env.WEBHOOK_SECRET,
});

const handleWebhook = async (req, res) => {
  const signature = req.headers["x-hub-signature-256"];
  const body = await req.text();

  if (!(await webhooks.verify(body, signature))) {
    res.status(401).send("Unauthorized");
    return;
  }

  // The rest of your logic here
};

Solución de problemas

Si está seguro de que la carga procede de GitHub pero se produce un error en la comprobación de la firma:

  • Asegúrese de que ha configurado un secreto para el webhook. El encabezado X-Hub-Signature-256 no estará presente si no ha configurado un secreto para el webhook. Para más información sobre cómo configurar un secreto para el webhook, consulta Editar los webhooks.
  • Asegúrese de que usa el encabezado correcto. GitHub recomienda usar el encabezado X-Hub-Signature-256, que utiliza el algoritmo HMAC-SHA256. El encabezado X-Hub-Signature usa el algoritmo HMAC-SHA1 y solo se incluye con fines de herencia.
  • Asegúrese de que usa el algoritmo correcto. Si usa el encabezado X-Hub-Signature-256, debe usar el algoritmo HMAC-SHA256.
  • Asegúrese de que usa el secreto de webhook correcto. Si no conoce el valor del secreto de webhook, puede actualizar el secreto del webhook. Para más información, consulta Editar los webhooks.
  • Asegúrese de que la carga y los encabezados no se modifiquen antes de la comprobación. Por ejemplo, si usa un proxy o un equilibrador de carga, asegúrese de que el proxy o el equilibrador de carga no modifiquen la carga o los encabezados.
  • Si tu implementación de idioma y servidor especifican un cifrado de caracteres, asegúrate de que estés manejando la carga útil como UTF-8. Las cargas de webhook pueden contener caracteres Unicode.

Información adicional