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.

Sobre la verificación de las entregas de webhook

Una vez que tu servidor esté configurado para recibir cargas útiles, escuchará cualquier transmisión que se envíe al punto de conexión que hayas configurado. 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 utilizar tiempo del servidor para procesar envíos que no proceden de GitHub además de ayudarle 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 AUTOTITLE.
  • 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 . Para más información, consulta AUTOTITLE.

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 AUTOTITLE.

Validación de entregas de webhook

GitHub utilizará su token secreto para crear una firma de hash que se le envía con cada carga. La firma del hash aparecerá en cada una de las entregas como el valor del encabezado. Para más información, consulta AUTOTITLE.

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 digest hexadecimal HMAC para calcular el hash.
  • La firma del hash siempre empieza con .
  • 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 una codificación de caracteres, asegúrate de manejar la carga útil como UTF-8. Las cargas de webhook pueden contener caracteres Unicode.
  • Nunca use un operador básico. En su lugar, considere el uso de un método como o , 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.

Pruebas de la verificación de la carga útil del webhook

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

  • :
  • :

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

  • firma:
  • X-Hub-Signature-256:

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 :

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 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 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 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

  • AUTOTITLE
  • AUTOTITLE