mybatis中sql注入主因是误用${}拼接用户输入,应严格限制${}仅用于白名单校验的动态表名/列名,所有用户参数必须用#{};xml中避免${}用于where/like等上下文,禁用拼接用户数据,排查第三方工具绕过预编译机制。

MyBatis 的 ${} 和 #{} 混用是主因
SSM(Spring + SpringMVC + MyBatis)中,SQL注入复现最直接的路径就是误用 ${}。它不做任何转义,原样拼接进 SQL 字符串;而 #{} 会走预编译参数占位(PreparedStatement),由 JDBC 驱动处理类型和转义。
常见错误场景:动态表名、列名、ORDER BY 字段必须用 ${},但开发者常把它用于用户可控输入,比如:WHERE username = '${username}'。一旦 username 是 admin' --,就直接落地成可执行恶意语句。
实操建议:
- 所有用户输入字段(登录名、搜索关键词、ID、邮箱等)一律用
#{},禁止${} - 真需动态列/表名时,必须白名单校验——比如排序字段只允许
["id", "name", "created_at"],再映射到${sortField} - MyBatis-Plus 的
QueryWrapper默认安全,但若调用eq("username", username)后又手写apply(" AND status = ${status}"),${status}就立刻失守
Spring MVC 参数绑定未设类型或校验边界
当 Controller 方法接收 String id 或 Object param,且没配 @Valid 或自定义 @InitBinder 过滤,攻击者就能把 1' UNION SELECT ... 当作普通字符串传进来,后续若被 ${} 拼接或反射进 SQL,漏洞即触发。
典型风险点:
-
@RequestParam String keyword直接透传给 MyBatis 的${keyword} - 使用
Map<string object></string>接收全部参数,再通过反射拼 SQL(常见于通用导出接口) - JSON 请求体里含
{"order": "id ASC; DROP TABLE users;"},没做字段白名单就存进 DB 或拼进查询
XML 映射文件中硬编码 SQL + 外部传参失控
MyBatis 的 XML 文件里若出现 <where><if test="keyword != null">AND name LIKE '%${keyword}%</if></where>,这就是高危信号。即使加了 LIKE,${keyword} 仍可注入 %' OR '1'='1,最终变成 AND name LIKE '%' OR '1'='1%'。
更隐蔽的是注释残留或条件嵌套错位,比如:
AND status = ${status}
<!-- 这里本该用 #{},但复制粘贴漏改 -->
实操建议:
- 全局 grep
\$\{,逐个确认是否真需动态拼接;95% 场景应替换为#{} - LIKE 模糊查询统一用
#{}+ Java 层拼"%" + keyword + "%",避免在 SQL 里写'%'${keyword}'%' - 禁用
<bind></bind>标签拼接用户输入,如<bind name="pattern" value="'%' + keyword + '%'"></bind>若keyword不洁,pattern仍是污染源
第三方工具或自定义拦截器绕过 MyBatis 安全机制
某些国产 ORM 工具、分页插件(如早期 PageHelper 低版本)、或团队自研的“通用 DAO”封装,会在 MyBatis 执行前二次拼接 SQL。如果它们内部用了 String.format 或 StringBuilder.append 拼接用户参数,就等于在预编译之外开了后门。
Metabase CVE-2026-72898 就是典型:它不走 MyBatis,而是用 JdbcTemplate + String.format 构造查询,email 参数直插进 SQL,零防护。
排查要点:
- 检查项目中是否有非标准 DAO 调用,特别是带
execute、update、query字样的自定义方法 - 翻查 pom.xml 是否引入了未经审计的分页/多租户/动态数据源组件,确认其 SQL 构造逻辑
- 对所有
JdbcTemplate或NamedParameterJdbcTemplate调用,确保参数全走Object[]或Map<string></string>,绝不用String.format(sql, param)
最易被忽略的是日志脱敏开关——有些拦截器在 debug 日志里把完整 SQL 打出来,若含 ${} 拼接,日志文件本身就成了注入 payload 的存储地。











