mcpField
S
sast-graphql

sast-graphql

github.com/utkusenセキュリティ1.3k70 回インストール5 日前に更新
リポジトリ

sast-graphql について

sast-graphqlは、コードベースのGraphQLインジェクションを検出する安全性評価スキルです。GraphQLの利用を確認し、動的に組み立てられる操作ドキュメントを特定して、入力をバッチ単位で追跡し結果をまとめます。

主な機能

  1. 01GraphQL利用の有無を確認
  2. 02動的な操作ドキュメント構築箇所の特定
  3. 03ユーザー入力を並列バッチで追跡
  4. 04検証結果の統合
  5. 05事前のアーキテクチャ概要が必要

活用シーン

  1. 01GraphQL操作ドキュメント構築の確認
  2. 02入力によるクエリテキスト変更の調査
  3. 03GraphQLゲートウェイ転送コードの評価

インストール

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

$npx skills add https://github.com/utkusen/sast-skills --skill sast-graphql

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

詳細

GraphQL インジェクションの検出

GraphQL インジェクションの脆弱性を見つけることを目的とした、重点的なセキュリティ評価を実施します。このスキルでは、サブエージェントを使った 3 段階のアプローチを採用します。recon(GraphQL の使用を確認し、GraphQL オペレーションドキュメントが安全でない方法で組み立てられている箇所をすべて特定)、batched verify(ユーザー入力がそれらの組み立て箇所に到達するかを、各 3 箇所までの並列バッチで追跡)、merge(バッチの結果を統合して最終レポートを作成)。

前提条件:sast/architecture.md が存在している必要があります。存在しない場合は、先に分析スキルを実行してください。


GraphQL インジェクションとは

GraphQL インジェクションは、ユーザーが制御するデータが変数マップを通じてのみ渡されるのではなく、GraphQL ドキュメント(クエリ、ミューテーション、サブスクリプションの文字列)に埋め込まれると発生します。するとパーサーは、攻撃者が制御する構文(新しいフィールド、エイリアス、ディレクティブ、フラグメント)を解釈し、意図を回避したり、認可されていないリゾルバーに到達したり、そのドキュメントが実行または転送された際にサーバー側の動作を変えたりする可能性があります。

中核となるパターン:検証されていないユーザー入力によって、execute、graphql、ゲートウェイクライアント、または文字列操作で組み立てられた HTTP ボディの query フィールドに渡される GraphQL オペレーション文字列の構造やテキストが変化します。

GraphQL インジェクションに該当するもの
  • ユーザー入力をオペレーション文字列に連結または補間すること:`query { user(id: "${id}") { name } }`、"query { user(id: \"" + id + "\") { name } }"
  • リクエストボディやパラメーターからの文字列連結によって、下流の GraphQL HTTP リクエストの JSON query フィールドを組み立てること
  • req.body.query(または同様の値)を、オペレーションをラップまたは拡張する別の補間テンプレートに渡すこと
  • 静的でない式によってドキュメントの構造が変化する動的な gql/graphql-tag テンプレートリテラル(静的ドキュメント内でバインドされた変数の値だけを変える場合は除く)
  • ユーザー入力からオペレーションテキストを選択または組み立てるサーバー側コード(許可リストのない「永続クエリ」ID → ドキュメントのマップを含む)
  • graphql.execute()、graphqlHTTP、Yoga/Apollo のリクエストパイプラインをラップし、第 1 引数(ドキュメント/ソース)がユーザーの影響を受ける可能性のある変数から組み立てられるコード
GraphQL インジェクションに該当しないもの

以下を GraphQL インジェクションとして報告しないでください。

  • リゾルバー内の SQL インジェクション:args から SQL を組み立てるリゾルバーコードは、SQL インジェクション(sast-sqli)であり、このスキルの対象ではありません
  • リゾルバー内の NoSQL インジェクション/コマンドインジェクション:同様に、適切な SAST スキルを使用してください
  • GraphQL 引数を介した IDOR:静的なドキュメントを使い、別のユーザーの ID を変数の JSON で渡す場合は、認可の不備であり、ドキュメントインジェクションではありません
  • 通常の変数バインド:{"query": "query($id: ID!) { user(id: $id) { name } }", "variables": {"id": userInput}} のような静的ドキュメントでは、値は変数としてバインドされ、ドキュメントの構造は固定されています(リゾルバーでの認可は引き続き確認してください)
  • イントロスペクション/フィールド候補の提示が有効:情報漏えいとセキュリティ強化の問題です。オペレーション文字列への注入が具体的な指摘内容である場合に限り、GraphQL インジェクションとして報告してください
  • クエリの深さ/複雑さによる DoS:レート制限とコスト分析の問題であり、別の種類です
GraphQL インジェクションを防ぐパターン

1. 変数を使う静的なオペレーションドキュメント

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { name }
  }
`;
// execute(schema, GET_USER, null, context, { id: userId });

2. サーバーで標準の HTTP ハンドラーを使用し、クライアントがドキュメントを送信して、サーバーが一度だけパースする

サーバーがクライアントのオペレーションとしてパースして実行するだけであれば、サーバー上に req.body.query が存在すること自体はリスクではありません。その経路でのインジェクションはクライアント側の問題です。execute の前、または転送の前にユーザー文字列を取り込んだ新しいドキュメントを組み立てるサーバー側の処理を報告してください。

3. 永続クエリ/許可リストに登録されたオペレーション ID

サーバー側のレジストリから ID に基づいてドキュメントを検索するため、クライアントは任意のドキュメントテキストを注入できません。

4. 静的文字列を使った graphql-js の Source。動的な値は variableValues のみ

graphql({ schema, source: staticQueryString, variableValues: { id: userId } });

脆弱な例と安全な例

Node.js — 下流 API 向けの動的ドキュメント
// VULNERABLE: user input in operation text
app.post('/proxy', async (req, res) => {
  const fragment = req.body.fragment;
  const query = `query { me { ${fragment} } }`;
  const data = await fetch('https://api.internal/graphql', {
    method: 'POST',
    body: JSON.stringify({ query }),
  });
});

// SECURE: static operation, user data only in variables
const PROXY_QUERY = `query ProxyMe { me { id name email } }`;
app.post('/proxy', async (req, res) => {
  const data = await fetch('https://api.internal/graphql', {
    method: 'POST',
    body: JSON.stringify({ query: PROXY_QUERY }),
  });
});
Python — execute に文字列形式で渡す
# VULNERABLE
def run_custom_query(user_gql: str):
    document = f"query {{ user {{ {user_gql} }} }}"
    return graphql_sync(schema, document)

# SECURE: validate against allowlist of named operations or use static documents only
ALLOWED = {"id", "name", "email"}
fields = [f for f in requested_fields if f in ALLOWED]
document = "query { user { " + " ".join(ALLOWED.intersection(set(requested_fields))) + " } }"
# Better: fixed FieldNodes, not string building from user input

実行

このスキルは、サブエージェントを使用して3つのフェーズで実行されます。sast/architecture.md の内容を、すべてのサブエージェントにコンテキストとして渡してください。

フェーズ 1:GraphQL 技術の調査とインジェクション候補箇所

次の指示でサブエージェントを起動します。

目標:(1)このコードベースで GraphQL が使われているかを判断します。(2)使われている場合は、変数によって variables の JSON だけでなくドキュメントのテキストも変わるような形で、GraphQL の オペレーションドキュメント(query/mutation/subscription のソース文字列)が文字列連結、補間、フォーマット、または動的な組み立てによって構築されている箇所をすべて特定します。結果を sast/graphql-recon.md に記録してください。

背景:プロジェクトのアーキテクチャ概要が提供されます。スタック、API の構成、BFF/ゲートウェイのパターンを把握するために活用してください。

パート A — GraphQL は使われているか?

次の項目を検索してください。

  • 依存関係:graphql、@apollo/server、apollo-server-express、@nestjs/graphql、graphql-yoga、@graphql-yoga/node、mercurius、strawberry-graphql、graphene、sangria、gqlgen、async-graphql、juniper、graphql-ruby、Hot Chocolate/GraphQL.Server など。
  • スキーマ関連ファイル:*.graphql、*.graphqls、コード生成設定(例:GraphQL Code Generator)
  • /graphql などをマウントするサーバールートやプラグイン

概要は次のいずれか一つだけにしてください。

  • GraphQL is used in this codebase.(ライブラリと主なエントリーポイントを列挙)
  • GraphQL is not used in this codebase.

パート B — インジェクション候補箇所(GraphQL が使われている場合のみ)

GraphQL が使われていない場合は、「Injection Candidate Sites」セクションを省略するか、該当箇所がないと記載してください。候補を捏造しないでください。

GraphQL が使われている場合は、安全でないドキュメント構築を検索してください。

  1. オペレーションテキストへの文字列連結/補間:
    • `query { ... ${x} ...}`、"mutation { " + userFragment + " }"
    • sprintf、format、% 書式、query または source 引数を構築する .format()
  2. ドキュメント引数がコンパイル時定数ではない呼び出し:
    • graphql(schema, dynamicString, ...)、execute({ schema, document: parsedDynamic, ...}) で、parse または execute に渡す文字列が静的でない部分から構築されている場合
    • ユーザーデータによってクエリ文字列を変更またはラップする graphqlHTTP({ schema, rootValue, context: (req) => ({ query: req.body.query + something }) }) のようなパターン
  3. 構築した GraphQL ボディを転送する HTTP クライアント:
    • JSON.stringify({ query: ...${userPart}... })、axios.post(url, { query: builtFromInput })
  4. 安全でない永続化/保存済みクエリの検索:
    • ユーザー入力のキーを使い、許可リストを通さずに読み込まれるオペレーションテキスト。ファイルパスや DB の値がドキュメントのソースになる場合

対象外(フェーズ 1 の候補として報告しないでください):

  • 完全に静的な source/query 文字列。リクエストから取得されるのが variableValues/variables のみの場合
  • ユーザーによる補間を含まない buildSchema によるスキーマ定義/SDL ファイル
  • パラメーター化された DB API で引数を使うだけのリゾルバー実装(任意で「リゾルバーは ORM を使用」と記載してもかまいませんが、ドキュメントが安全でない形で構築されていない限り、GraphQL インジェクション候補ではありません)

出力形式 — sast/graphql-recon.md に記載してください。

# GraphQL Recon: [Project Name]

## Summary
GraphQL is [used / not used] in this codebase.
[If used: libraries, main server files, typical endpoint paths]
Found [N] injection candidate site(s) where operation documents may be built unsafely. [If not used, say N/A or 0 and skip candidate list]

## GraphQL Surface (only if used)
- **Libraries / frameworks**: ...
- **Entry points**: ...
- **Notable files**: ...

## Injection Candidate Sites

### 1. [Descriptive name]
- **File**: `path/to/file.ext` (lines X-Y)
- **Function / endpoint**: ...
- **Execution / call pattern**: [graphql.execute / fetch with body / gql template / etc.]
- **Construction pattern**: [concat / template literal / format / forwarded body mutation]
- **Interpolated variable(s)**: ...
- **Code snippet**:

...


[Repeat for each site; if none, write "No injection candidate sites found." under the heading]
フェーズ 1 の後:フェーズ 2 に進む前のゲート

フェーズ 1 が完了したら、sast/graphql-recon.md を読みます。

ゲート 1 — GraphQL 技術が使われていない

概要に GraphQL が使われていない(または、同等の内容として GraphQL ライブラリ、スキーマ、サーバーがなく、明らかに使われていない)と記載されている場合は、フェーズ 2 と 3 をスキップします。以下を sast/graphql-results.md に書き込み、終了します。

# GraphQL Injection Analysis Results

No GraphQL technology detected in this codebase.

ゲート 2 — GraphQL は使われているが、インジェクション候補がない

GraphQL は使われているものの、インジェクション候補サイトが0 件(概要に候補数 0 と記載されている、または「Injection Candidate Sites」セクションに該当箇所がない、あるいは空である)場合は、フェーズ 2 と 3 をスキップします。以下を sast/graphql-results.md に書き込み、終了します。

# GraphQL Injection Analysis Results

No vulnerabilities found.

それ以外の場合はフェーズ 2 に進みます。

フェーズ 2:ユーザー入力からインジェクション候補サイトまでを追跡する(バッチ処理)

フェーズ 1 が完了し、両方のゲートを通過した後(GraphQL が使われており、候補サイトが少なくとも 1 つある場合)、sast/graphql-recon.md を読み、「Injection Candidate Sites」を1 バッチあたり最大 3 サイトに分割します(各 ### N. セクションが 1 サイトです)。バッチごとにサブエージェントを 1 つ起動し、並列で実行します。各サブエージェントは、割り当てられたサイトのみの汚染を追跡し、結果を各自のバッチファイルに書き込みます。

バッチ分割手順(サブエージェントではなく、オーケストレーターであるあなたが実行します):

  1. sast/graphql-recon.md を読み、「Injection Candidate Sites」内の番号付き候補セクション(### 1., ### 2. など)の数を数えます。
  2. 最大 3 件ずつのバッチに分割します。たとえば、サイトが 8 件なら、3 バッチ(1~3、4~6、7~8)に分けます。
  3. 各バッチについて、recon ファイルから該当する候補セクションの全文を抽出します。
  4. 各サブエージェントには、割り当てられた候補セクションのみ(アーキテクチャのコンテキストを含む)を渡し、すべてのバッチサブエージェントを並列で起動します。
  5. 各サブエージェントは sast/graphql-batch-N.md に書き込みます。N は 1 から始まるバッチ番号です。
  6. sast/architecture.md からプロジェクトの主要な言語/フレームワークを特定し、上記の「Vulnerable vs. Secure Examples」セクションから該当する例だけを選びます。たとえば、プロジェクトが Node.js を使っている場合は「Node.js — dynamic document for downstream API」の例を、Python を使っている場合は「Python — string format into execute」の例を含めます。以下の [TECH-STACK EXAMPLES] で指定されている箇所に、選択した例を各サブエージェントへの指示に含めます。

各バッチサブエージェントには、以下の指示を与えます(バッチ固有の値に置き換えてください)。

目標:割り当てられた各インジェクション候補箇所について、ユーザー提供データが操作ドキュメントの動的部分に到達するかどうかを判断します。GraphQL インジェクションの脆弱性を見つけることが目的です。結果を sast/graphql-batch-[N].md に記録してください。

割り当てられた候補箇所(調査フェーズより):

[元の番号を維持したまま、割り当てられた候補セクションの全文を貼り付けてください]

コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。API の構成、リクエスト処理、BFF/ゲートウェイのパターンを把握するために使用してください。

GraphQL インジェクションのリファレンス — 確認事項:

ユーザーが制御するデータによって、静的ドキュメントにバインドされた変数を介する場合を除き、GraphQL ドキュメントのテキスト(クエリ/ミューテーション/サブスクリプションのソース)が変更されてはいけません。汚染データが操作の文字列組み立てに到達する場合は、問題として報告してください。

GraphQL インジェクションに該当しないもの — ここでは報告しないでください:

  • リゾルバー内の SQL/NoSQL インジェクション — 他の SAST スキルで扱います
  • 静的ドキュメント+変数による IDOR — ドキュメントインジェクションではなく、認可の問題です
  • 固定ドキュメント文字列での通常の変数バインディング
  • イントロスペクションの有効化 — 問題が操作文字列インジェクションに特化している場合を除きます
  • クエリ深度/複雑度を利用した DoS — 別の種類の問題です

リスクを軽減する緩和策:

  • 文字列を組み立てる前に、フィールドまたは操作 ID を許可リストで検証する
  • 予期しない定義を拒否するパーサー検証(ただし、ユーザーがドキュメント構造を制御できない方法を優先してください)

このプロジェクトの技術スタックにおける脆弱な例と安全な例:

[TECH-STACK EXAMPLES]

割り当てられた各箇所について、動的な値をさかのぼって追跡してください:

  1. 直接的なユーザー入力 — クエリパラメーター、パスパラメーター、JSON ボディのフィールド(再ラップされた query を含む)、ヘッダー、Cookie
  2. 間接的なユーザー入力 — ヘルパー、ミドルウェア、コンテキストビルダー
  3. 二次的な入力 — 後でドキュメントの構築に使用される保存済みの設定や DB フィールド。書き込み経路も追跡してください
  4. サーバー側のみ — 設定、環境変数、ハードコードされた断片。クライアントから悪用することはできません

分類:

  • 脆弱:有効な緩和策がないまま、ユーザーが制御するデータがドキュメントの構築に到達する
  • 脆弱の可能性あり:汚染データの流入が疑われる、またはサニタイズが不十分
  • 脆弱ではない:サーバー側のみ、または有効な許可リスト/静的ドキュメントの経路
  • 手動レビューが必要:フローが不明瞭

出力形式 — sast/graphql-batch-[N].md に記録してください:

# GraphQL バッチ [N] の結果

## 検出結果

### [脆弱] 説明的な名前
- **ファイル**:`path/to/file.ext`(行 X-Y)
- **エンドポイント/関数**:...
- **問題**:...
- **汚染データの追跡**:...
- **影響**:[例:未認可フィールド、ゲートウェイの迂回、内部 GraphQL に対する SSRF のような動作]
- **修正方法**:[静的な操作、変数のみ、永続クエリの許可リスト]
- **動的テスト**:

[フラグメント/ディレクティブ/フィールドを注入する curl またはブラウザー内 GraphQL リクエスト]


### [脆弱の可能性あり] 説明的な名前
- **ファイル**:...
- **エンドポイント/関数**:...
- **問題**:...
- **汚染データの追跡**:...
- **懸念事項**:...
- **修正方法**:...
- **動的テスト**:

...


### [脆弱ではない] 説明的な名前
- **ファイル**:...
- **エンドポイント/関数**:...
- **理由**:...

### [手動レビューが必要] 説明的な名前
- **ファイル**:...
- **エンドポイント/関数**:...
- **不確実な点**:...
- **提案**:...
フェーズ 3: マージ — バッチ結果の統合

すべてのフェーズ 2 のバッチサブエージェントが完了したら、すべての sast/graphql-batch-*.md ファイルを読み込み、単一の sast/graphql-results.md にマージします。これ(オーケストレーター)を直接実行します — サブエージェントは不要です。

マージ手順:

  1. すべての sast/graphql-batch-1.md、sast/graphql-batch-2.md、... ファイルを読み込みます。
  2. 各バッチファイルからすべての検出結果を収集し、1 つのリストに統合します。元の分類とすべての詳細フィールドを保持します。
  3. エグゼクティブサマリーのために、すべてのバッチの合計を集計します。
  4. 次の形式を使用して、マージされたレポートを sast/graphql-results.md に書き込みます:
# GraphQL Injection Analysis Results: [Project Name]

## Executive Summary
- Candidate sites analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]

## Findings

[All findings from all batches, grouped by classification:
 VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
 Preserve every field from the batch results exactly as written.]
  1. sast/graphql-results.md を書き込んだ後、すべての中間バッチファイル(sast/graphql-batch-*.md)を削除します。

重要なリマインダー

  • sast/architecture.md を読み込み、その内容をコンテキストとしてすべてのサブエージェントに渡します。
  • フェーズ 1 で GraphQL テクノロジーが検出されなかった場合は、フェーズ 2 と 3 をスキップします — 「No GraphQL technology detected」結果ファイルを書き込みます。
  • GraphQL が使用されているが、フェーズ 1 でインジェクション候補が検出されなかった場合は、フェーズ 2 と 3 をスキップします — 「No vulnerabilities found」を書き込みます。
  • フェーズ 2 はフェーズ 1 の完了後に実行する必要があります — これは偵察出力に依存します。
  • フェーズ 3 はすべてのフェーズ 2 バッチの完了後に実行する必要があります — これはすべてのバッチ出力に依存します。
  • バッチサイズはサブエージェントあたり 3 つの候補サイトです。合計 1〜3 サイトの場合は、単一のサブエージェントを使用します。10 の場合は、4 つのサブエージェントを使用します(3+3+3+1)。
  • すべてのバッチサブエージェントを並行して起動します — 順次実行しないでください。
  • 各バッチサブエージェントは、偵察ファイル全体ではなく、割り当てられた候補のテキストのみを偵察ファイルから受け取ります。これにより、各サブエージェントのコンテキストが小さく焦点を絞った状態に保たれます。
  • フェーズ 1 はテイントを追跡しません。フェーズ 2 は追跡します。
  • リゾルバー層の SQL/NoSQL の問題は他のスキルに属します。このスキルはオペレーションドキュメントの構築を対象としています。
  • 疑わしい場合は、「Not Vulnerable」ではなく「Needs Manual Review」として分類します。
  • 中間ファイルをクリーンアップします: 最終的な sast/graphql-results.md を書き込んだ後、sast/graphql-recon.md とすべての sast/graphql-batch-*.md ファイルを削除します。

コメント