游乐游手机版
首页/数据库/文章详情

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

时间:2026-07-02 09:03
SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须严格执行白名单校验,这是最基本的安全底线。

为什么在Spring Boot 3中动态拼接SQL语句会导致严重安全漏洞?

数据库只识别字符串本身,无法理解你的真正意图——你拼接出的那条SQL,攻击者完全可以将其改写成另一条恶意命令。事实就是这么简单。

动态拼接SQL的本质,是将用户输入当作可执行代码

Spring Boot 3本身并不生成SQL语句,但开发者一旦使用String.format()+进行拼接,或者采用MyBatis的${}语法,就相当于把用户的输入原封不动地塞进了SQL字符串中。此时数据库收到的不是“查询用户名为XXX的用户”,而是一段等待解析的完整文本——攻击者只需让这段文本变成SELECT * FROM users WHERE username = 'admin' OR '1'='1',查询条件就会完全失效。

  • 常见错误写法:@Select("SELECT * FROM users WHERE name = '${name}'")(MyBatis注解中使用${}
  • 危险场景:模糊搜索、动态排序、多条件组合查询
  • 后果不仅仅是报错,而是静默返回本不该看到的数据,甚至执行DROP TABLE等破坏操作

MyBatis-Plus的QueryWrapper并不能自动免疫注入

许多人误以为使用QueryWrapper就绝对安全,实际上它只是封装了参数绑定的逻辑——一旦混用apply()last()方法插入原始SQL片段,就会立刻回归到拼接模式,失去保护。

  • queryWrapper.eq("name", name) ✅ 安全(底层走PreparedStatement)
  • queryWrapper.apply("age > " + userAge) ❌ 危险(userAge为用户传入的字符串)
  • queryWrapper.last("ORDER BY " + sortField) ❌ 危险(sortField若未做白名单校验,可填入id; DROP TABLE users--

JPA的@Query注解同样要区分“编译期”与“运行期”

@Query默认使用命名参数(:name)是安全的;但一旦启用nativeQuery = true并在内部写上WHERE name = ?1,再配合String.valueOf()进行拼接传参,就等于绕过了框架的所有保护机制。

  • 安全写法:@Query("SELECT * FROM users WHERE name = :name")
  • 高危写法:@Query(value = "SELECT * FROM users WHERE name = ?1", nativeQuery = true) + repository.findByName("' OR 1=1 --")
  • 更隐蔽的陷阱:@Query中使用CONCAT('%', :keyword, '%')做模糊查询没有问题,但若写成"%"+ keyword +"%"再拼入SQL字符串,就会瞬间崩塌

真正防住注入的关键不在于“用了哪个框架”,而在于“谁在构建最终的SQL字符串”

Spring Boot 3的JDBC层、MyBatis、JPA都依赖于PreparedStatement,但它只会在参数值被单独传入时才生效。只要用户输入参与了SQL字符串的拼接过程,哪怕只拼了一个字段名、一个表名、一个ORDER BY子句,防御体系就已经被打破。

  • 表名、列名、排序方向等无法参数化的部分,必须进行白名单校验(例如sortField只允许"id""name""created_at"
  • 动态IN查询不能靠拼接?占位符的个数,而应使用Collection参数配合IN :ids的形式
  • 在日志中看到Preparing: SELECT * FROM users WHERE name = ?才代表真正进入了预编译流程;如果日志显示Preparing: SELECT * FROM users WHERE name = 'admin''--',说明已经中招

最容易被忽略的一点:安全边界不在Controller层,而在DAO层最后一行SQL构造语句。哪怕前面做了十层校验,只要最后那一行用了+${},整条安全链路就会瞬间坍塌。

来源:https://www.php.cn/faq/2749022.html
上一篇Redis 7.0 AOF持久化多文件管理及manifest元数据作用解析 下一篇如何用SQL编写按不同工作日统计员工出勤率
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN