mcpField
H
hunt-fintech-graphql

hunt-fintech-graphql

github.com/elementalsoulsセキュリティ5.3k159 回インストール今日更新
リポジトリ

hunt-fintech-graphql について

フィンテックの GraphQL API を監査するテスター向けのセキュリティスキルです。資金移動、金額の精度処理、KYC・個人情報の認可を重点的に調べ、手順と検証基準を示します。

主な機能

  1. 01資金移動ミューテーションと台帳書き込みの把握
  2. 02冪等キーの再送と小数精度のテスト
  3. 03送金元の認可と IDOR の確認
  4. 04ネストされた KYC・個人情報フィールドの調査
  5. 05エイリアス一括処理の二重支出とマスアサインメントの検証

活用シーン

  1. 01送金、出金、リワード API の監査
  2. 02フィンテック GraphQL の口座アクセス制御の確認
  3. 03金額の解析・丸めと為替レート整合性のテスト
  4. 04台帳への並行書き込みリスクの再現

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill hunt-fintech-graphql

インストール後、Claude Code で/hunt-fintech-graphqlを入力して読み込まれたか確認します。

詳細

Fintech GraphQL が異なるリスククラスである理由

一般的な GraphQL の脆弱性(IDOR、マスアサインメント、イントロスペクション、バッチ処理の悪用。hunt-graphql を参照)も ここでは当てはまりますが、被害の範囲はまったく異なります。SaaS アプリのリゾルバーのバグは データを漏えいさせますが、台帳の変更処理に同種のバグがあると、資金が動きます。Fintech の GraphQL バックエンドを独自の調査対象にする要因は、次の 3 つです。

  • 資金移動のミューテーションは、ほぼ常に複式簿記台帳のリゾルバーです。 1 つの GraphQL ミューテーション(transferFunds、redeemRewards、withdrawToBank)によって、 複数の台帳書き込み(借方+貸方+手数料)が発生することがあり、これらはアトミックでなければなりません。GraphQL の柔軟な入力形式と エイリアスによるバッチ処理により、これらの書き込みを簡単に同期ずれさせられます。
  • 小数は表示形式ではなく、攻撃者が制御できる入力です。 金額、為替レート、利息、 報酬ポイントは通常、GraphQL スカラー(Float、String、カスタムの Decimal/Money スカラー)として渡されます。リゾルバーがその値をどのように解析し、丸めるかは、それ自体が悪用可能な攻撃対象です。 金融系以外の GraphQL API では、これはほとんど存在しません。
  • KYC/PII フィールドが、同じ型の一般的なアカウントフィールドの隣にあります。 User や Account 型では、ssnLast4、routingNumber、kycStatus、governmentIdUrl、 linkedBankAccount が displayName や email と並んで公開されていることがよくあります。スキーマ全体で使われる型に対する フィールドレベルの認可チェックが 1 つ欠けているだけで、その型に触れるあらゆるクエリに影響が広がります。

攻撃対象領域を示すシグナル

URL/スキーマの命名パターン(hunt-graphql の一般的な /graphql リストに加えて確認するもの):

/graphql/ledger
/graphql/payments
/api/wallet/graphql
/internal/ledger-graphql
/banking/graphql

スキーマのイントロスペクションや JS バンドルで grep する価値のあるフィールド名/型名:

balance, availableBalance, pendingBalance, ledgerEntry, ledgerEntries
transferFunds, withdraw, redeem, topUp, reverseTransaction, adjustBalance
kycStatus, ssnLast4, routingNumber, accountNumber, governmentIdUrl
quoteExchangeRate, interestAccrued, rewardsPoints, portfolioValue
idempotencyKey, clientMutationId

この分野に特有の技術スタックの手がかり:

  • 内部 GraphQL ゲートウェイの背後にある Plaid/Stripe/Dwolla/Marqeta(bankLink、plaidLinkToken ミューテーション)
  • 専用の ledger または payments サブグラフを持つ Apollo Federation。ゲートウェイで統合されたスキーマを迂回して、サブグラフ独自のイントロスペクションに直接アクセスできるか確認してください
  • スキーマ内のカスタム Money/Decimal/BigDecimal GraphQL スカラー(scalar Money)。このスカラーのパーサーは、直接ファジングする価値があります

