웹후크 제공 유효성 검사 알아보기
페이로드를 수신하도록 서버를 구성하고 나면, 구성한 엔드포인트에 제공된 모든 페이로드를 수신 대기합니다. 서버가 GitHub에서 보낸 웹후크 제공만 처리하도록 하고 제공이 변조되지 않았는지 확인하려면, 제공을 계속 처리하기 전에 웹후크 서명의 유효성을 검사해야 합니다. 이렇게 하면 GitHub 이외의 제공을 처리하는 데 서버 시간을 소비하지 않고 중간자 공격을 방지하는 데 도움이 됩니다.
이렇게 하려면 다음을 수행해야 합니다.
- 웹후크에 대한 비밀 토큰을 만듭니다.
- 서버에 토큰을 안전하게 저장합니다.
- 토큰에 대해 들어오는 웹후크 페이로드의 유효성을 검사하여 GitHub에서 가져온 것이고 변조되지 않았는지 확인합니다.
비밀 토큰 만들기
비밀 토큰을 사용하여 새 웹후크를 만들거나 기존 웹후크에 비밀 토큰을 추가할 수 있습니다. 비밀 토큰을 만들 때는 엔트로피가 높은 임의의 텍스트 문자열을 선택해야 합니다.
- 비밀 토큰을 사용하여 새 웹후크를 만들려면 "웹후크 만들기"을(를) 참조하세요.
- 기존 웹후크에 비밀 토큰을 추가하려면 웹후크의 설정을 편집합니다. “비밀”로 이동해
secret
키로 사용할 문자열을 입력합니다. 자세한 내용은 "웹후크 편집하기"을(를) 참조하세요.
비밀 토큰 안전하게 저장하기
비밀 토큰을 만든 후에는 서버에서 액세스할 수 있는 안전한 위치에 저장해야 합니다. 토큰을 애플리케이션에 하드 코딩하거나 리포지토리에 푸시하지 마세요. 코드에서 인증 자격 증명을 안전하게 사용하는 방법에 대한 자세한 내용은 "해당 API 자격 증명 보안 유지"을(를) 참조하세요.
웹후크 제공 유효성 검사하기
GitHub에서 이 비밀 토큰을 사용하여 각 페이로드로 전송되는 해시 서명을 만듭니다. 해시 서명은 각 제공에서 X-Hub-Signature-256
헤더 값으로 표시됩니다. 자세한 내용은 "웹후크 이벤트 및 페이로드"을(를) 참조하세요.
웹후크 제공을 처리하는 코드에서 비밀 토큰을 사용하여 해시를 계산해야 합니다. 그런 다음 GitHub에서 보낸 해시를 계산된 예상 해시와 비교해 일치하는지 확인합니다. 다양한 프로그래밍 언어로 해시의 유효성을 검사하는 방법의 예시는 "예시"를 참조하세요.
웹후크 페이로드의 유효성을 검사할 때는 다음과 같은 사항에 유의해야 합니다.
- GitHub은(는) HMAC 16진수 다이제스트를 사용하여 해시를 계산합니다.
- 해시 서명은 언제나 텍스트
sha256=
(으)로 시작합니다. - 해시 서명은 웹후크의 비밀 토큰 및 페이로드 콘텐츠를 사용하여 생성됩니다.
- 언어 및 서버 구현에서 문자 인코딩을 지정하는 경우 페이로드를 UTF-8로 처리해야 합니다. 웹후크 페이로드는 유니코드 문자를 포함할 수 있습니다.
- 일반
==
연산자를 사용하지 마세요. 대신, 정규 동격 연산자에 대한 특정 타이밍 공격을 완화하는 데 도움이 되는 "일정한 시간" 문자열 비교를 수행하는secure_compare
또는crypto.timingSafeEqual
같은 메서드나 JIT 최적화 언어의 정규 루프를 사용하는 것이 좋습니다.
웹후크 페이로드 유효성 검사 테스트하기
다음 secret
및 payload
값을 사용하여 올바르게 구현되었는지 확인할 수 있습니다.
secret
: "It's a Secret to Everybody"payload
: "Hello, World!"
올바르게 구현되었다면 생성하는 서명이 다음 서명 값과 일치해야 합니다.
- 서명:
757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- X-Hub-Signature-256:
sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
예시
선택한 프로그래밍 언어를 사용하여 코드에서 HMAC 검증을 구현할 수 있습니다. 다음은 구현이 다양한 프로그래밍 언어로 표시되는 방식을 보여 주는 몇 가지 예입니다.
Ruby 예제
예를 들어 다음의 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
그런 다음 웹후크 페이로드를 받을 때 호출할 수 있습니다.
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
Python 예시
예를 들어 다음의 verify_signature
함수를 정의하고 웹후크 페이로드를 받을 때 호출할 수 있습니다.
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 예제
예를 들어 다음의 verifySignature
함수를 정의하고 웹후크 페이로드를 받을 때 모든 JavaScript 환경에서 호출할 수 있습니다.
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 예제
예를 들어 다음의 verify_signature
함수를 정의하고 웹후크 페이로드를 받을 때 호출할 수 있습니다.
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 };
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
};
문제 해결
페이로드가 GitHub에서 온 것으로 확신하지만 서명 검증이 실패하는 경우:
- 웹후크에 대한 비밀을 구성했는지 확인하세요. 웹후크에 대한 비밀을 구성하지 않은 경우
X-Hub-Signature-256
머리글이 표시되지 않습니다. 웹후크 비밀에 관한 자세한 내용은 "웹후크 편집하기"을(를) 참조하세요. - 올바른 머리글을 사용하고 있는지 확인하세요. GitHub에서는 HMAC-SHA256 알고리즘을 사용하는
X-Hub-Signature-256
머리글글을 사용하는 것이 좋습니다.X-Hub-Signature
머리글은 HMAC-SHA1 알고리즘을 사용하며 레거시 용도로만 포함됩니다. - 올바른 알고리즘을 사용하고 있는지 확인하세요.
X-Hub-Signature-256
머리글을 사용하는 경우, HMAC-SHA256 알고리즘을 사용해야 합니다. - 올바른 웹후크 비밀을 사용하고 있는지 확인하세요. 웹후크 비밀 값을 모르는 경우, 웹후크 비밀을 업데이트할 수 있습니다. 자세한 내용은 "웹후크 편집하기"을(를) 참조하세요.
- 검증 전에 페이로드 및 머리글이 수정되지 않았는지 확인하세요. 예를 들어 프록시 또는 부하 분산 장치를 사용하는 경우, 프록시 또는 부하 분산 장치가 페이로드 또는 머리글을 수정하지 않는지 확인합니다.
- 언어 및 서버 구현에서 문자 인코딩을 지정하는 경우 페이로드를 UTF-8로 처리해야 합니다. 웹후크 페이로드는 유니코드 문자를 포함할 수 있습니다.