RuoYi若依框架中MyBatis的‘坑’:除了${},还有哪些写法可能导致SQL注入?
RuoYi框架中MyBatis的SQL注入风险全景解析在Java企业级应用开发中ORM框架的安全使用一直是开发者需要重点关注的问题。RuoYi作为国内广泛使用的快速开发框架其安全实践对众多企业项目具有示范意义。本文将深入剖析MyBatis在RuoYi框架中可能存在的SQL注入风险点帮助开发者构建更安全的应用程序。1. MyBatis动态SQL的潜在风险MyBatis提供了强大的动态SQL功能但不当使用这些特性可能引入严重的安全漏洞。除了常见的${}直接拼接问题外开发者还需要警惕其他几种高危场景。1.1if标签中的隐式拼接许多开发者认为在if标签中使用${}是安全的但实际上这同样危险select idfindUsers resultTypeUser SELECT * FROM users WHERE 11 if testusername ! null AND username ${username} /if /select安全替代方案始终使用#{}预处理参数select idfindUsers resultTypeUser SELECT * FROM users WHERE 11 if testusername ! null AND username #{username} /if /select1.2choose/when条件分支中的风险条件分支中的参数拼接同样需要警惕select idgetUserList resultTypeUser SELECT * FROM users where choose when testorderBy name ORDER BY ${orderBy} /when when testorderBy id ORDER BY ${orderBy} /when /choose /where /select安全实践使用白名单验证private static final SetString ALLOWED_ORDER_COLUMNS Set.of(id, name, create_time); public ListUser getUserList(Param(orderBy) String orderBy) { if (!ALLOWED_ORDER_COLUMNS.contains(orderBy)) { orderBy id; } return userMapper.getUserList(orderBy); }2. 注解方式开发的隐患MyBatis支持注解方式编写SQL这种方式虽然简洁但也容易引入安全问题。2.1 Select注解中的字符串拼接Select(SELECT * FROM users WHERE username ${username}) User findByUsername(Param(username) String username);安全写法Select(SELECT * FROM users WHERE username #{username}) User findByUsername(Param(username) String username);2.2 Update/Insert注解的风险批量更新操作中尤其需要注意Update(UPDATE products SET price price * #{factor} WHERE category ${category}) int updatePrices(Param(factor) double factor, Param(category) String category);改进方案Update(UPDATE products SET price price * #{factor} WHERE category #{category}) int updatePrices(Param(factor) double factor, Param(category) String category);3. 特殊场景下的注入风险某些特定SQL语法结构更容易成为注入攻击的目标需要特别关注。3.1 LIKE查询的安全实现不安全的LIKE查询select idsearchUsers resultTypeUser SELECT * FROM users WHERE username LIKE %${keyword}% /select安全方案使用CONCAT函数与预处理参数select idsearchUsers resultTypeUser SELECT * FROM users WHERE username LIKE CONCAT(%, #{keyword}, %) /select3.2 IN语句的参数处理IN语句是SQL注入的高发区select idgetUsersByIds resultTypeUser SELECT * FROM users WHERE id IN (${ids}) /select安全实现使用MyBatis的foreach标签select idgetUsersByIds resultTypeUser SELECT * FROM users WHERE id IN foreach itemid collectionids open( separator, close) #{id} /foreach /select3.3 ORDER BY动态排序的安全处理动态排序是另一个高风险点Select(SELECT * FROM products ORDER BY ${sortField} ${sortOrder}) ListProduct getProductsSorted(Param(sortField) String sortField, Param(sortOrder) String sortOrder);防御方案使用白名单验证private static final SetString ALLOWED_SORT_FIELDS Set.of(price, create_time, sales); private static final SetString ALLOWED_ORDERS Set.of(ASC, DESC); public ListProduct getProductsSorted(String sortField, String sortOrder) { if (!ALLOWED_SORT_FIELDS.contains(sortField)) { sortField create_time; } if (!ALLOWED_ORDERS.contains(sortOrder.toUpperCase())) { sortOrder DESC; } return productMapper.getProductsSorted(sortField, sortOrder); }4. RuoYi框架中的安全增强实践基于RuoYi框架的实际开发经验我们总结了几种提升MyBatis使用安全性的有效方法。4.1 全局SQL过滤器的实现在RuoYi中可以通过自定义拦截器增强安全性Intercepts({ Signature(type StatementHandler.class, methodprepare, args{Connection.class, Integer.class}) }) public class SqlFilterInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler (StatementHandler) invocation.getTarget(); BoundSql boundSql handler.getBoundSql(); String sql boundSql.getSql(); // 检测SQL中的危险模式 if (sql.contains(${) !isSafeDynamicSql(sql)) { throw new RuntimeException(检测到不安全的SQL动态拼接); } return invocation.proceed(); } private boolean isSafeDynamicSql(String sql) { // 实现安全的白名单检查逻辑 } }4.2 MyBatis参数处理器扩展自定义类型处理器可以增加额外的安全校验public class SafeStringTypeHandler extends BaseTypeHandlerString { Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { // 执行参数安全检查 if (containsSqlInjectionPattern(parameter)) { throw new IllegalArgumentException(参数包含潜在SQL注入风险); } ps.setString(i, parameter); } // 其他必要的方法实现... }4.3 安全开发检查清单在RuoYi项目开发中建议遵循以下安全规范动态表名/列名处理必须使用白名单机制验证禁止直接拼接用户输入批量操作安全使用foreach标签而非字符串拼接限制批量操作的数据量日志记录规范记录SQL执行参数但需脱敏敏感数据监控异常的SQL模式第三方组件集成仔细审查自动生成的SQL语句禁用危险的默认配置5. 代码审计中的关键检查点针对使用MyBatis的项目进行安全审计时以下位置需要特别关注5.1 XML映射文件检查重点搜索所有${出现的位置检查动态SQL标签内的参数使用方式审查sql片段中的拼接逻辑危险模式示例sql idorderByClause ORDER BY ${orderBy} ${orderDirection} /sql5.2 注解接口检查重点检查所有Select、Update等注解中的SQL特别注意字符串拼接操作审查注解与XML混合使用的情况5.3 常见业务场景检查用户搜索功能LIKE查询数据导出功能动态列选择报表生成复杂动态SQL管理后台批量操作6. 安全编码的最佳实践结合RuoYi框架特点我们总结出以下安全编码建议6.1 分层防御策略表示层输入验证和过滤参数类型检查业务层业务逻辑验证操作权限检查持久层使用预处理语句最小权限原则6.2 MyBatis特定防护措施配置安全禁用不安全的MyBatis特性启用严格的SQL模式# application.yml中的安全配置 mybatis: configuration: safe-row-bounds-enabled: true safe-result-handler-enabled: true aggressive-lazy-loading: false工具集成使用SQL静态分析工具集成OWASP ESAPI进行输出编码6.3 持续安全实践代码审查将SQL注入检查纳入CR流程使用自动化工具扫描测试策略专门的SQL注入测试用例模糊测试动态SQL部分监控响应日志记录异常SQL模式建立快速响应机制在实际项目开发中我们曾遇到一个典型案例一个动态报表生成功能因为使用了${}拼接列名而导致注入漏洞。通过将其改造为使用严格的列名白名单验证不仅解决了安全问题还提高了系统的稳定性。这种从安全角度重构代码的过程往往还能发现并修复其他潜在的逻辑问题。