まず hunt-graphql の探索+イントロスペクションの手法を実行してスキーマを取得してください。以下はすべて、 資金移動関連の型を含むスキーマをすでに取得している(または一部を列挙している)ことを前提としています。


手順に沿ったハンティング手法

  1. 直接、または副作用として残高に影響するすべての変更処理を洗い出します。 transfer*/withdraw* だけでなく、redeemRewards、applyCoupon、upgradeTier、closeAccount(残高が返金されることがよくあります)、disputeTransaction(暫定的に入金されることがよくあります)も対象です。

  2. 資金移動を伴う変更処理ごとに、台帳への書き込み形式を特定します。 1 回の変更処理で台帳エントリーが 1 件作成されるのか、それとも複数作成されるのか(送金元からの引き落とし、受取人への入金、手数料エントリーなど)を確認します。複数エントリーが書き込まれる処理こそ、競合を試す価値があります。ステージ 4 を参照してください。

  3. 冪等性キーの処理をテストします。 同一の変更処理(同じ idempotencyKey/clientMutationId)を、間を空けずに 2 回連続で、また時間を空けて 2 回送信します。2 回目の呼び出しで台帳に書き込みが行われるなら、サーバー側で冪等性が保証されていません。再送すれば二重実行されます。

  4. 金額を受け取るすべての引数について、小数や精度のエッジケースをテストします。 ペイロードのセクションを参照してください。サーバー側の丸め処理がクライアントに表示される丸め処理と一致していることを確認します。不一致は直接、収益化につながります。

  5. アカウント/ポートフォリオのノード ID を使ったアカウント間の IDOR を調べます。 hunt-idor/hunt-graphql と同様に調査しますが、特に transferFunds のような変更処理で、指定した送金元アカウントが認証済みの呼び出し元に属していることを検証しているかテストします。ID が一致するアカウントが存在するかどうかだけでは不十分です。これがフィンテック特有の IDOR です。クレジット先の認可が正しく実装されていても、デビット元の認可は見落としやすいものです(開発者にとって、任意のアカウントへの入金は「安全そう」に見えるためチェックされますが、引き落としは明らかに安全ではないため、チェックされます。ただし、一方の方向しかチェックされないことがあります)。

  6. KYC/PII フィールドのフィールドレベル認可を確認します。 共有の User/Account 型を返すすべてのコンテキストからクエリし、プロフィール画面だけに限定しないでください。たとえば、counterparty { ssnLast4 } を埋め込んだ transaction 型は、認可チェックが抜けやすい箇所です。開発者がトップレベルの transaction クエリは認可していても、ネストされた counterparty のフィールドアクセスを再確認していないことがあるためです。

  7. 一括代入によって到達可能な管理者権限の変更処理を探します。 認証チェックの欠落だけが対象ではありません。たとえば、一般ユーザー向けの変更処理では公開されるべきでない、クライアントから設定可能な status や override フィールドを入力オブジェクトが持ち、リゾルバーがそれを受け入れてしまうケースです(updateTransaction(input: {id, status: "COMPLETED", amount: "..."}))。

  8. 通貨引数の整合性をテストします。 UI からは生成されない sourceCurrency/targetCurrency の組み合わせ(たとえば、通貨換算を伴う同一アカウント間の送金)を指定して、送金/見積もりの変更処理を送信し、リゾルバーの為替レート検索と台帳への書き込みで同じレートが使われるか確認します。ここに TOCTOU の隙があれば、直接裁定取引につながるバグです。

  9. エイリアスのバッチ処理と資金移動を伴う変更処理を組み合わせて、二重支出をテストします。 エイリアスのバッチ処理だけでリゾルバーがアカウントごとの書き込みを直列化していないと確認できたら、並列 HTTP によるエスカレーションについて hunt-race-condition を参照してください。


ペイロードと検出パターン

冪等性キーのリプレイテスト:

mutation {
  transferFunds(input: {
    idempotencyKey: "test-key-001"
    sourceAccountId: "acc_1"
    destAccountId: "acc_2"
    amount: "10.00"
  }) { transactionId status }
}

