hunt-business-logic について
hunt-business-logicは、バグバウンティの調査者向けに、取引、本人確認、アクセス制御のワークフローにある欠陥を調べるセキュリティテストスキルです。攻撃対象の手掛かり、テスト手順、バイパス例、影響検証を業務上の影響に沿って整理しています。
主な機能
- 01取引と本人確認フローの確認
- 02レート制限とヘッダー操作のテスト
- 03決済とWebhook検証の確認
- 04内部ページの探索
- 05影響検証チェックリスト
活用シーン
- 01チェックアウト価格の改ざんテスト
- 02APIで確認ゲートが強制されるかの確認
- 03コールバックとWebhookの保護評価
- 04公開された内部ページの発見
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-business-logicインストール後、Claude Code で/hunt-business-logicを入力して読み込まれたか確認します。
詳細
主要な標的
金銭取引、本人確認、アクセス制御が現実世界での影響と交わるプラットフォームでは、ビジネスロジックの脆弱性が最も高額な報奨金につながります。特に価値の高い標的は次のとおりです。
- Eコマースおよび決済プラットフォーム(Valve/Steam、Shopify)— 決済フローの操作、商品の無料入手、価格の改ざん
- マーケットプレイスおよびギグエコノミーアプリ(Airbnb、Uber)— 不正行為や危険なやり取りにつながる本人確認の回避
- 段階的なアクセス権を持つ SaaS(Mozilla Monitor)— 権限がないのに、確認を回避して監視機能を利用
- 利用者の多いコンシューマーアプリ(Snapchat、Yelp)— スパム、列挙、または大規模な不正利用を可能にするレート制限の回避
報奨金の対象となる資産の種類:チェックアウトフロー、サブスクリプションのエンドポイント、コールバック/確認システム、Webhook ハンドラー、インターネットに公開された従業員向け/社内ポータル、そして認可の判断にクライアントから提供されたデータを信頼するあらゆるエンドポイントです。
攻撃対象領域を示す兆候
注意すべき URL のパターン:
/checkout、/order、/subscribe、/payment、/verify、/confirm、/callback/internal、/employee、/summit、/staff、/admin— 誤って公開されている社内ページ/api/v*/payment、/api/v*/notify、/webhook— 決済プロバイダーのコールバックX-Forwarded-For、X-Real-IP、CF-Connecting-IPヘッダーを受け付けるエンドポイント
レスポンス/ヘッダーの兆候:
- カートや注文データに関連付けられた、検証されていないセッション状態を含む
Set-Cookie - レスポンスに含まれる決済プロバイダー名:
Smart2Pay、Stripe、PayPal、Braintree - サードパーティの決済ページを経由するリダイレクトチェーン(処理中のデータを狙える可能性)
- CAPTCHA やトークンがないサブスクリプション/確認エンドポイントからの
200 OK
JS のパターン:
- フロントエンドのバンドルにハードコードされた内部 URL(
/employee/、/staff/、/internal/) - サーバーに送信する前のクライアント側での価格計算
- フロントエンドでしか確認されない検証ロジック(
if (verified) { ... }) - CSRF 対策トークンやレート制限トークンのない
fetch('/api/subscribe', { method: 'POST', body: ... })
技術スタックの兆候:
- 下書き/未公開のチャネルページを持つ Shopify ストアフロント
- セッションやアカウントと紐付けず、IP ベースのレート制限を使用するアプリ
- HMAC 署名の検証を行わない決済 Webhook
- 機能を有効にする前に所有権を確認しない、SMS/電話のコールバックフロー
手順に沿ったハンティング方法
-
すべての認証境界をマッピングします。 対象をクロールします。認証済みコンテンツを提供するページ/エンドポイント(従業員ポータル、プレミアム機能、注文ページ)を特定し、それぞれを未認証の状態でテストします。JS バンドル内にインデックスされている、または robots.txt/sitemap.xml からリンクされている内部ページを探します。
-
すべての検証フローを特定します。 メール認証、電話/SMS 認証、決済認証、CAPTCHA、年齢確認を列挙します。それぞれについて、検証手順を完全に省略するとどうなるか、有効なトークンを別のアカウントで再利用するとどうなるかをテストします。
-
すべてのフォームでレート制限の制御をテストします。 すべての POST エンドポイント(登録、ログイン、OTP、検索)に対し、短時間に 50 件以上のリクエストを送信します。Cookie を削除する、
X-Forwarded-For/X-Real-IPヘッダーをローテーションする、User-Agentを変更するといった条件を試します。サーバーが接続元 IP ではなく、ヘッダー内の IP を使用しているか確認します。 -
決済フローを傍受し、改ざんを試みます。 Burp Suite を使用して、ブラウザー、アプリケーション、決済プロバイダー間のすべてのリクエストを傍受します。価格、通貨、注文 ID、ステータスの各フィールドがどこで設定されているかを特定します。金額を $0.01 に変更する、または通貨を低価値通貨に変更することを試みます。決済確認を受け付ける POST バック/Webhook エンドポイントを探し、HMAC/署名を検証しているかテストします。
-
電話番号/折り返し番号の検証をテストします。 プラットフォームが折り返し番号を受け付ける場合は、自分が所有していない番号を設定できるか、その番号にプラットフォームから電話/SMS が送られ、入力したことだけを根拠に信頼が付与されるかをテストします。被害者の番号を設定することも試します。
-
保護されていない従業員向け/内部向けの領域を確認します。 Shodan、GitHub、JS バンドル、Wayback Machine を検索して、内部サブドメイン/パスへの参照を探します。認証なしでアクセスできるかテストします。これらの領域から注文、データへのアクセス、権限昇格が可能か確認します。
-
ビジネスへの影響を検証します。 各発見事項について、金銭的損失、不正アクセス、データ漏えいにつながるかを判断します。エンドツーエンドの攻撃手順を記録します。
ペイロードと検出パターン
ヘッダーのローテーションによるレート制限の回避:
# Rotate X-Forwarded-For to bypass IP rate limiting
for i in $(seq 1 100); do
curl --max-time 30 --connect-timeout 10 -s -X POST https://target.com/api/subscribe \
-H "X-Forwarded-For: 10.0.0.$i" \
-H "X-Real-IP: 10.0.0.$i" \
-H "Content-Type: application/json" \
-d '{"email":"victim+'"$i"'@example.com"}' \
-o /dev/null -w "%{http_code}\n"
done
決済の改ざん — 通信中の価格を変更:
POST /payment/initiate HTTP/1.1
Host: target.com
amount=0.01¤cy=USD&order_id=12345&product_id=99
# Look for unvalidated webhook endpoints
curl --max-time 30 --connect-timeout 10 -X POST https://target.com/payment/callback \
-H "Content-Type: application/json" \
-d '{"status":"success","amount":"0.01","order_id":"12345","transaction_id":"fake-txn"}'
認証なしでの内部ページの検出:
# Check robots.txt and sitemap for internal paths
curl --max-time 30 --connect-timeout 10 -s https://target.com/robots.txt | grep -iE "(disallow|allow)"
curl --max-time 30 --connect-timeout 10 -s https://target.com/sitemap.xml | grep -iE "(employee|internal|staff|summit|admin)"
# Grep JS bundles for internal paths
curl --max-time 30 --connect-timeout 10 -s https://target.com/assets/app.js | grep -oE '"/[a-zA-Z0-9/_-]{3,50}"' | sort -u
メール認証の回避:
# Skip the verification step: hit the post-verification API endpoint directly
# with an unverified session. If it succeeds, the gate is UI-only.
curl --max-time 30 --connect-timeout 10 -s -X POST https://monitor.target.com/api/monitoring/enable \
-H "Cookie: session=<your_unverified_session>" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
# Replay verification token on different account
curl --max-time 30 --connect-timeout 10 -X POST https://target.com/verify \
-d 'token=VALID_TOKEN_FROM_ACCOUNT_A&[email protected]'
クライアント側ロジックの問題を探す Grep パターン:
# Find price calculations in JS
grep -iE "(price|amount|total|cost)\s*[=*+]" app.js
# Find internal URLs in JS bundles
grep -oE '"/(employee|internal|staff|admin|summit)[^"]*"' *.js
# Find unvalidated IP header usage in server code
grep -iE "x-forwarded-for|x-real-ip|cf-connecting-ip" src/ -r
よくある根本原因
-
サーバーが金銭に関する判断で、クライアントから渡されたデータを信頼している。 開発者が価格計算をフロントエンドに委ねたり、フォーム/URL から渡された金額フィールドを、正規の情報源(商品データベース)と照合してサーバー上で再検証せずに処理したりしています。
-
検証が UI でのみ適用され、API では適用されていない。 フロントエンドは検証を通過するまで機能を非表示にしますが、バックエンド API エンドポイントは認証済みステータスでなくても完全に機能し、認証済みのリクエストであれば何でも成功します。
-
IP ベースのレート制限が、偽装可能なヘッダーを参照している。 開発者が実際の接続元 IP ではなく、
request.headers['X-Forwarded-For']を使用してレート制限を実装しているため、ヘッダーを変更するだけで簡単に回避できます。 -
決済 Webhook に署名検証がない。 開発者が決済プロバイダーから提供された HMAC 署名を検証せずに「成功」Webhook を実装しているため、誰でも偽の成功通知を POST できます。
-
内部/従業員向けページにアクセス制御がない。 開発者が URL を非公開にすれば十分だと思い込んだ、または新しいルートへの認証適用を忘れたため、内部ツールが認証ミドルウェアなしで本番ドメインにデプロイされています。
-
電話番号/折り返し番号の検証が助言的なものにとどまり、強制されていない。 システムは電話番号を受け取ると、その番号の所有者または管理者であることを確認せず、番号を送信したユーザーを信頼します。
-
下書き/チャネル固有のストアフロントが、完全な注文機能を引き継いでいる。 Shopify のようなプラットフォームでは、特定のチャネル(従業員向けイベント)のストアフロントを作成できます。URL が知られていれば、非公開でも注文機能を完全に利用できる状態になっています。
バイパス手法
| 防御策 | バイパス方法 |
|---|---|
| IP ベースのレート制限 | リクエストごとに X-Forwarded-For、X-Real-IP、True-Client-IP、CF-Connecting-IP ヘッダーをローテーションする |
| サブスクリプションフォームの CAPTCHA | まずヘッダーベースのバイパスを試します。CAPTCHA がウェブフォームにしかない場合は、基盤となる API エンドポイントを直接呼び出します |
| メール認証ゲート | 認証後の API エンドポイントに直接アクセスします。有効なトークンを再送します。verified=true がクライアント側で設定できる Cookie/パラメーターかどうかを確認します |
| 支払い金額のサーバー側検証 | 通貨を価値の低い通貨に変更します。$0.00 または負の金額を試します。注文 ID を操作して別の商品を参照させます |
| Webhook の HMAC 検証 | 署名ヘッダーなし/空で試します(検証がそもそも強制されているか確認します。改変したペイロードが「通る」のは、検証されていない場合に限ります)。変更していないキャプチャ済みの Webhook を再送し、冪等性/リプレイ防止が欠落していないかテストします |
| 内部ページの認証 | 未認証で試します。権限の低いアカウントで試します。パストラバーサルのバリエーションを試します(/employee/../employee/) |
| 電話番号認証(OTP 送信) | OTP 検証なしで他人の電話番号を送信します。システムが OTP 確認時ではなく、番号の送信時点で信頼を付与するか確認します |
ゲート 0 の検証
レポートを書く前に、以下の 3 つすべてに回答してください。
-
攻撃者は現時点で何を実行できますか? 具体的に説明してください。「未認証ユーザーが、物理商品を $0 で注文できます」や「攻撃者がメール認証をバイパスし、所有者でなくても任意のメールアドレスを監視できます」、または「攻撃者が任意のアドレスにサブスクリプションメールを無制限に送信できます」のように記述します。影響が曖昧な場合は却下してください。
-
被害者は何を失いますか? 具体的で、原因を特定できる損失を挙げてください。金銭的損失(無料の商品、不正な支払い)、プライバシーの損失(電話番号のなりすまし、無断の監視)、サービスの悪用(レート制限のバイパスによるスパムキャンペーン)、またはセキュリティの低下(未認証の ID が機密性の高い操作で信頼される)などです。損失が純粋に理論上のものにすぎない場合は、深刻度を再評価してください。
-
ゼロから 10 分以内に再現できますか? 新しいアカウントを作成する(またはアカウントを使わない)手順を実行します。記載した手順に従い、影響を再現してください。記載した手順で 10 分以内にエンドツーエンドの再現を確実に行えない場合、方法論が不完全です。提出前に改善してください。
実際の影響例
シナリオ 1 — 公開された社内ストアフロント経由での無料商品の取得(Shopify 形式) 従業員向けサミットのページが、スタッフに無料の書籍を配布するための非公開チャネルとして、一般公開の Shopify ストアフロントにデプロイされていました。URL は、JS バンドルの解析やリンクの共有によって発見できる状態でした。URL にアクセスした匿名ユーザーは、認証なしで商品を閲覧してチェックアウトを完了でき、会社負担で発送される物理的な商品を受け取ることができました。影響:注文ごとの直接的な金銭的損失が発生し、迅速に検知できなければ大量注文につながる可能性があります。
シナリオ 2 — 送信中の改ざんによる決済操作(Valve/Steam 形式) 決済フローでは、注文金額と通貨がクライアント側で制御可能なパラメーターを通じて渡され、その後、サードパーティの決済プロバイダー(Smart2Pay)へリダイレクトされていました。Burp Suite でリダイレクトを傍受して金額フィールドを変更すると、攻撃者は 0.01 ドルで実際の決済を完了できました。一方、HMAC 検証を行っていないアプリケーションの webhook はプロバイダーからの確認を受け入れ、アカウントに商品やサービスの全額分の価値を付与していました。影響:重大な金銭的損失が発生します。攻撃者はほぼ無コストで商品やサービスの全価値を受け取ることができ、無限に繰り返し実行できます。
シナリオ 3 — 不正な監視を可能にするメール認証の回避(Mozilla 形式) 侵害監視サービスでは、あるアドレスの監視アラートを有効にする前に、メール認証が必要でした。認証チェックは UI フローでは強制されていましたが、基盤となる API は有効なセッションがあれば任意のアドレスに対する監視設定リクエストを受け入れ、認証ステップを完全に回避できる状態でした。攻撃者は、自分が所有していないメールアドレスの監視を設定し、被害者のアカウントに関する侵害通知データ(認証情報の漏えい状況を含む可能性があります)を受け取ることができました。影響:プライバシーが侵害され、攻撃者は対象者に知られることも同意を得ることもなく、そのメールアドレスが侵害に含まれていたかどうかを把握できます。
公開済みレポートの引用(追加分 +5 — 2016-2023)
以下の実際に検証済みのバグバウンティ/協調的な脆弱性開示事例を、このスキルに追加します。5件すべてに、測定可能な金銭的影響(実際の損失が実証されている、またはプラットフォーム全体への影響を定量化できる)が共通しています。
-
Stripe — 手数料割引の競合による複数回引き換え (H1 #1849626)
- サブクラス:クーポン/割引の競合による複数回引き換え+金銭的プリミティブ
- ペイロード:Stripe Support が一度限りの 20,000 ドルの手数料クレジットを適用しました。研究者は「accept-discount」POST をキャプチャし、Burp Turbo Intruder で並列に 30 回再送信しました。受け付けられるたびに、新たにアカウントへクレジットが付与されます
- 根本原因:割引受け付けエンドポイントに冪等性がなく、(account_id, discount_id) に一意制約がありませんでした
- 年:2023 — $5,000、修正前に手数料無料の取引が 600,000 ドル分発生(テイクレート 3%で Stripe の実損は約 18,000 ドル)
-
marketplace.example.com — ギフトカードの競合による複数回引き換え (H1 #759247)
- サブクラス:ギフトカード/ストアクレジットの競合による複数回引き換え
- ペイロード:有効なギフトカード 1 枚を使い、Turbo Intruder 経由で 10 個のソケットから
/redeemに並列 POST します。残高には額面の N 倍が加算されます - 根本原因:gift_card テーブルに行レベルのロックがなく、残高の引き落としと加算が別々のトランザクションで行われていました
- 年:2019 — $1,500
-
Upserve / OLO — 負の数量による価格操作 (H1 #364843)
- サブクラス:カート内の負の数量による価格改ざん
- ペイロード:
POST /api/order {"items":[{"id":1,"qty":1,"price":50},{"id":2,"qty":-3,"price":50}]}— 注文合計は-$100と計算され、決済時には約 0 ドルに切り下げられますが、料理は提供されます - 根本原因:サーバーは
qty >= 1のチェックなしでqty * priceを計算します - 年:2018 — このサブクラスの典型的な引用事例(プログラムによる受け付けのみ)
-
Krisp — PUT の改ざんによる座席あたりの支払い額削減 (H1 #1446090)
- サブクラス:単価へのマスアサインメント/数量割引の操作
- ペイロード:
PUT /v2/seatsのボディに、サーバーが信頼するpriceフィールドが含まれています。60 ドルの代わりにprice=1を設定します。サブスクリプションが更新され、請求エンジンは 100 席分を 1 ドル/席で請求します - 根本原因:サーバーは
plan_idから価格を検索せず、リクエストボディの価格を読み取ります。典型的なマスアサインメントです - 年:2021 — Stripe で請求される SaaS の価格設定に関する露出
-
Stripe — 処理途中の差し替えによるアーカイブ済み価格での支払い (H1 #1328278)
- サブクラス:カート状態の TOCTOU/キャンセル後の提供(チェックアウト時の価格バージョン競合)
- ペイロード:販売者が古い価格(例:新価格が 50 ドルのところ 5 ドル)をアーカイブします。購入者は新しい決済リンクからチェックアウトを開始し、その途中で
price_idをアーカイブ済みのものに差し替えます。Stripe は 5 ドルを請求し、サブスクリプションは新しいティアでプロビジョニングされます - 根本原因:決済リンクは「有効であること」を検証し、価格オブジェクトは「存在すること」を検証しますが、「price.active AND price ∈ link.allowed_prices」という結合条件がありません
- 年:2021 — Stripe Medium(サブスクリプションごとに継続的な損失)
検証
ビジネスロジックへの対応準備ができていることを確認するには、このセルフテストを実行してください。
-
API 直接呼び出しテスト — curl でフロントエンドの検証を回避できることを確認します:
curl --max-time 30 --connect-timeout 10 -s -X POST "https://httpbin.org/post" -H "Content-Type: application/json" -d '{"quantity":-1,"price":0}' | python3 -m json.tool 2>/dev/null | grep -q "quantity" && echo "PASS" || echo "(httpbin may be down)" -
同時リクエストテスト — curl を並列実行できることを確認します:
curl --help 2>/dev/null | grep -q "parallel" && echo "PASS: curl parallel support" || echo "NOTE: older curl version" -
整数の境界値テスト — 境界値への対応を確認します:
echo "-1 0 2147483648 999999999" | grep -q "2147483648" && echo "PASS: overflow edge case present" || echo "FAIL"
これら 3 つのテストで、ビジネスロジックを調査する準備ができていることを確認できます。
落とし穴
- ビジネスロジックのバグは影響が小さいと思い込む — 価格の改ざん、クーポンの不正利用、返金詐欺は、最も高額な報奨金が支払われるバグの一つです。「単なるロジックの問題」として軽視しないでください。
- 正常系だけをテストする — バグはエッジケースに潜んでいます。負の数量、ゼロ価格、引き換え時の競合状態、期限切れのクーポン、同時リクエストをテストしてください。
- 単一ステップのテスト — ビジネスロジックのバグでは、多くの場合、複数ステップの手順が必要です(カートに追加+クーポンを適用+数量を変更+チェックアウト)。一連のフロー全体をテストしてください。
- エラーハンドリングを無視する — エッジケースでの500エラーは、多くの場合、一貫性のないデータ(注文の一部だけが作成される、二重請求、無料の商品)につながる未処理の状態を示しています。
- フロントエンドとAPIを比較しない — フロントエンドでは制限を適用していても、APIでは適用していない場合があります。直接APIを呼び出して、すべての制約をテストしてください。
- 通貨と丸めの問題 — 小数通貨の処理(例:0.001 USD)によって丸め誤差が生じ、一括操作を通じて累積することがあります。
関連スキルと連鎖
hunt-race-condition— ロジックフロー内の一意性や割当量のチェックは、すべて競合の候補です。連鎖の原型:ビジネスロジック(クーポン/クレジット/プロモーション)+競合状態 → Turbo Intruderを使い、1つのTCPパケットでクーポンをN回引き換える。hunt-idor— クライアントから送信された識別子(order_id、tenant_id、beneficiary_id)を信頼するロジックフローは、IDORと直接重なります。連鎖の原型:ビジネスロジックの手順スキップ+beneficiary_idのIDOR → 被害者のアカウントから資金を移動する。hunt-api-misconfig— 手順スキップや検証のバイパスは、多くの場合、マスアサインメント可能なフィールドの近くにあります。連鎖の原型:ビジネスロジック上のメール確認スキップ+APIのマスアサインメント(verified:true, role:admin)→ メールアドレスを制御せずにATOを実現する。hunt-ato— パスワードリセット、メールアドレス変更、アカウント復旧フローのロジックバグは、ATOの主要な経路です。連鎖の原型:ビジネスロジック(再認証なしでメールアドレスの変更を受け付ける)+hunt-atoPath 2 → 被害者に気づかれずにメールアドレスを攻撃者のものに変更 → 攻撃者のメールボックスにパスワードリセットを送信する。security-arsenal— Business-Logic Probe Checklist(負の数量、小数のオーバーフロー、通貨の切り替え、直接URLへの移動による手順スキップ、ステートマシンの逆行)とAlways-Rejectedリストを読み込み、自分で引き起こしたバグを報告しないようにしてください。triage-validation— 7-Question Gate(特にQ4「非現実的な前提条件なしに、外部の攻撃者が悪用できますか?」)を適用してください。ロジックバグには、具体的な金銭、PII、または状態への影響が必要です。「フローが不自然に見える」だけでは不十分です。
価格の改ざん — クライアント側を信頼するパターン
SaaSで最もよく見られるビジネスロジックのパターンです。サーバーがクライアントのリクエストボディにあるpriceフィールドを信頼します。クライアントが"price":0を送信すると、サーバーはデータベースから商品の価格を取得する代わりに、R$0でチェックアウトセッションを作成します。
検出
# 1. Create a purchase with normal flow, capture the request
# 2. Replay with modified price
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://api.target.com/checkout" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"items":[{"id":"premium_product","quantity":1,"price":0}]}'
# 3. Test negative prices
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://api.target.com/api/payments/create" \
-H "Authorization: Bearer $TOKEN" \
-d '{"items":[{"id":"plan","quantity":1,"price":-100}]}'
# 4. Test arbitrary quantities
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://api.target.com/api/checkout" \
-d '{"items":[{"id":"key","quantity":999,"price":0}]}'
指標
- チェックアウト/決済エンドポイントが、サードパーティの決済URL(Stripe、Asaas、MercadoPago)を返す
- 決済URLに
cs_test_のプレフィックスがある(Stripeのテストモード — 決済は実際には処理されない) - 価格の値にかかわらず、サーバーが購入IDとチェックアウトURLを返す
- 商品価格がJSバンドル内に見えるが、サーバー側では照合されていない
検証
- 確認済み:チェックアウトURLがR$0で作成され、決済ページにR$0.00と表示される
- 重大な問題として確認済み:R$0で購入が完了し、ライセンス/キーが付与される
- 誤検知:サーバーが「価格が一致しません」として拒否するか、サーバー側で価格を取得している
関連項目
hunt-write-gap— サーバーのPATCH/POSTエンドポイントに検証がないため、価格の改ざんが成功する場合hunt-supabase— anon key経由で、PostgRESTが価格フィールドに任意の値を受け入れる場合