我的GraphQL安全学习之旅
GraphQL简介
GraphQL是Facebook的一个开源项目,定义了一种查询语言,用来代替传统的RESTful API。看到QL这样的字眼,很容易产生误解,以为是新的数据库查询语言,但其实GraphQL和数据库没有什么太大关系,GraphQL并不直接操作查询数据库,可以理解为传统的后端代码与数据库之间又多加了一层,这一层就是GraphQL.
传统的RESTful API是一个接口对应一个url,GraphQL是通过一个url接口改变请求所POST的查询参数,来达到多个api的查询效果。
GraphQL初窥
在查找GraphQL资料的过程中,都可以看到一个简单的demo,不过查询结果都是代码写死的,对于理解GraphQL和数据库之间的关系,并不是很有帮助,我写了一个简单和数据库连接的demo,python代码如下:
1 | from flask import Flask |
访问http://localhost:5000/ 即可看到demo。
1 | query{ |
查询结果如下:
查询出来的数据即UserModel.query.all()
的执行结果。但如果我要在浏览器实现只查询id=2的用户的信息是做不到,因为后端python代码里没有写,也就是说,只有代码里写了接口,定义了相应的schema,才能通过GraphQL查询出对应结果,所以并不是通过GraphQL就能查询获取数据库所有数据。
GraphQL的安全问题
如果看过p牛在先知大会上的分享——《攻击GraphQL》,会对GraphQL的安全问题有一个全面的认识。这里,在GraphQL安全问题研究上,我并没有新的发现,可以算是个人的学习笔记以及自己的一些理解。
让我们先回顾一下p牛总结的问题。
这ppt中提到的sql注入,个人感觉和GraphQL并不是太大关系,在不了解GraphQL的时候,一度以为看到用了GraphQL就以为找到了注入。如果代码的数据库查询都是像我上面那样的规范操作,自然是不存在sql注入的。若查询返回数据的方式使用的是原生语句查询数据库,一旦用户参数未经安全过滤进入数据库,还是存在sql注入。
至于csrf,GraphQL可以通过Mutation进行数据更改,若graphql接口未做安全校验,自然可以通过构造恶意html进行攻击。ppt中给的poc如下:
1 | <html> |
个人在挖掘src的过程中,碰到不少站点有用到GraphQL进行数据查询。发现的漏洞主要是信息泄露,和ddos拒绝服务。
发现的ddos漏洞在黑盒层面无法判断是否和GraphqQL有直接的关系,漏洞很简单,在graphql的query请求当中,有一个limit参数,当我将参数调成一个超大数字时,网站就卡死宕机了,无法判断后端哪一层崩溃了。
再回到信息泄露的问题上,个人觉得这才是大家在用GraphQL进行开发时,常忽略的地方。GraphQL可以使用schema和type查询可用的对象和对象的所有字段。
如下,以我上面的demo为例,查询可用对象
1 | query __schema{ |
得到User,再查询User的字段。
1 | { __type(name: "User") { |
在实际挖洞过程中,就可判断是否存在敏感字段,如用户的password,phone,address等。再做进一步的漏洞挖掘。
但有时目标网站可能存在几十个对象,一个一个查找出具体的字段显示是太麻烦了,是否可以编写工具进行批量查询呢?本想自己写一下,但发现已经有大佬给我们做好了。
项目在git地址为https://github.com/doyensec/graph-ql
,通过这个工具可以详细列出网站的对象还有对应字段。分析他的python源码可以得到这两个payload。
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}}}}}}}} |
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}}}} |
测试效果如下,只需一次请求就可列出对象和字段。
工具的效果如下:
在实际使用过程中,常常需要修改脚本,修改post的参数名称以及返回结果的参数名,使之与实际请求结果相对应,若有登陆态校验,则还需要添加cookie,这一块脚本有待改进,有时间就造一个顺手的轮子。
至于嵌套查询导致ddos,在实际挖掘中暂时没有碰到,具体介绍可以参考freebuf的《GraphQL安全指北》这篇文章。
已经造完了
实现很简单,把上一个项目生成html的函数抠出来,再自己重写一个发送payload的请求的函数,避免修改请求头和cookie的麻烦,至于原来项目硬编码取返回包data参数导致有些情况下无法使用的问题,我添加了一个-d参数来解决,即当返回结果不是data参数时,可以指定参数名。
项目地址:https://github.com/deenrookie/x-graphql