1 Lab: Accessing private GraphQL posts
打开 burp 刷新页面,在 Proxy History 中发现 /graphql/v1
这样的 endpoint,应该是跟 GraphQL 有关的 API。
点开报文,发现查询是关于获取所有博客 Summaries 的,毕竟是刷新主页后产生的报文。查看响应报文,发现唯独没有 id 为 3 的博客内容,猜测 flag 可能藏在 id 为 3 的博客中。
这里我起猛了,竟然直接给 getAllBlogPosts
传了参数,不出意外地报错了。
查看 Proxy History,发现访问博客时会发送一个 GET 请求,参数为 postId
,自然想到直接修改 postId=3
,通过 IDOR 访问博客。结果提示 404.
继续分析 Proxy History,发现访问某篇博客时,GET 请求后会紧跟一个 POST /graphql/vi
通过 GraphQL API 查询具体博客内容。而当我们直接发送 GET /post?postId=3
的请求时,服务端提示 404 之后,就没有使用 GraphQL API 做进一步查询了。
找到对应的查询语法,直接发送 POST 请求调用 GraphQL API 获取博客内容。
修改参数发送后,发现就一篇普通博客呀,有 password 嘛?
找了半年没找到,查下 schema 先。
报错了,删掉红框那三行再试下。又进行了一些其它的调试,成功拿到 BlogPost 的所有字段,其中有个 postPassword,seems juicy.
重新请求 getBlogPost
拿到 password.
2 Lab: Accidental exposure of private GraphQL fields
第一步还是找 GraphQL endpoints。
Burp Proxy History 里面找到一个 POST /graphql/v1
的 endpoints,查询语句是获取所有博客简介等相关信息的。
第二步,通过 Introspection 功能找所有的 schema。选择 “GraphQL”,右键报文(随便一个位置),选 GraphQL -> Set introspection query,即可快速生成查询语句。
此外还可以借助一些工具,如 Burp 插件 InQL(专门用来分析 GraphQL 的)提高我们分析效率。
查看 InQL 的扫描结果,发现查询 “getUser” 有点 juicy。
简单修改下查询语句,一般 admin 的 id 不是 1 就是 0(当然,也不绝对,运气),成功拿到 admin 密码。
使用管理员账号登录。
根据题干,删除用户 Carlos,结束。
3 Lab: Finding a hidden GraphQL endpoint
题目要求跟 lab 2 一样,用户管理模块使用 GraphQL 实现,通过 IDOR 登录 admin 用户,删除用户 “Carlos”.
GraphQL endpoint 被隐藏了,一个一个试好麻烦!!使用 GraphQL 通过用查询语句 {"query": "query{__typename}"}
[!quote] GraphQL services will often respond to any non-GraphQL request with a “query not present” or similar error.
直接 Intruder 爆破。
啥也没爆出来
尝试 POST + json
就一个 /api
回复了 “Query not present”,剩下全是 404。PS: 貌似是我学艺不精,提示 “Query not present” 大概率就是 GraphQL endpoint 了?
我知道问题在哪了,我直接把 {"query": "query{__typename}"}
url 编码后就用 GET 传给 /api
了,其实应该写成:query=query{__typename}
。(这里稍微借鉴了下 solution)
找到 endpoint 之后,就该找 schema 信息了。BUT,Introspection 被拦了。
调试后发现,并不是 Introspection 查询语句被拦了,而是直接用 burp 的 GrapQL 生成语句后,请求报文貌似并不对。(好吧,请求报文中就多了几个引号,貌似并不影响,还是 __schema
被拦了)
之后的 GET 请求是这样的。
它把 GraphQL 的语句使用 URL 编码后,直接加在了参数里面。
修改成 ?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D
成功。
Introspection 查询语句(oneliner)
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}}}}}}}} |
服务器判断 introspection 查询的逻辑貌似是正则匹配 __schema{
,在 __schema
后加个换行,绕过。
成功。
保存响应中的 introspection schema 到一个 JSON 文件,之后使用 InQL 打开,简单分析下。
或者直接右键 introspection schema 响应报文 -> GraphQL -> “Save GraphQL to site map”
尝试查询后,发现用户 “Carlos” 对应的 id 是 3,由于没法查询到密码,就不能登录 admin panel 然后删除用户了。
好在有个允许删除用户的 mutation,输入 id 改为 3,成功删除用户 “Carlos”.
4 Lab: Bypassing GraphQL brute force protections
-> 传送门:暴力破解用户 “Carlos” 的密码,字典已给出。
首先定位 GraphQL endpoint,
对 mutation 语句稍微做下修改,使用别名在要给 HTTP 报文中进行多次查询(即“暴力破解”)从而获取到正确的密码。
题目中也给出了对应的字典,我们将它保存在 txt 文件中,再写个 python 脚本批量生成查询语句。
1 | result = "" |
结果如下:
搞完已经没法访问了,我好菜,呜呜。
修改 GraphQL 语句,发送。全局搜索为 true 的结果,对应的密码是 “zxcvbnm”
登录,成功!
官方 solution 里面貌似用的 js 脚本,都大差不差。
1 | copy(`123456,password,12345678,qwerty,123456789,12345,1234,111111,1234567,dragon,123123,baseball,abc123,football,monkey,letmein,shadow,master,666666,qwertyuiop,123321,mustang,1234567890,michael,654321,superman,1qaz2wsx,7777777,121212,000000,qazwsx,123qwe,killer,trustno1,jordan,jennifer,zxcvbnm,asdfgh,hunter,buster,soccer,harley,batman,andrew,tigger,sunshine,iloveyou,2000,charlie,robert,thomas,hockey,ranger,daniel,starwars,klaster,112233,george,computer,michelle,jessica,pepper,1111,zxcvbn,555555,11111111,131313,freedom,777777,pass,maggie,159753,aaaaaa,ginger,princess,joshua,cheese,amanda,summer,love,ashley,nicole,chelsea,biteme,matthew,access,yankees,987654321,dallas,austin,thunder,taylor,matrix,mobilemail,mom,monitor,monitoring,montana,moon,moscow`.split(',').map((element,index)=>` |
5 Lab: Performing CSRF exploits over GraphQL
-> 传送门:用户修改邮箱地址功能使用了 GraphQL,且存在 CSRF。
修改邮箱的报文如下所示:
表单不支持 application/json
编码类型,所以无法直接构造 CSRF poc,这也是使用 application/json
抵御 CSRF 攻击的原因。
要利用 CSRF,请求要么使用 GET 方法,要么使用 POST + x-www-form-urlencoded。不过 /graphql/v1
貌似支持 POST + x-www-form-urlencoded。
CSRF POC
1 | <html> |
问题
奇怪了,为啥 “View exploit” 可以,”Deliver exploit to victim” 后就没反应了?
尼玛的,我又憨了!邮件地址应该改成一个从没用过的呀。我在 Repeater 里面先测试了一个邮箱,然后直接写在了 POC 里面,怪不得一直没反应。
修改邮件地址后,lab 解决!
GraphQL API 的所有 lab 都做完啦!!!完结撒花。