like注入防护关键在于参数化而非转义,mybatis用#{}、pdo用prepare+bindvalue、sqlalchemy用+拼接或显式占位符,禁用${}和字符串拼接,配合白名单与通配符控制。

LIKE 查询本身不安全,关键在怎么用。只要用户输入被拼进 SQL 字符串(比如 "%".$q."%"),就等于把数据库的执行权交给了攻击者——参数化没做对,转义再勤也没用。
MyBatis 里 ${} 和 #{} 别混用
这是 Java 项目中最常见的翻车点:${username} 是字符串替换,等同于直接拼接;#{username} 才走 PreparedStatement 预编译。
- 错误写法:
WHERE name LIKE '%${q}%'→ 输入admin%' OR '1'='1直接触发全表查询 - 正确写法一(推荐):
WHERE name LIKE CONCAT('%', #{q}, '%')→ % 由数据库拼,#{q}是受保护参数 - 正确写法二:
WHERE name LIKE #{pattern}→ 后端提前拼好"%" + q + "%"再传入,确保#{pattern}是完整字符串值 - 严禁在
<script></script>标签里又手动拼 SQL,那会绕过#{}的防护
PHP PDO 必须用 prepare() + bindValue()
别信 mysql_real_escape_string()(已废弃),也别用 addslashes() 处理 LIKE 场景——它们不处理 ESCAPE 逻辑,且宽字节下可被绕过。
- 危险写法:
"WHERE name LIKE '%".$q."%'"→ 无论加多少过滤都拦不住' OR 1=1 -- - 安全写法:
$stmt = $pdo->prepare("SELECT * FROM user WHERE name LIKE ? ESCAPE ''"); $safe_q = str_replace(['%', '_'], ['%', '_'], $q); $stmt->bindValue(1, "%{$safe_q}%", PDO::PARAM_STR); - 注意:MySQL 的
ESCAPE ''要求你显式转义输入中的%、_、\,不能只靠绑定参数
Django/Flask SQLAlchemy 的 like() 不是万能的
filter(title__icontains=q) 是安全的,但如果你手写 f-string 或 format() 拼 SQL,就等于放弃防线。
- 错误写法:
Article.query.filter(Article.title.like(f'%{q}%'))→ f-string 在 Python 层拼,SQL 还没生成就已污染 - 正确写法:
Article.query.filter(Article.title.like('%' + q + '%'))→ + 操作在 Python 层,SQL 参数仍由 SQLAlchemy 绑定 - 更可控方式:
session.execute(text("SELECT * FROM article WHERE title LIKE :kw"), {"kw": f"%{q}%"})→ 显式占位符 + 字典传参 - 如果业务允许,优先用
__search(PostgreSQL)或match()(MySQL FULLTEXT),它们天然限制 UNION、子查询类注入路径
通配符语义风险比语法注入更隐蔽
即使参数化做到位了,LIKE 的通配符逻辑仍在生效。用户搜 admin% 可能匹配到 administrator,甚至被用于布尔盲注构造。
- 前导
%("%".$q."%")暴露面最大,尽量改用后缀匹配($q."%")或前缀匹配("%".$q) - 对中文搜索,建议白名单校验:
preg_match('/^[x{4e00}-x{9fa5}a-zA-Z0-9s.,!?-]+$/u', $q),拒绝%、_、'等字符 - 错误响应要收敛:不要返回 MySQL Error 1064,也不要让响应时间随输入长度明显变化(防时间盲注)
真正卡住 LIKE 注入的,从来不是“怎么转义”,而是“压根不让它进 SQL 字符串”。参数化是底线,白名单是保险丝,而通配符语义控制——才是容易被忽略的最后一道缝。