同じ idempotencyKey を使って2回送信します。異なる transactionId で両方とも成功する場合 =冪等性が適用されていません。

小数精度/丸めのプローブ:

mutation { transferFunds(input: {sourceAccountId:"acc_1", destAccountId:"acc_2", amount: "0.001"}) { transactionId } }
mutation { transferFunds(input: {sourceAccountId:"acc_1", destAccountId:"acc_2", amount: "9999999999999999.99"}) { transactionId } }
mutation { transferFunds(input: {sourceAccountId:"acc_1", destAccountId:"acc_2", amount: "1e2"}) { transactionId } }
mutation { transferFunds(input: {sourceAccountId:"acc_1", destAccountId:"acc_2", amount: "-50.00"}) { transactionId } }

1セント未満の金額で切り捨てと四捨五入の処理をテストします(N回繰り返して、丸め誤差による残高のずれを蓄積させます)。科学表記と過大な値で、Money/Decimal スカラーのパーサーがオーバーフローや精度損失を伴う動作をするネイティブの float/int にフォールバックするかどうかをテストします。負の金額で、リゾルバーが符号をサーバー側で決めているか、クライアントの値を信頼しているかをテストします。

エイリアスを使った一括二重使用プローブ(並列 HTTP に進む前に確認):

mutation {
  r1: redeemRewards(input: {rewardId: "rwd_1", accountId: "acc_1"}) { success }
  r2: redeemRewards(input: {rewardId: "rwd_1", accountId: "acc_1"}) { success }
  r3: redeemRewards(input: {rewardId: "rwd_1", accountId: "acc_1"}) { success }
}

単一使用の特典/クーポンに対して複数のエイリアスが成功する場合、リゾルバーは一括リクエスト内でアカウント単位/リソース単位の書き込みを直列化していません。これを並列 HTTP POST と組み合わせて実際の二重使用の影響を確認する方法については、hunt-race-condition を参照してください。

送金元アカウントの認可プローブ(非対称 IDOR チェック):

mutation {
  transferFunds(input: {
    sourceAccountId: "VICTIM_ACCOUNT_ID"
    destAccountId: "ATTACKER_CONTROLLED_ACCOUNT_ID"
    amount: "1.00"
  }) { transactionId status }
}

攻撃者自身のセッション/トークンで実行します。成功した場合、リゾルバーは送金先が攻撃者の管理下にあること(当然必要です)は検証していますが、送金元が呼び出し元に属していることは検証していません。

ネストされたフィールドレベルの PII プローブ:

query {
  transaction(id: "txn_123") {
    amount
    counterparty { displayName ssnLast4 routingNumber kycStatus }
  }
}

共通の取引以外に取引相手との関係がないユーザーとしてクエリします。トップレベルの transaction クエリで取引自体が正しくスコープされていても、ネストされた PII フィールドへのアクセスが成功すれば、それが問題です。

管理者向けの入力フィールドを対象としたマスアサインメントプローブ:

mutation {
  updateTransaction(input: {id: "txn_123", status: "COMPLETED", amount: "0.01"}) { id status }
}

クライアント UI ではこれらのフィールドを公開していないミューテーションに対して、非管理者ユーザーとして送信します。それでもスキーマがフィールドを受け入れる場合、これは台帳状態へのマスアサインメントです。


