直接拼接百分号本身不导致注入,真正致命的是伴随字符串拼接构造sql——只要未使用预编译,用户输入就被当作代码执行,攻击者可通过闭合引号、注入or 1=1等篡改逻辑。

直接拼接百分号(如 '%' + userInput + '%')本身不导致注入,真正致命的是——它几乎必然伴随字符串拼接构造 SQL,把用户输入裸奔塞进 SQL 字符串里。只要没走预编译,% 和 _ 就不是通配符,而是攻击者手里的撬棍。
为什么 '%' + userInput + '%' 出现在 SQL 拼接中就等于交出数据库钥匙
因为 JDBC 或数据库驱动根本分不清哪部分是代码、哪部分是数据。一旦你写:"WHERE name LIKE '%" + userInput + "%'",攻击者输入 admin' OR '1'='1,最终执行的就是:
WHERE name LIKE '%admin' OR '1'='1%'
单引号闭合、逻辑篡改、注释绕过——全成立。这不是“可能被利用”,而是“必然被利用”。
-
%和_是 SQL 标准通配符,但它们在字符串拼接上下文中,和'、--、;一样,都是可被语法解析的字符 - 哪怕只加了前后
%,只要userInput是拼进 SQL 字符串的,就等于把整个 SQL 解析权让渡给前端 -
mysqli_real_escape_string()或StringEscapeUtils.escapeSql()对%和_完全无效——它们不是 SQL 元字符,而是 LIKE 语义字符
CONCAT('%', ?, '%') 为什么安全,而 CONCAT('%', '${userInput}', '%') 依然危险
关键不在函数,而在参数传递机制:
-
CONCAT('%', ?, '%')中的?是 PreparedStatement 占位符,JDBC 把值当纯数据传入,数据库只执行函数,不解析内容 -
CONCAT('%', '${userInput}', '%')中的${}是 MyBatis 的文本替换,等同于 Java 字符串拼接,userInput在 SQL 编译前就被插进去了 - 同理,
"WHERE name LIKE CONCAT('%', '" + userInput + "', '%')"—— 拼接发生在 Java 层,userInput已经失控
空值、特殊字符、跨库兼容性这些坑比注入更常让你翻车
很多线上故障不是因为被黑,而是因为没想清楚边界场景:
-
userInput为null→"%" + null + "%"变成"%null%",查不到任何数据,且难以定位 -
userInput含%或_→ 用户搜a_c,结果匹配abc、axc,业务逻辑错乱(这不是注入,但体验崩坏) - MySQL 用
CONCAT(),PostgreSQL 要用'%' || ? || '%',Oracle 同样不认CONCAT;若硬写死函数,换库即报错 - 旧版 MySQL 对
%%(空输入拼出来)可能报 warning 或返回全表,暴露行为差异
最稳妥的路径从来不是在 SQL 里玩函数魔术,而是:Java 层判空 + trim + 长度限制 + 手动拼 "%" + keyword + "%",再用 #{pattern} 绑定。安全、清晰、可测、跨库——所有复杂点都收束在可控的一层里。











