当前位置: 首页 > news >正文

实战避坑:MyBatis中${}拼接如何优雅又安全?

“今天安全部门突然发来一封邮件,提示我提交的代码中存在 41 条安全漏洞,顿时脑子嗡的一声——开发不是在写业务代码吗?怎么一不小心就踩了‘雷’?”

这是我今天开发过程中遇到的真实经历。虽然平常对安全规范都有一定的意识,但在实际业务需求推动下,很多“临时处理方案”或“权宜之计”,往往埋下了深深的隐患。这次排查的众多漏洞中,有一个让我尤为警惕——SQL注入

1. SQL注入

SQL注入(SQL Injection)并不是一个陌生的词汇,它是OWASP列出的十大安全漏洞之一,也是最常见、危害最大的安全问题之一。

你可能会以为只有在拼接SQL字符串、使用Statement而不是PreparedStatement时才会出现注入问题。但实际上,它早已潜伏在你日常的MyBatis XML文件中,尤其是——${}的使用场景。

比如下面的代码:

<select id="selectByField" resultType="User">SELECT * FROM user WHERE ${fieldName} = #{value}
</select>

这段代码看似简洁优雅,支持动态传入查询字段,满足了业务需求的灵活性。然而,它同时也给了攻击者一个大开门:SQL注入的入口

2. ${}#{}区别

在 MyBatis 中,${}#{} 是两个经常被混淆的语法。它们的本质区别决定了是否安全:

在这里插入图片描述

以登录查询为例:

<select id="selectByUsernameAndPassword" resultType="User">SELECT * FROM user WHERE username = #{username} AND password = #{password}
</select>

这就是推荐做法:使用 #{},MyBatis 会自动处理参数预编译,杜绝注入风险。

而如果你使用了 ${}

<select id="selectByField" resultType="User">SELECT * FROM user WHERE ${fieldName} = #{value}
</select>

如果传入的 fieldName = "1=1; DROP TABLE user;",那么灾难就来了。

然而问题也来了:有些场景,不用 ${} 就做不了。

3. 使用 ${}的原因

虽然 ${} 是高危操作,但在某些业务场景中,它却不可或缺。

这就是它的诱惑:用起来太方便了。

以下是一些典型例子:

  • 动态拼接表名(如按照日期划分的日志表:log_202406, log_202407
  • 动态拼接字段名(如模糊查询时字段可选)
  • 动态排序字段(如 ORDER BY ${orderByField}
  • 动态设置更新字段(如在更新时决定字段名)

示例:

<select id="getLogFromTable" resultType="LogRecord">SELECT * FROM ${tableName} WHERE user_id = #{userId}
</select>

这段代码中,${tableName} 必须使用字符串拼接,否则无法替换成表名。

但代价是,你必须手动对 ${} 负责 —— 否则就像把数据库交到了攻击者手上。

因此,口诀是:

参数用 #{},结构用 ${},结构要防注入,四步护航最靠谱。

4. 四步曲

我们以一个常见场景为例来说明:根据不同表名动态查询数据

场景需求:

<select id="selectFromDynamicTable" resultType="User">SELECT * FROM ${tableName} WHERE id = #{id}
</select>

这个功能需求很明确,但也十分危险。我们可以使用以下四种方式逐步加强安全性:

1. 白名单过滤

List<String> allowTables = Arrays.asList("user", "user_backup", "user_log");
if (!allowTables.contains(tableName)) {throw new IllegalArgumentException("非法表名!");
}

2. 正则校验

if (!tableName.matches("^[a-zA-Z0-9_]+$")) {throw new IllegalArgumentException("非法表名格式!");
}

3. 表存在性校验

目标:在执行真正的SQL前,先验证这个表是否在当前数据库中存在。

1. DAO 接口
public interface MetaTableMapper {int countTableExists(@Param("tableName") String tableName);
}
2. Mapper XML
<select id="countTableExists" resultType="int">SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = #{tableName}
</select>
3. Service 层示例
@Autowired
private MetaTableMapper metaTableMapper;public void validateTableName(String tableName) {if (!tableName.matches("^[a-zA-Z0-9_]+$")) {throw new IllegalArgumentException("表名非法!");}int count = metaTableMapper.countTableExists(tableName);if (count == 0) {throw new IllegalArgumentException("表不存在!");}
}
4. 使用场景

在执行动态SQL前先校验表名:

public List<User> getUserFromDynamicTable(String tableName, Long userId) {validateTableName(tableName); // 防注入检查return userMapper.selectFromDynamicTable(tableName, userId);
}
5. MyBatis XML 中的动态表查询(结构拼接)
<select id="selectFromDynamicTable" resultType="User">SELECT * FROM ${tableName} WHERE id = #{userId}
</select>

结合表名正则 + 数据库表存在性校验,最大限度降低注入风险。

4. 使用 MyBatis 的 choose/when 标签

<select id="selectFromKnownTables" resultType="User">SELECT * FROM <choose><when test="tableName == 'user'">user</when><when test="tableName == 'user_backup'">user_backup</when><otherwise>invalid_table</otherwise></choose>WHERE id = #{id}
</select>

安全永远是开发中不可忽视的部分,而SQL注入是最容易被忽略却最危险的漏洞之一。尤其是在 MyBatis 的动态 SQL 场景下,我们常常为了灵活性使用 ${},却在不经意间打开了安全后门

http://www.lqws.cn/news/589195.html

相关文章:

  • Python 数据分析与机器学习入门 (二):NumPy 核心教程,玩转多维数组
  • Redis 集群
  • SQLite 安装使用教程
  • 长短期记忆网络(LSTM):让神经网络拥有 “持久记忆力” 的神奇魔法
  • 反射,枚举和lambda表达式
  • Bessel位势方程求解步骤
  • 国产化替换中政务行业通用的解决方案是什么?需要注意的事项有哪些?
  • 链表题解——移除链表元素【LeetCode】
  • 基于DSP的边缘检测与图像锐化算法研究与实现
  • ACE之ACE_NonBlocking_Connect_Handler问题分析
  • Vue防抖节流
  • localStorage 和 sessionStorage
  • ViT与CLIP:图像×文本 多模态读心术揭秘
  • python开篇介绍
  • 人工智能参与高考作文写作的实证研究
  • 大根堆加小根堆查找中位数o(N)时间复杂度
  • I/O I/O基本概念与基本I/O函数 6.30
  • CppCon 2018 学习:An allocator is a handle to a heap Lessons learned from std::pmr
  • 第八章IPv4、IPv6、ICMP、ARP、RARP
  • Mysql索引优化
  • 矩阵方程 线性代数
  • 深度学习04 卷积神经网络CNN
  • docker使用容器网络
  • SQL学习笔记5
  • python环境快速搭建
  • springboot中多个定时任务(@Scheduled)如何互不影响
  • jenkins集成sonarqube(使用token进行远程调用)
  • 查看CPU支持的指令集和特性
  • 项目:数据库应用系统开发:智能电商管理系统
  • 华为云Flexus+DeepSeek征文 | 基于华为云Flexus X实例部署Dify平台构建企业行政助手的可用性研究