MyBatis Example模式SQL注入风险 - 实践

在使用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代码通常是安全的,真正的风险往往来自于开发者的不当扩展和使用。

posted @ 2025-09-12 09:46  yfceshi  阅读(16)  评论(0)    收藏  举报