よくある根本原因

  1. 金額/手数料の検証がクライアント側のみ。 UI は正しい金額を計算して表示しますが、リゾルバーは GraphQL クライアントが実際に送信した値を信頼しています。「アプリは常に正しい値を送信する」からです。
  2. 複数エントリーの台帳書き込みがアトミックでない。 借方、貸方、手数料のエントリーが単一のトランザクション/ロック内ではなく、別々の逐次ステートメントとして書き込まれています。これによって生じる競合の隙間を、エイリアスの一括実行と並列 HTTP がまさに悪用します。
  3. Money/Decimal スカラーがエッジケースの入力(科学表記、過大な文字列)でネイティブの float パーサーにフォールバックする。 これにより、固定小数点精度を保証するはずのシステムに浮動小数点の丸め誤差が再び持ち込まれます。
  4. 冪等性キーは保存されるものの、書き込みを実行する前にチェックされない。 キーはサポート/デバッグ目的でログに記録されているだけで、重複排除のゲートとして使われていません。
  5. フィールドレベルの認可が、型ごとではなくトップレベルのクエリごとに実装されている。 User/Account 型の機密フィールドは、直接クエリされた場合(me { ssnLast4 })には保護されますが、無関係なクエリ内で同じ型がネストされて返される場合(transaction { counterparty {...} })には保護されません。
  6. 送金先アカウントの存在確認はある一方で、送金元アカウントの所有権チェックが欠けている。 方法論のステップ5を参照してください。引き落としは危険に見えるためレビューされますが、「このアカウントは呼び出し元のものか」というチェックは、ひそかに入金先に対してのみ適用されています。
  7. 管理者向け/内部向けミューテーションが、追加の任意フィールドを持たせただけの同じ入力型を公開ミューテーションと共有している。 リゾルバーレイヤーで非管理者の呼び出し元に対してフィールドを取り除く処理はありません。

Gate 0 の検証

資金移動に関する調査結果には、一般的な GraphQL IDOR よりも厳格な基準が必要です。「クエリによって他人の残高が返される」ことには実質的な影響がありますが、「不正な金額を送信したら 400 が返された」だけでは影響があるとは言えません。

  1. 実際に台帳への書き込みが発生し、その証拠を示せますか? アカウント残高を変更前と変更後で照会してください。200 や成功レスポンスの本文ではなく、状態が変化したことが証拠になります。
  2. 再現性のある成功であり、タイミングによる偶然ではありませんか? 競合状態や二重支払いに関する調査結果は、クリーンな状態から 2 回再現してください。特定の負荷条件下でしか成功しない場合は、悪用が確実に可能だと主張するのではなく、その条件が成立する時間枠を正直に記載してください。
  3. 攻撃者が持っていない価値を移動させるか、見るべきでないデータを明らかにしていますか? 「ミューテーションが予想外の入力型を受け付け、API がエラーメッセージを返した」だけでは不十分です。不正な Money スカラーによって詳細な GraphQL エラーからスタックトレースが漏えいするケースは、金融ロジックの問題ではなく、hunt-source-leak クラスの調査結果です。報告ではこの 2 つを混同しないでください。

関連するスキルとチェーン

  • hunt-graphql — 一般的な GraphQL の探索、イントロスペクションのバイパス、ノード ID の IDOR、エイリアスによるバッチ処理の手法を扱う親スキルです。まずこのスキルを読み込んでください。hunt-fintech-graphql はその手法を前提とし、資金移動に特化した差分のみを追加します。
  • hunt-business-logic — クーポンや報酬の二重引き換えなど、ロジック上の欠陥パターンは、ここでの redeemRewards/applyCoupon 形式のミューテーションにも直接応用できます。
  • hunt-race-condition — エイリアスによるバッチ処理だけで、資金移動ミューテーションが書き込みを直列化しないことを確認した後の、エスカレーション手順です。並列 HTTP/単一パケット攻撃と組み合わせることで、再現性のある二重支払いの PoC を作成できます。
  • hunt-api-misconfig — マスアサインメントや JWT クレーム改ざんの手法は、一般ユーザーがアクセスできる、管理者向けの形式をした GraphQL 入力オブジェクトにも直接応用できます。
  • hunt-idor — 送金元アカウントと送金先アカウントで認可が非対称になるパターン(手順 5)は、ミューテーション引数に対する一般的な IDOR クラスの、金融技術固有の事例です。
  • evidence-hygiene — 残高のスクリーンショットや台帳エントリーの PoC には、他のあらゆるキャプチャと同じく、Cookie や PII を適切にマスキングする必要があります。また、実際に利用されている金融口座の口座番号や残高を、そのまま掲載しないよう注意してください。
  • triage-validation — 報告書の作成前に上記の Gate 0 を適用してください。金融技術プログラムのトリアージチームは、台帳の状態変化が実証されていないものを即座に却下します。

コメント