Webhook Secret 문제

혹시 제가 어디를 잘못하고 있는지 알려주실 분이 계실 것 같습니다.

Discourse 웹훅을 설정했는데, 비밀키(Secret) 없이는 정상적으로 작동합니다. 그런 다음 Secret에 문자열을 입력하고, 해당 문자열을 수신 웹훅의 헤더에 복사해서 다음과 같이 설정했습니다:

X-Discourse-Event-Signature: secret_here

이렇게 하면 송신 웹훅이 전송될 때 Body에서 다음 오류가 발생합니다:

{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}

아마 제가 방법을 잘못 알고 있는 것 같아서, 알려주시면 감사하겠습니다!

비밀키는 sha256으로 암호화되어 있다는 점을 알고 있으며, 따라서 전송되는 헤더는 다음과 같습니다:

X-Discourse-Event-Signature: sha256=XXX

하지만 수신 웹훅의 보안 헤더(Security Headers)에서 이 정보를 어떻게 처리해야 하는지 확실하지 않습니다.

이것이 단순히 게시글 작성 방식인지 확실하지는 않지만, 실제 헤더는 다음과 같아야 합니다.

X-Discourse-Event-Signature: XXX

(선택적으로) 수신 측에서 이 값을 사용하여 웹훅이 실제로 Discourse에서 전송되었는지 검증할 수 있습니다. 검증을 수행하지 않으면 악의적인 제3자가 웹훅 이벤트를 위조할 가능성이 있습니다.

이것은 무슨 뜻인가요?

즉, 수신 웹훅의 Signature는 선택한 평문 시크릿이 아니라 암호화된 시크릿이어야 한다는 말씀이신가요?

수신 애플리케이션에서는 헤더를 다음과 같이 작성하고 있습니다:
X-Discourse-Event-Signature: XXX

하지만 시크릿을 사용할 때 401 forbidden 오류가 계속 발생합니다.

그 스크린샷은 어디에서 가져온 건가요?
설치 환경에 대해 좀 더 자세히 설명해 주실 수 있을까요?

저의 WordPress 웹사이트입니다. 웹훅을 수신하기 위해 WordPress 플러그인을 사용하고 있습니다. 보안 헤더(시크릿)를 추가하지 않으면 웹훅이 정상적으로 작동합니다. 시크릿을 추가했을 때만 문제가 발생합니다.

사용 중인 플러그인은 다음과 같습니다: https://automatorplugin.com/knowledge-base/webhook-triggers/

시간 내어 봐주셔서 감사합니다, Richard!

그 후 문제를 좁혀 보았습니다:

커스텀 보안 헤더는 수신 웹훅 트리거에 포함될 수 있지만, WordPress가 대시(-)를 언더스코어(_)로 변경할 수 있다는 점이 중요합니다. 예를 들어, "x-api-key"를 사용하려면 대신 "x_api_key"를 사용해야 할 수 있습니다.

다만 @RGJ 님께 관련 질문이 하나 있습니다. 제 시크릿이 "1234"라고 가정해 보겠습니다. 수신 웹훅에서 X-Discourse-Event-Signature 값은 다음과 같아야 하나요?

  1. 1234;
  2. sha256=encrypted_value_here;
  3. encrypted_value_here

?

2번인 sha256=XXX입니다.

사용 중인 WordPress 플러그인은 동적 서명이 아닌 정적 헤더 값만 비교할 수 있는 것 같습니다. 이는 Discourse에만 해당하는 특성입니다.

WP Discourse 플러그인이 시크릿을 처리하는 방식을 확인해 보세요:

이후 웹훅이 전송될 때마다 sha 값이 변경되는 것을 알게 되었습니다. 따라서 수신 웹훅에 sha 값을 넣는 방식은 작동하지 않을 것입니다. 제 생각에는 예제 #1이 되어야 할 것 같고, 여러분이 제안하신 대로(정적 헤더)라면, 플러그인이 헤더 응답을 생성하기 전에 해시를 먼저 디코딩해야 하는 것 같습니다.

결론적으로, 이 플러그인에서는 해시된 X-Discourse-Event-Signature를 사용할 수 없다고 생각합니다. 왜냐하면 이 플러그인은 정적 값만 허용하기 때문입니다.

