公元二零二六年一月二十七日,基友突发消息,求助一个棘手的注入点,看来是遇到了硬茬。

既然地址发过来了,那就开干。
初步探测:识别与拦截
上手先试了单引号,发现没有效果,初步判断这可能是一个数字型注入。

接下来,尝试经典的逻辑判断 and 1=1,立刻触发了防护。

看来有WAF(Web应用防火墙)在守着。换用常见的注释符 --+ 试试,结果一样被拦。

这个WAF的配置有点东西,常规的FUZZ手法一时半会儿没过去。这里就不展开讲FUZZ了,有兴趣可以翻阅旧文。不过,测试中发现了一个关键点:内联注释 /*!...*/ 是有效的。

既然页面有数据库报错信息,那思路就很明确了——走报错注入。尝试经典的 updatexml 函数,果然被拦截了。

这种情况在意料之中。常见的报错函数大概率都在黑名单里,但我们可以拆解payload,配合内联注释来尝试绕过。
迂回策略:启用冷门函数
我换了一个相对冷门的 MySQL 函数 gtid_subset(),它要求两个参数都是有效的GTID格式,否则就会产生报错,正好可以利用。
gtid_subset(参数1,参数2)
直接使用 gtid_subset(1,1),成功执行并报错,说明这个函数没有被规则直接匹配。

接下来,尝试把参数1换成 database() 来获取库名,即 gtid_subset(database(),1),立刻被拦截。

那么WAF匹配的是什么呢?我们把括号去掉,写成 gtid_subset(database,1),发现请求通过了(虽然因为语法错误而执行失败)。

这说明WAF规则匹配的是 “完整函数名加括号” 这种模式,比如 database()。那么绕过思路就清晰了:只要让函数名和括号不直接连接在一起即可。内联注释正好可以充当这个“分隔符”。
将括号写在内联注释里:gtid_subset(database/*!*/(),1)。看,成功绕过并获取到了数据库名。

高级技巧:内联注释拆分与拼接
基于上面的原理,即使想使用 updatexml 这类常见函数,也是可以实现的。核心思路就是:将整个payload拆分,然后用多个内联注释拼接起来,打乱WAF的匹配模式。
目标是构造出等效于 updatexml(1,concat(0x7e,(database()),0x7e),1) 的语句。
首先,直接使用完整的 updatexml 函数会被拦截。

所以第一步,先把函数名和第一个括号拆开:updatexml/*!*/(1,concat...。

接下来,里面的 database() 也是一个完整函数,需要继续拆分。我们可以把 concat 函数及其参数也逐步拆解。最终构造的payload如下,其中 /*!*/ 起到了“粘合剂”和“分隔符”的双重作用:
and updatexml/*!*/(1,/*!concat*/(0x7e,/*!*/(/*!database*//*!*/(/*!*/)),0x7e),1)--+
为了更直观,可以写成多行形式:
and updatexml/*!*/(1,
/*!concat*/(0x7e,
/*!*/(/*!database*//*!*/(/*!*/)),
0x7e),1)--+

执行成功,通过XPATH语法错误回显了数据库信息。至此,证明漏洞存在,并且找到了绕过防护的方法。
总结与思考
这次绕过主要依赖两个点:一是使用了相对冷门的 gtid_subset() 函数作为突破口;二是充分利用了 MySQL 内联注释 /*!...*/ 的特性,将敏感的“函数名+括号”模式进行拆分和重组,从而规避了WAF的字符串匹配规则。
这提醒我们,在安全防御中,仅依赖黑名单规则匹配常见函数和关键词是远远不够的。攻击者总会找到各种奇怪的函数和奇妙的拼接方式。对于开发者而言,坚持使用参数化查询等根本性防御手段,比单纯依赖WAF更为重要。
在云栈社区的安全板块里,也经常能看到关于各种新型绕过手法的讨论。安全攻防是一场持续的动态博弈,保持学习才能跟上节奏。