☰
MyBatis #{} 和 ${},很多开发踩坑的 SQL 注入根源
2026/9/29 7:16:38 网站建设 项目流程

MyBatis #{} 和 ${},很多开发踩坑的 SQL 注入根源

摘要

MyBatis 是 Java 项目中最常用的持久层框架,很多开发同学误以为只要使用 MyBatis 就天然免疫 SQL 注入。但在大量代码审计和 SRC 漏洞挖掘中发现,绝大多数 Java 业务系统的 SQL 注入漏洞,根源就是混淆了#{}和${}。#{}使用预编译,安全;${}直接字符串拼接,极易产生注入。本文从底层原理、代码示例、实战漏洞场景、修复方式、代码审计要点一次性讲清楚,帮助开发避开坑,同时给白帽提供审计思路。本文内容仅用于代码审计、授权安全测试学习,禁止用于未授权系统渗透。

免责声明:本文技术内容仅用于网络安全学习、代码审计、授权范围内安全评估。未经目标系统所有者书面许可,禁止对任何系统进行探测、渗透测试。违规操作带来的全部法律责任,由操作者自行承担。

0x00 前言

做代码审计的时候,经常看到这样的场景:
开发:“我们项目用 MyBatis,不会有 SQL 注入。”
打开 Mapper 文件,一眼就看到:

select * from order where orderId = ${orderId}

一个${}直接把整个防护废掉,SQL 注入直接诞生。

很多初级开发分不清两者差异,只知道#{}是传参,${}也拿来接收前端可控参数。
一句话核心结论:
✅#{}:预编译、参数绑定,防止 SQL 注入
❌${}:直接字符串替换拼接,存在 SQL 注入风险

0x01 底层原理拆解

1.1 #{} 的执行原理

#{}是 MyBatis 的参数占位符,底层使用 JDBCPreparedStatement预编译机制。
MyBatis 会把 SQL 模板提前发给数据库,SQL 结构固定不变,#{}对应的位置变成?占位符。用户传入的参数,只会作为参数值绑定进去,不会参与 SQL 语法解析。

示例 Mapper:

<select id="getOrderById" resultType="Order"> select id,order_no,status from t_order where order_id = #{orderId} </select>

底层生成的 SQL 模板:

select id,order_no,status from t_order where order_id = ?

前端传入参数1' or 1=1,这个完整字符串只会被当成一个值,不会破坏原有 SQL 语法,数据库只会去匹配order_id等于字符串1' or 1=1这条记录,不会执行注入逻辑。

就算传入单引号,数据库只会当成普通字符串内容,不会闭合 SQL 语句,天然防御 SQL 注入。

1.2 ${} 的执行原理

${}是字符串直接替换。MyBatis 不会做预编译,只是简单把变量的值原封不动替换到 SQL 语句中,再把完整拼接后的 SQL 交给数据库执行。

Mapper 示例:

<select id="getOrderById" resultType="Order"> select id,order_no,status from t_order where order_id = ${orderId} </select>

前端传入参数:1' or '1'='1
替换之后最终交给数据库执行的 SQL 变成:

select id,order_no,status from t_order where order_id = 1' or '1'='1

SQL 语法直接被破坏,注入语句成功执行,这就是 SQL 注入。

关键点:({} 不会做任何转义,输入什么就拼什么。只要){} 接收的参数是前端可控,就存在注入风险。

0x02 什么时候才允许使用 ${}?

很多人看完直接把所有${}全部删掉,但${}并不是完全不能用。
${} 适用场景:传入的内容是开发可控的固定值,不是用户前端输入。

常见合法场景:

  1. 动态排序,动态传入排序字段、排序方向(order by 后的字段名)
order by ${sortField} ${sortOrder}

这里不能用#{},因为 order by 后面不能使用占位符,数据库不支持。
⚠️风险:如果 sortField、sortOrder 参数来自前端用户可控输入,就会产生注入。
正确做法:后端做白名单校验,只允许预设的字段,不在白名单直接拒绝。

  1. 动态表名,业务需要切换不同分表
select * from ${tableName} where id=#{id}

tableName 必须后端代码白名单校验,不允许直接接收前端传入值。

一句话总结:${} 只用来拼接 SQL 语法结构(表名、排序字段),绝对不能用来接收查询条件的值。

0x03 实战漏洞案例:SRC 中遇到的 ${} 注入漏洞

漏洞场景

业务接口,前端传入 orderId 参数,后端 Mapper 直接使用 ${orderId} 拼接查询。
Mapper 代码:

<select id="getOrderInfo" resultType="Order"> select order_no,create_time from t_order where order_id = ${orderId} </select>

接口直接接收用户传入 orderId,没有任何校验。

Payload:

orderId=1' union select 1,version()--+

经过 MyBatis 替换后 SQL:

select order_no,create_time from t_order where order_id = 1' union select 1,version()--+

直接触发联合查询注入。
虽然项目使用 MyBatis,但是${}直接造成高危 SQL 注入。

