Skip to content

Verify webhook signatures in constant time to prevent timing attacks#36

Open
masaru87 wants to merge 1 commit into
smartpay-co:mainfrom
masaru87:fix-webhook-signature-timing-safe
Open

Verify webhook signatures in constant time to prevent timing attacks#36
masaru87 wants to merge 1 commit into
smartpay-co:mainfrom
masaru87:fix-webhook-signature-timing-safe

Conversation

@masaru87

Copy link
Copy Markdown

突然の PR 失礼します。webhook 署名検証まわりでセキュリティ上の問題を見つけたので修正します。

問題

Smartpay.verifyWebhookSignaturesrc/Smartpay/webhooks.ts)は、受信した署名と計算した HMAC を通常の文字列比較で照合しています。

return signature === calculatedSignature;

=== は最初に異なる文字が現れた時点で早期リターンするため、比較にかかる時間が「先頭から何文字一致したか」に依存します。webhook エンドポイントは攻撃者が任意の署名で繰り返し叩けるので、応答時間の差から正しい署名を 1 文字ずつ推測できます(典型的なタイミング攻撃)。決済 webhook の署名は、これが破られると偽イベントを本物として受理してしまうため、実害があります。

Stripe / GitHub をはじめ、各社の webhook SDK が署名照合に定数時間比較を使っているのはこのためです。

修正

crypto.timingSafeEqual で定数時間比較に変更しました。timingSafeEqual は等長バッファを要求するので、長さが違う場合だけ先に false を返しています(長さの相違は秘密を漏らしません)。正しい署名の受理挙動は変わりません。

テスト

  • 既存の「正しい署名を受理する」テストはそのまま通ります。
  • 署名検証の reject 経路にテストが無かったので、Reject invalid webhook signature を追加しました(同じ長さの誤署名・短い署名・長い署名の 3 ケース。長さ不一致で timingSafeEqual が throw しないことも兼ねて確認)。

Node 18 でビルド・test:unit(12 件)・eslint がすべて通ることを確認済みです。

verifyWebhookSignature compared the received signature against the
expected HMAC with `===`, which short-circuits on the first differing
character. That leaks timing information an attacker can use to recover
a valid signature byte by byte. Compare with crypto.timingSafeEqual
instead, rejecting length mismatches up front (timingSafeEqual requires
equal-length buffers). Adds tests for the reject path, which previously
had no coverage.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant