GraphQL安全初探

前言

记得p神大佬在先知白帽的时候分享过graphiql相关的安全风险,一直没有时间研究。正巧这次项目遇见了graphiql的一些技术。所以对graphiql进行学习分享一下

基础知识

概念

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

graphiql的优势

  • 请求你所要的数据不多不少(数据裁剪)
  • 获取多个资源只用一个请求(多个资源一个uri)
  • 描述所有的可能类型系统

合作案例

其实在国内实际项目中遇到的比较少,不过从官网来看使用graphiql的大厂确实不少
合作厂商

调试工具

brew cask install graphiql (因为使用的mac直接brew安装)
因为graphiql也是基于http的其实burp也可以但是格式比较丑。

用于描述接口的操作类型,有Query(查询)、Mutation(更改)和Subscription(订阅)三种。我们重点关注的是有Query、Mutation详细大家参考官方文档https://graphql.cn/learn/queries/

内省机制

内省机制是为了方便知道GraphQL Schema支持哪些查询而诞生的。有点类似于Swagger的感觉。

一般的表现形式是双下滑杠开头: __Schema, __Type, __TypeKind, __Field, __InputValue, __EnumValue, __Directive

具体实现:https://github.com/graphql/graphql-js/blob/master/src/type/introspection.js

获取某个对象的可用字段

1
2
3
4
5
6
7
8
9
10
11
12
{
__type(name: "User") {
name
fields {
name
type {
name
kind
}
}
}
}

获取某个对象的可用字段

获取所有可用的对象

1
2
3
4
5
6
7
{
__schema {
types {
name
}
}
}

获取所有可用的对象

获取所有可用的方法和字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
}
}
}
}
}
}
}
}

Query类常规的查询方法定义(这类操作方法的关键词必须为Query)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#定义查询方法
type Query {
# 通过UserId获取单个用户详细信息
getUsers(UserId: String!): User
}



# 定义用户对象
type User {
# 数据库id
id: String!
# 用户名
username: String
#密码
password: String
# 所属部门id列表
departmentIds: [String!]!
# 用户拥有的角色列表
roles: [String!]!
# 用户的手机号列表
phone: String
#入职时间
hiredate: Date
#工号
employeeNumber: String
}

怎么查询数据,!号是参数不能为空,如果定义了不能为空,查询时也要填类型的时候后面也要加!.这是个坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
query ($UserId: String!) {
getUsers(UserId: $UserId) {
username
#phone这块是个对象,上面懒得定义了。直接展示String代表了。这个主要是展现对象形式的查找
phone{
areaCode
number
}
}
}


#variables:
{"UserId":"123456"}

没有参数的查询方式

1
2
3
4
5
6
query{
getInvoices{
bankName
companyName
}
}

Mutation类增删改操作,这类操作方法的关键词必须为Mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Mutation {

# 投票接口
voteTicket(request: VoteTicketRequest): GeneralResponse!

}

input VoteTicketRequest {
# 投票id
voteId: String!
# 选项id list
optionIds:[String!]!
}



# 通用返回结果
type GeneralResponse {
# 创建结果
ok: Boolean!
# 错误信息
errMsg: String
}

传值调用:

1
2
3
4
5
6
7
# 自己构造
mutation ($request: VoteTicketRequest) {
voteTicket(request: $request) {
ok
errMsg
}
}

Query Variables

1
{"request":{"voteId":"6669","optionIds":["112233"]}}

信息泄露

前面说到了graphiql有强大的内省机制,可以获取所有可调用的接口和属性的字段。
一般直接可以用上述第三个Payload去获取。
或者是直接通过一些工具,比如自带的https://github.com/skevy/graphiql-app/releases/ 开发者调试用具,点击Docs可查看到相关的Query和Mutation方法
Docs
也可以直接关键字搜索
关键字搜索

权限控制

graphiql本身并不提供任何安全控制,所权限控制都依赖于后端Server的鉴权。此时开发如果安全意识较低的话,就会出现越权的问题。测试方法与常规 rest api类似

1
2
3
4
5
6
7
8
query($id: Int!){
findId(id: $id){
id
username
}
}

{"id": 111}

直接操控id参数就可以水平
操控id

2.获取敏感字段
因为graphiql的一大特点就是获取数据不多不少,依靠前端来控制数据裁剪。众所周知前端一切不可信。当某个对象存在敏感数据时,可以通过修改查询语句来获取敏感字段。

敏感字段

错误的字段限制

当开发人员意识到这是敏感字段时,因为安全意识不足采取deprecated注解将相关参数给废弃掉。
通过内省机制缺失无法查到
内省无法查看

通过includeDeprecated属性,依旧可以利用内省机制将废弃字段显示出来
废弃字段显示

并且数据依旧可以直接读取
废弃依旧读取

graphiql注入

本人感觉graphiql注入比较鸡肋。
1.只能控制相关的schema语句不能控制实际方法的逻辑。(不能凭空构造方法或修改方法逻辑,伪造了了相关的query、mutation语句也需要有后端的的Server方法接着,很显然没有)
2.场景有限,一般场景都可以通过抓包的方式去修改schema语句。而适用下图场景。无法直接抓包修改schema语句

GraphQL注入

dos风险

主要是因为对象相互嵌套引用引起的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

type Query{
findUser(id: Int): User
}

type User{
id: Int!
username: String
address: Adderss
}

type Adderss{
ip: String!
geographic: String!
user: User

}

如下当这么一直引用嵌套下去将引起dos

1
2
3
4
5
6
7
8
9
10
11
12
13
query{
findId(id:333){
id
username
address{
user{
address{
user.......直接引用下去
}
}
}
}
}

常规的Owasp top10依旧存在

不管解析器怎么处理,最终参数的传递就以是到了你本身自己实现的Service层代码逻辑。你没有对参数做任何过滤,带入SQL操作那必然会存在注入问题。其他的问题一样

总结

总的来说新技术给我带来便利同时,也给我们带了更多的风险。常规的漏洞没避免,引入了一些新的风险。回想起来当时一手SSH(Struts+Spring+Hibernate)就可以打遍天下。到后来SSH退出舞台出现SpringMVC->SpringBoot->SpringCloud->ServerLess还仅仅是Java系,网站架构也从传统架构(单点应用)—>分布式架构(以项目进行拆分)—>SOA架构—>微服务架构->服务网格。不断在掉发的路上。。。(不说了落后就要挨打,继续搬砖了)

参考:

https://graphql.cn/
https://xzfile.aliyuncs.com/upload/zcon/2018/7_%E6%94%BB%E5%87%BBGraphQL_phithon.pdf
https://segmentfault.com/a/1190000017766370

文章作者: Screw
文章链接: http://screwsec.com/2020/05/30/GraphQL%E5%AE%89%E5%85%A8%E5%88%9D%E6%8E%A2/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Screw's blog