Informationen zur Validierung von Webhook-Zustellungen
Sobald Ihr Server für den Empfang von Nutzdaten konfiguriert ist, horcht er auf jede Übermittlung , die an den von Ihnen konfigurierten Endpunkt gesendet wird. Um sicherzustellen, dass Ihr Server nur Webhook-Zustellungen verarbeitet, die von GitHub gesendet wurden, und um sicherzustellen, dass die Zustellung nicht manipuliert wurde, sollten Sie die Webhook-Signatur überprüfen, bevor Sie die Zustellung weiter verarbeiten. Dies hilft Ihnen, Serverzeit für die Verarbeitung von Zustellungen zu vermeiden, die nicht aus GitHub stammen, sowie dabei, Man-in-the-Middle-Angriffe zu vermeiden.
Gehen Sie hierzu wie folgt vor:
- Erstellen Sie ein geheimes Token für einen Webhook.
- Speichern Sie den Token sicher auf Ihrem Server.
- Validieren Sie eingehende Webhook-Nutzdaten anhand des Tokens, um zu überprüfen, ob sie von GitHub stammen.
Erstellen eines geheimen Tokens
Sie können einen neuen Webhook mit einem geheimen Token erstellen oder einen geheimen Token zu einem bestehenden Webhook hinzufügen. Wenn Sie einen geheimen Token erstellen, sollten Sie eine zufällige Textfolge mit hoher Entropie wählen.
- Um einen neuen Webhook mit einem geheimen Token zu erstellen, siehe „Erstellen von Webhooks“.
- Um einen geheimen Token zu einem bestehenden Webhook hinzuzufügen, bearbeiten Sie die Einstellungen des Webhooks. Geben Sie unter „Geheimnis“ eine Zeichenfolge ein, die als
secret
-Schlüssel verwendet werden soll. Weitere Informationen findest du unter Bearbeiten von Webhooks.
Sichere Speicherung des geheimen Tokens
Nachdem Sie einen geheimen Token erstellt haben, sollten Sie ihn an einem sicheren Ort speichern, auf den Ihr Server zugreifen kann. Programmieren Sie niemals ein Token fest in eine Anwendung ein oder geben Sie einen Token niemals an ein Repository weiter. Weitere Informationen über die sichere Verwendung von Authentifizierungsnachweisen in Ihrem Code finden Sie unter „Schützen deiner API-Anmeldeinformationen“.
Validierung von Webhook-Zustellung
GitHub Enterprise Server verwendet Ihren geheimen Token, um eine Hash-Signatur zu erstellen, die Ihnen mit jeder Nutzlast gesendet wird. Die Hash-Signatur erscheint in jeder Übermittlung als Wert der X-Hub-Signature-256
Kopfzeile. Weitere Informationen findest du unter Webhook-Ereignisse und -Nutzlasten.
In Ihrem Code, der Webhook-Übermittlungen verarbeitet, sollten Sie einen Hash mit Ihrem geheimen Token berechnen. Vergleichen Sie dann den Hash, den GitHub gesendet hat, mit dem erwarteten Hash, den Sie berechnet haben, und stellen Sie sicher, dass sie übereinstimmen. Beispiele, die zeigen, wie die Hashes in verschiedenen Programmiersprachen validiert werden können, finden Sie unter „Beispiele“.
Bei der Validierung von Webhook-Payloads sind einige wichtige Dinge zu beachten:
- GitHub Enterprise Server verwendet einen hexadezimalen HMAC-Digest (Hash-based Message Authentication Code), um den Hash zu berechnen.
- Die Hash-Signatur beginnt immer mit
sha256=
. - Die Hash-Signatur wird mit dem geheimen Token Ihres Webhooks und dem Inhalt der Nutzdaten erstellt.
- Wenn deine Sprach- und Serverimplementierung eine Zeichencodierung angibt, musst du sicherstellen, dass diese Nutzdaten als UTF-8 behandelt werden. Webhook-Payloads können Unicode-Zeichen enthalten.
- Verwenden Sie niemals einen einfachen
==
Operator. Ziehen Sie stattdessen eine Methode wiesecure_compare
odercrypto.timingSafeEqual
in Erwägung, die einen String-Vergleich in „konstanter Zeit“ durchführt, um bestimmte Timing-Angriffe gegen reguläre Gleichheitsoperatoren oder reguläre Schleifen in JIT-optimierten Sprachen zu entschärfen.
Testen der Webhook-Nutzlastvalidierung
Sie können die folgenden secret
und payload
Werte verwenden, um zu überprüfen, ob Ihre Implementierung korrekt ist:
secret
: „Es ist ein Geheimnis für alle“payload
: „Hallo, Welt!“
Wenn Ihre Implementierung korrekt ist, sollten die von Ihnen erstellten Signaturen mit den folgenden Signaturwerten übereinstimmen:
- Signatur:
757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- X-Hub-Signatur-256:
sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Beispiele
Sie können eine Programmiersprache Ihrer Wahl verwenden, um die HMAC-Verifizierung in Ihrem Code zu implementieren. Nachfolgend finden Sie einige Beispiele, die zeigen, wie eine Implementierung in verschiedenen Programmiersprachen aussehen könnte.
Ruby-Beispiel
Beispielsweise kannst du die folgende verify_signature
-Funktion definieren:
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
Anschließend kannst du die Funktion aufrufen, wenn du eine Webhooknutzlast empfängst:
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
Beispiel für Python
Du kannst beispielsweise die folgende verify_signature
-Funktion definieren und aufrufen, wenn du eine Webhooknutzlast empfängst:
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!")
JavaScript-Beispiel
Sie können beispielsweise die folgende verifySignature
-Funktion definieren und sie in jeder JavaScript-Umgebung aufrufen, wenn Sie eine Webhook-Nutzlast empfangen:
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;
}
Typescript-Beispiel
Du kannst beispielsweise die folgende verify_signature
-Funktion definieren und aufrufen, wenn du eine Webhooknutzlast empfängst:
import * as crypto from "crypto"; const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET; const verify_signature = (req: Request) => { const signature = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest("hex"); let trusted = Buffer.from(`sha256=${signature}`, 'ascii'); let untrusted = Buffer.from(req.headers.get("x-hub-signature-256"), 'ascii'); return crypto.timingSafeEqual(trusted, untrusted); }; const handleWebhook = (req: Request, res: Response) => { if (!verify_signature(req)) { res.status(401).send("Unauthorized"); return; } // The rest of your logic here };
import * as crypto from "crypto";
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;
const verify_signature = (req: Request) => {
const signature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
let trusted = Buffer.from(`sha256=${signature}`, 'ascii');
let untrusted = Buffer.from(req.headers.get("x-hub-signature-256"), 'ascii');
return crypto.timingSafeEqual(trusted, untrusted);
};
const handleWebhook = (req: Request, res: Response) => {
if (!verify_signature(req)) {
res.status(401).send("Unauthorized");
return;
}
// The rest of your logic here
};
Problembehandlung
Wenn Sie sicher sind, dass die Nutzdaten aus GitHub stammen, die Signatur-Überprüfung jedoch fehlschlägt:
- Stellen Sie sicher, dass Sie ein Geheimnis für Ihren Webhook konfiguriert haben. Der
X-Hub-Signature-256
-Header ist nicht vorhanden, wenn Sie kein Geheimnis für Ihren Webhook konfiguriert haben. Weitere Informationen zu Webhook-Geheimnissen finden Sie unter „Bearbeiten von Webhooks“. - Stellen Sie sicher, dass Sie die richtige Kopfzeile verwenden. GitHub empfiehlt, den
X-Hub-Signature-256
-Header zu verwenden, der den HMAC-SHA256-Algorithmus verwendet. DerX-Hub-Signature
-Header verwendet den HMAC-SHA1-Algorithmus und ist nur für Legacy-Zwecke enthalten. - Stellen Sie sicher, dass Sie den richtigen Algorithmus verwenden. Wenn Sie den
X-Hub-Signature-256
-Header verwenden, sollten Sie den HMAC-SHA256-Algorithmus verwenden. - Stellen Sie sicher, dass Sie das richtige Webhook-Geheimnis verwenden. Wenn Sie den Wert Ihres Webhook-Geheimnisses nicht kennen, können Sie das Webhook-Geheimnis aktualisieren. Weitere Informationen findest du unter Bearbeiten von Webhooks.
- Stellen Sie sicher, dass die Nutzdaten und die Header vor der Überprüfung nicht geändert werden. Wenn Sie z. B. einen Proxy oder einen Lastenausgleich verwenden, stellen Sie sicher, dass der Proxy oder lastenausgleich keine Nutzdaten oder Header ändert.
- Wenn deine Sprach- und Serverimplementierung eine Zeichencodierung angibt, musst du sicherstellen, dass diese Nutzdaten als UTF-8 behandelt werden. Webhook-Payloads können Unicode-Zeichen enthalten.