Secret 없이 웹훅을 사용하는 것이 안전하지 않나요? 저는 main-site.com/wp-json/fol/fil/532563-5312534와 같이 눈에 잘 띄지 않는 URL을 사용하여 제 Discourse에서 메인 웹사이트로 사용자 이벤트를 전송할 예정입니다. 또한 수신 웹훅에서 일치해야 하는 정적 헤더를 추가할 수도 있습니다.

동적으로 변경되는 hmac을 처리할 수 있는 애플리케이션의 예를 보여줄 수 있을까요?

해당 값은 웹훅 페이로드에 대해 계산되므로, 모든 요청마다 달라집니다.

흥미로운 질문입니다. 제가 아는 유일한 애플리케이션은 WP Discourse 플러그인데, 이는 Discourse 웹훅을 처리하도록 특별히 구성되었습니다. 만약 Discourse의 구현 방식이 시크릿을 사용하여 웹훅을 검증하는 것을 막고 있다면, 이에 대해 조사해 볼 필요가 있을지도 모릅니다.

이 부분은 다른 분들의 답변을 기다리겠습니다.

웹훅 검증을 하지 않는 것이 우려된다면, Incoming Webhook Triggers 플러그인의 웹훅 수신 코드에 웹훅 데이터가 처리되기 전에 실행되는 액션 후크가 있을 수 있습니다. 만약 그렇다면, 해당 후크에 연결되는 함수를 작성하여 데이터가 Discourse 웹훅용인지 확인한 후, 이전 게시물에서 링크한 코드와 유사한 방식으로 시크릿을 검증하는 것이 가능할 것입니다.

호기심이 생겨 이에 대해 조금 조사해 보았습니다. 동적으로 변경되는 HMAC 서명을 _수신_하도록 구성된 애플리케이션에 대해서는 확신이 서지 않지만, 페이로드를 기준으로 계산된 HMAC 서명을 사용하여 웹훅 요청을 인증하는 것은 _웹훅을 전송_하는 애플리케이션에서 상당히 표준적인 관행입니다. 예를 들어, GitHub, Stripe, Shopify 모두 이 방식을 사용합니다.

웹훅 인증에 시크릿 토큰 방식을 사용하는 인기 있는 애플리케이션도 꽤 많습니다. 예를 들어 (2021년 기준) Mailchimp, Trello, Slack, Discord 모두 이 접근 방식을 사용합니다. Slack이 여전히 이 방식을 사용하는 것 같습니다: Slack developer FAQ | Slack Developer Docs. 웹훅을 수신하는 애플리케이션의 편의성을 높이는 데 도움이 될 수 있다고 생각합니다.

보안과 편의성 사이에서 전송하는 애플리케이션이 사용할 방식을 결정하는 데에는 트레이드오프가 있다고 생각합니다. Discourse의 HMAC 서명 방식에 대한 제 우려는, 사람들이 변경되는 HMAC 서명을 처리하는 어려움을 회피하기 위해 시크릿 키 없이 웹훅을 구성하게 될 수 있다는 점입니다. 예를 들어, Meta에는 Zapier로 Discourse 웹훅을 보내는 것에 대한 몇 가지 참조가 있지만, Zapier에서 서명을 검증하는 방법에 대한 언급은 없습니다. (Zapier에서 서명을 검증하는 것은 기술적으로 가능해야 합니다. 다만 현재 제가 테스트할 수 있는 환경은 아닙니다.)

저는 이 의견에 전적으로 동의합니다. 개발자들이 웹훅 데이터를 보호하는 더 쉽고 호환성이 높은 방법을 탐색해 볼 수 있기를 바랍니다.

또한 Zapier가 실제로 서명을 검증할 수 있는지도 확신할 수 없습니다.

웹훅 시크릿 기능은 다음에 따라 구현되었습니다:

원하시는 소프트웨어가 이 표준 보안 통신 기능을 지원하지 않는 것으로 보이지만, 해당 기능 없이도 Discourse 웹훅을 사용할 수 있습니다.

공유 시크릿을 포함한 고정 헤더를 추가할 계획은 없으며, 그 보안 가치는 의문스럽기 때문입니다. 다만, 이 기능이 필요하시다면 플러그인을 통해 구현할 수 있을 것입니다.