Webhookで決済のような重要なイベントの情報ををやり取りする場合には、リクエストに対する認証、完全性が保証されていてほしいです。
認証が保証されていなければ、どんなリクエストを送ればいいかさえわかれば、誰でもなりすましてリクエストができてしまいます。
完全性が保証されていないと、途中でリクエストが改ざんされていてもわかりません。
実装者あるいは利用者として、どのような対策があるのか調べて考えてみました。
考えられる対策
- リクエスト自体に認証のための情報を含める
- ヘッダ
- Basic認証
- Bearerトークン
- etc. - パラメータ
- ボディ
- メッセージ認証コードもしくはデジタル署名
対策 | 認証 | 完全性 |
---|---|---|
1 | ○ | ✗ |
2 | ○ | ○ |
どのような場合に問題が起きるか
1の場合、リクエスト自体が見れてしまうと任意のメッセージを送れます。また完全性は保証されていません。
2の場合リクエスト自体には鍵が含まれていません。送信時に使う鍵とアルゴリズムがわかると任意のメッセージを送れます。
きちんとした証明書を使ったhttpsであれば、基本リクエスト自体が見られることは無いはずなので、1でも問題ないように思えます。
ただ、以下のようなことをしてしまい、更にその内容が見れるような状態にあると任意のリクエストが送れるようになってしまいます。
- httpで送ってしまう
- ログに残ってしまう
簡単に起こり得そうなので基本的には2が好ましいと思います。
しかし2は送信側にもロジックが必要です。自分がWebhook受け取り側で、送信者が2をしてくれない場合には受信側でできることは1になってしまいます。
実際のサービスでの例
触ったことのあるサービスではどのような実装になっているのかいくつか見てみました。
Stripeの場合
-
Checking Webhook Signatures | Stripe
-
Stripe-Signature
ヘッダにHMAC(SHA-256)のメッセージ認証コードがついてくる - 鍵はendpoint secretとしてwebhook設定から見れる
-
Stripe-Signature
はタイムスタンプも含むことでリプレイ攻撃の対策としている
-
GitHubの場合
-
Securing your webhooks | GitHub Developer Guide
-
X-Hub-Signature
ヘッダにHMAC(SHA-1)のメッセージ認証コードがついてくる - 鍵は自分で生成したものをwebhook設定から設定する
-
SendGridの場合
-
Event Webhook - ドキュメント | SendGrid
- メッセージ認証コードは無しで、Basic認証推奨
結局どうすれば良いのか
実装者として
- 基本メッセージ認証コードをつける
- 重要かどうかは受け取り側次第のところがある
- 将来の仕様変更で重要な情報を含むようになるかもしれない
利用者として
- 自分にとってそこまで重要ではない情報なら気にしなくていい
- Webhook提供者がメッセージ認証コードをつけてくれているならその検証をする
- 基本は提供されているライブラリ、ドキュメントにある実装を使う
- 自分で検証する場合には定数時間の文字列比較アルゴリズムを使う
- Timing Attack対策
- メッセージ認証コードがない場合、予測不可能性を持った乱数で認証トークンを生成してリクエストのURLに設定し検証する
- 上と同様、文字列比較は定数時間のアルゴリズムを使う
- URLにBasic認証の情報を含められるなら使う
- 自分が使っているサービスでは使えないものがあった
- だめならリクエストパラメータに認証トークンを入れる
- ログに残らないよう対策する
- 例えばRailsだったら
Rails.application.config.filter_parameters
に追加する
- 例えばRailsだったら
- ログに残らないよう対策する