GraphQl Injection
前段时间试着打了几场ctftime上的比赛,遇到了很多以前没有看到过的东西,其中刚拿到最蒙逼的就是graphql。比赛出现的频次还挺高,所以研究研究记录一下。
涉及到的题目有:HITB CTF 2017-Blog SEC-T 2017-Dark Market
GraphQl是什么?
GraphQL 可以看做一条以通用基础业务数据模型为基础、将后端服务和前端页面联系在一起的东西。其实就是放在页面数据和服务端中间做数据处理的东西。
GraphQl能实现的最重要的功能,就是在一个数据接口,执行不同的查询,返回不同的数据,比如以下例子:
a 查询对应的响应:
{
"user" : {
"id": 3500401,
"name": "Jing Chen",
"isViewerFriend": true
}
}
b 查询对应的响应:
{
"user" : {
"name": "Jing Chen",
"profilePicture": {
"uri": "http: //someurl.cdn/pic.jpg",、
"width": 50,
"height": 50
}
}
}
这两个查询是传入同一个数据接口的,只需要改变查询内容,前端就能定制服务器返回的响应内容,这就是 GraphQL 的客户端指定查询(Client Specified Queries)。
如何使用
在这里可以学习GraphQl的具体操作:
题目中的操作
HITB CTF 2017-Blog
这道题在查询时可以看到这个传参query={ itemsForAuthor(id:"aYT0x") { id title } }
其实就是
query=
{
itemsForAuthor(id:"aYT0x") {
id
title
}
}
这里的”aYT0x”其中的”YT0x”base64一下就是a=1,因此构建一个原来一个不存在的id,比如query={ itemsForAuthor(id:"aYT0xaaaa") { id title } }
就会产生报错
{"errors":[{"message":"(sqlite3.ProgrammingError) You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings. [SQL: u'SELECT author.id AS author_id, author.name AS author_name \\nFROM author \\nWHERE author.id = ?'] [parameters: ('1i\\xa6\\x9a',)]","locations":[{"column":4,"line":1}]}],"data":{"itemsForAuthor":null}}
报错发现是sqLite数据库,在base64编码一下,就可以愉快的进行注入了。
SEC-T 2017-Dark Market
个人感觉这道题对GraqhQl的考察要透彻一点,毕竟上道题没有表现出客户端指定查询(Client Specified Queries),其实还是一个用了GraphQl的普通注入题,后面这道感觉思路要更厉害一点。(导致即使查询可控变多了,虽然对GraphQl有一点了解,但是仍然没有做出来😂😂😂)
这道题是一道商店的题,开头有100块,但是要买110块的东西。一般拿到这种买东西的题,一般就是条件竞争、注入得高级账号、进制转换等方法。
但是做这道题的时候蒙逼了,只有一个没有注入漏洞的登录页面、一个买东西的页面、一个可以根据订货码来查询所定货物的页面,其中最后一个页面用了GraphyQl。这道题里面根本没有可以加钱的功能,也没有拿到高级账号的办法。
唯一突破口是在查询订货码的地方,可以自定义查询,可以查询订货码、id、商品描述等商品信息,各种猜解发现查询不出什么敏感信息,后来只有放弃。
后来看到一个老外的writeup,简直666.
这里对里面的一些东西讲解一下。
首先这道题是可以客户端指定查询的,但是我不知道有什么查询姿势,大佬用了一个很厉害的payload:
这个payload不是专门对于题目环境弄的,而是可以在各个GraphQl环境中使用,(比如上面那个GraphQl教程的环境),把各个query、mutation、subscription查询出来。
writeup中查询出的database_schema.json
在这个database_schema.json中,可以看到根据订单号和userid删除订单,退回花的钱到现有账户,这样再注册一个账户进行之前账户的退款操作,这样总钱数就可以大于110,买到flag了。
那么如何从database_schema.json中找到自己想要的query和mutation呢,请看下面。
database_schema.json剖析
首先拿到1540行的东西肯定是蒙蔽的,我们先从头看起。
首先我们知道,在GraphQl中定义操作,有query、mutation等,这些在文件最前面有定义:
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": {
"name": "MyMutations"
},
这里规定了queryType和mutationType的name,全局搜索这个name搜索,既可看到Query的定义。
这个文件中,就在这一段的下面就是对Query的定义:
"types": [
{
"kind": "OBJECT",
"name": "Query",
"description": null,
"fields": [
.....
]
}
]
在fields中就是查询方法,findProduct和findOrder。
这里拿findProduct来讲一下:
{
"name": "findProduct",
"description": null,
"args": [
{
"name": "productId",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Products",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
这里除了可以看出是通过参数productId查询之外,主要是在type中:
"type": {
"kind": "OBJECT",
"name": "Products",
"ofType": null
},
这里的"name": "Products"
规定了可以查询出来的东西,全局搜索Products可以找到:
{
"kind": "OBJECT",
"name": "Products",
"description": null,
"fields": [
.....
]
}
这部分在69行到149行, "fields":[.....]
里面就是可以查询的参数信息,有id、name、desc、image、price。用同样的方法就可以看到在findOrder中可以查询id、productId、userId、orderId、user (223行到303行)。
这里的user比较有用:
{
"name": "user",
"description": null,
"args": [],
"type": {
"kind": "OBJECT",
"name": "Users",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
可以看到type里又有一个 "name": "Users"
,说明这里查询user之后里面可以嵌套一个子查询,子查询的定义可以通过寻找"name": "Users"
的查询方法找到。
{
"kind": "OBJECT",
"name": "Users",
"description": null,
"fields": [
....
]
}
这里fields里面定义了可以查询id、name等。所以可以这样定义payload:
query= {
findOrder (orderId: "9fcdcedb-f15f-4759-b6d4-dfbe2419a0fc")
{
user
{
id
name
}
}
}
后面就查找一下deleteOrder
这个Mutation,方法近似,简单很多。具体payload可以看上面大佬的的writeup链接,这里就不再赘述了。