在使用MyBatis逆向工程生成的Example查询模式时,很多开发者看到XML中存在${}
占位符就会担心SQL注入问题。但实际上,存在${}
并不等同于存在SQL注入风险。本文将详细分析何时会存在真正的注入风险。
存在SQL注入的两个关键前提
前提一:Criteria存在自定义扩展且接受外部输入
MyBatis Generator生成的标准Criteria类是相对安全的,但如果开发者添加了自定义扩展方法,就可能引入风险:
// 危险的自定义扩展示例
public class MTaxSbPayExample {public static class Criteria extends GeneratedCriteria {// ❌ 危险:允许用户直接传入SQL条件public Criteria andCustomCondition(String sqlCondition, Object value) {addCriterion(sqlCondition, value, "custom");return (Criteria) this;}// ❌ 危险:动态表名或列名public Criteria andDynamicColumn(String columnName, String operator, Object value) {addCriterion(columnName + " " + operator, value, columnName);return (Criteria) this;}}
}
如果用户输入直接传递给这些方法:
// 恶意输入示例
String userInput = "1=1; DROP TABLE users;--";
example.createCriteria().andCustomCondition(userInput, "someValue");
前提二:Example对象作为接口参数且orderByClause被不当赋值
另一个风险点是orderByClause
字段,因为ORDER BY子句通常需要使用${}
来处理列名:
<if test="orderByClause != null">order by ${orderByClause}
</if>
危险场景:
// ❌ 危险:直接将用户输入作为排序条件
@RestController
public class DataController {public List<MTaxSbPay> getData(@RequestParam String sortBy) {MTaxSbPayExample example = new MTaxSbPayExample();// 直接使用用户输入,存在注入风险example.setOrderByClause(sortBy); return mapper.selectByExample(example);}
}
恶意请求:
GET /getData?sortBy=id; DROP TABLE users;--
为什么存在${}
不一定有SQL注入风险
1. 硬编码的安全使用
在标准的MyBatis Generator实现中,${criterion.condition}
中的condition是硬编码的:
// 生成的安全方法
public Criteria andIdEqualTo(String value) {addCriterion("ID =", value, "id"); // "ID =" 是硬编码字符串return (Criteria) this;
}
对应的XML处理:
<when test="criterion.singleValue">and ${criterion.condition} #{criterion.value}
</when>
最终生成安全的SQL:
WHERE ID = ? -- 参数: 用户输入值
2. 参数分离的设计模式
MyBatis Generator采用了条件与参数分离的设计:
- 条件部分(
${criterion.condition}
):硬编码的操作符,如=
、>
、LIKE
- 参数部分(
#{criterion.value}
):用户输入,通过参数化查询处理
这种设计确保了即使使用${}
,也不会直接拼接用户输入的内容。
3. 用户输入路径受限
标准的Criteria类只提供预定义的方法:
// 用户只能通过这些安全的方法构建查询
criteria.andIdEqualTo(userInput); // 安全
criteria.andNameLike("%" + userInput + "%"); // 安全
criteria.andStatusIn(Arrays.asList("ACTIVE", "INACTIVE")); // 安全
用户无法直接控制criterion.condition
的值,只能影响criterion.value
,而后者是参数化处理的。
总结
MyBatis的${}
占位符本身并不等同于SQL注入漏洞。关键在于:
- 数据来源:是硬编码还是用户输入?
- 使用方式:是否进行了适当的验证和过滤?
- 设计模式:是否采用了参数分离的安全设计?
标准的MyBatis Generator生成的Example代码通常是安全的,真正的风险往往来自于开发者的不当扩展和使用。