2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Webhookを安全に使うための完全ガイド

Last updated at Posted at 2025-12-02

Webhook を安全に使うための完全ガイド

─ 署名検証・リプレイ攻撃対策をわかりやすく解説

Webhook は「外部サービスから HTTP リクエストを受け取る仕組み」です。
便利ですが、設計を間違えると誰にでも叩かれてしまうため、セキュリティを意識しない運用はとても危険です。

この記事では

  • Webhook が危険な理由
  • 安全に使うための原則
  • HMAC 署名の実装例(Python)
  • タイムスタンプ検証 / nonce / IP 制限
  • 実運用での注意点
    を、初心者でも理解できる形でまとめました。

これはgakuseibotのアドベントカレンダー三日目の記事です。是非お読みください!!

目次

  • Webhook はなぜ危険になりやすい?

  • 安全に使うための必須ポイント

  • HMAC 署名検証の実装(Python)

  • リプレイ攻撃を防ぐ方法

  • IP 制限は「補助」でしかない理由

  • Webhook 設計でやってはいけないこと

  • まとめ

1. Webhook はなぜ危険になりやすい?

Webhook は 「インターネットから受け取る HTTP POST」 というだけの仕組みです。

つまり…

URL が漏れたら誰でも叩ける

Webhook の URL は パスワードではありません。
ランダム文字列にしても、

URL がログに残った

誤って公開した

誰かが推測した
などで漏れる可能性があります。

正規の送信者かどうか判別できない

HTTP リクエストを送るのは誰でも可能なので
「本当にサービスから送られたリクエストなのか?」
を検証しなければいけません。

過去のリクエストを再送する「リプレイ攻撃」

正しい署名をもつリクエストでも、
同じデータを悪意ある第三者が再送する
=「リプレイ攻撃」が起きる可能性があります。

2. Webhook を安全に使うための必須ポイント

Webhook を安全にするために必要なのは以下の3つです。

① HMAC 署名で送信元を検証する

最も重要な方法。
秘密鍵(secret)を使って送信者が署名し、受信側で同じ計算をして一致を確認する。

→ 正しい送信者しか正しい署名を作れない

② タイムスタンプや nonce で「使い回し」を防ぐ

・一定時間以内(例:±5分)だけ受け付ける
・nonce を保存して一度使われたものは拒否する

→ リプレイ攻撃を防ぐ

③ HTTPS を必ず使う

HTTP だと盗聴されるため、署名ごと漏れます。

  1. HMAC 署名検証の実装例(Python)

ここでは、サービス側が以下を送ってくる想定とします

ヘッダー:X-Signature(HMAC-SHA256)

ヘッダー:X-Timestamp

ボディ:JSON

実装は完全に一般的な HMAC の仕組みで、特定サービス固有の仕様は含みません。

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)

SECRET = b"your_webhook_secret"  # 自分だけが知っている秘密鍵


def verify_signature(timestamp: str, body: bytes, signature: str) -> bool:
    """
    署名が正しいか検証する関数
    """
    message = timestamp.encode() + body

    expected_signature = hmac.new(
        SECRET,
        message,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected_signature, signature)


@app.route("/webhook", methods=["POST"])
def webhook():
    timestamp = request.headers.get("X-Timestamp")
    signature = request.headers.get("X-Signature")
    body = request.get_data()

    # 必須ヘッダーがなければ拒否
    if not timestamp or not signature:
        abort(400)

    # 署名検証
    if not verify_signature(timestamp, body, signature):
        abort(401)  # Unauthorized

    data = request.json
    print("Valid webhook received:", data)

    return "ok", 200


if __name__ == "__main__":
    app.run(port=3000)

ポイント

hmac.compare_digest() を使うことでタイミング攻撃対策になる

署名は secret を知っている送信者だけが作れる

timestamp + body を使うことで「タイムスタンプ検証」が可能

3. リプレイ攻撃を防ぐ方法

HMAC だけでは「使い回しの再送」を防げません。

必ず以下どちらかを使います。

方法①:タイムスタンプの時間差チェック

例:
「送信時刻と現在時刻の差が5分以内なら許可」

理由:
攻撃者が署名付きリクエストを盗んでも、
5分後には無効になります。

例(Python)

import time

ALLOWED_DELAY = 300  # 5分

if abs(time.time() - int(timestamp)) > ALLOWED_DELAY:
    abort(408)  # Request Timeout

方法②:nonce(使い捨てトークン)を保存して再利用禁止

サーバー側が
「この nonce は今日初めて見たか?」
をチェックし、既に使われていれば拒否します。

例:Redis などを使うと簡単。

4. IP 制限は「補助」でしかない理由

IP 制限は有効な対策ですが、
単独では十分な対策ではありません。

大規模サービスは IP 範囲が変動する

CDN を使っている場合はエッジの IP になる

IP 偽装は難しいが、「正規サービスになりすました他サービス」から来る可能性はゼロではない

したがって、

HMAC(必須) + IP 制限(補助)
という形が最も安全です。

5. Webhook 設計で絶対やってはいけないこと

「URL を長くしておけば安全」は間違い

URL は秘密鍵ではありません。

HTTP を使う

通信内容が盗まれます。

署名を文字列比較(==)で判定する

タイミング攻撃のリスクがあります。
→ hmac.compare_digest() を使用する。

タイムスタンプを検証しない

リプレイ攻撃を防げなくなります。

8. まとめ

Webhook を安全に設計するには以下が必須です。

✔ HMAC 署名で送信元を検証
✔ タイムスタンプ・nonce でリプレイ防止
✔ HTTPS の利用
✔ IP 制限は“補助”として使う
✔ compare_digest を使う
✔ URL に頼ったセキュリティは危険

これらは「事実ベースの一般的なセキュリティ原則」で、
特定サービスに依存しない普遍的な内容です。

お読みいただきありがとうございました!!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?