很多开发以为 MyBatis 自带防护,忽略了 ${} 的字符串拼接,这是 Java 项目里最常见的注入根源。

0x04 容易踩坑的几个误区

误区 1:${} 会自动转义单引号

❌ 错误。${}不会做任何转义,原样替换。
#{}才会在预编译阶段处理特殊字符。

误区 2:MyBatis 项目就一定不会 SQL 注入

❌ 错误。MyBatis 只是框架,安全取决于写法。
只要代码使用${}接收用户可控参数,就会存在注入风险。

误区 3:加了 WAF,就算用 ${} 也没事

❌ 错误。WAF 只是外层防护,可以被绕过。代码层面的拼接漏洞才是根源,WAF 一旦被突破漏洞直接利用。

误区 4:使用 ${} 的时候,前端传数字就安全

❌ 错误。就算业务参数预期是数字,攻击者抓包修改参数,传入带单引号的字符串,依然触发注入。
不要依靠业务预期的参数类型来防护。

0x05 ${} 场景的安全处理方案(动态排序 / 动态表名)

当业务确实必须使用${}(order by、动态表名),不能直接接收前端参数,必须后端做白名单校验。

Java 伪代码示例,动态排序参数校验:

// 白名单,只允许预设的字段 List<String> allowField = Arrays.asList("id","create_time","order_no"); if(!allowField.contains(sortField)){ // 不在白名单,直接抛出异常拒绝查询 throw new Exception("非法排序字段"); }

禁止直接把前端传来的 sortField 直接丢进 Mapper 的 ${sortField}。

禁止使用黑名单过滤,黑名单可以被各种方式绕过,白名单才可靠。

0x06 代码审计快速排查技巧(白帽审计 Mapper 文件)

快速定位 MyBatis 注入风险点,审计 Mapper xml 文件:

  1. 搜索${,找出所有${}变量;
  2. 向上追溯参数来源:判断变量是否可以由前端用户控制;
  3. 如果前端可控,并且没有后端白名单校验 → 判定存在 SQL 注入风险;
  4. 如果变量是后端硬编码、固定枚举值,则风险较低。

审计优先级:
${用户可控参数}→ 高危
#{任意参数}→ 无注入风险

小提示:很多项目使用 MyBatis-Plus,MP 同样遵循这个逻辑,自定义 XML 里写的 ${} 依旧存在注入。

0x07 漏洞修复方案

  1. 优先替换 ${} 为 #{},查询条件全部使用预编译占位符,从根源解决注入;
  2. 业务场景必须保留${}(动态表名、排序字段),后端增加严格白名单校验,非法参数直接拦截,不进入 SQL 查询;
  3. 输入校验:对业务参数增加格式校验,例如订单号只能是数字;
  4. 数据库账号最小权限,限制业务账号访问 information_schema;
  5. 代码审计,扫描项目中所有${},梳理参数来源;
  6. 上线前安全测试,重点测试使用 ${} 的接口。

0x08 总结

  1. #{}底层预编译参数绑定,防止 SQL 注入,查询条件优先使用;
  2. ${}直接字符串替换拼接 SQL,用户可控参数传入 ${} 就极易产生注入;
  3. ${}不是不能用,仅适用于拼接表名、排序字段这类 SQL 结构,且必须后端白名单校验,不能直接接收前端输入;
  4. 使用 MyBatis 不等于免疫 SQL 注入,框架不兜底,代码写法决定安全;
  5. 代码审计时,检索${快速定位风险点,是 Java Web 挖 SQL 注入的经典思路。

最后

关于网络安全技术储备

学好网络安全不论是就业还是做副业赚钱都不错,但要学会网络安全还是要有一个学习规划。最后大家分享一份全套的网络安全学习资料,给那些想学习网络安全的小伙伴们一点帮助!

对于0基础小白入门:

如果你是零基础小白,想快速入门网络安全是可以考虑的。

一方面是学习时间相对较短,学习内容更全面更集中。

二方面是可以找到适合自己的学习方案

包括:网安成长学习路线图、SRC&黑客文档、护网行动、黑客必读书单、面试题、学习视频等教程。带你从零基础系统性的学好网络安全!

需要的可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

👉1.成长路线图&学习规划👈
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

👉2.网安入门到进阶视频教程👈
很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩。(全套教程文末领取哈)

👉3.SRC&黑客文档👈
大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录

SRC技术文籍:

黑客资料由于是敏感资源,这里不能直接展示哦!(全套教程文末领取哈)

👉4.护网行动资料👈
其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!

👉5.黑客必读书单👈

随着互联网技术的飞速发展,网络安全已经成为了当今科技领域的一大热点。这些SQL注入、CCNA、Web渗透、Linux服务器等,以其强大的语言理解和防御能力,正在守护着我们网络世界。 那以下这些PDF籍就是非常不错的学习资源。

👉6.网络安全岗面试题合集👈
当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。

这份完整版的网络安全学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询