LIKE 查询中的 SQL 注入风险与安全写法

LIKE 查询是 Web 应用中最常用的数据库操作之一,搜索框、模糊匹配、后台筛选几乎都离不开它。然而,正是这个看似简单的 %keyword%,常常成为 SQL 注入的突破口。很多开发者知道用参数化查询防注入,却在 LIKE 场景下不自觉地又把用户输入拼回了 SQL 字符串。本文从攻击面和防御写法两个角度,把 LIKE 查询的安全问题讲清楚。

为什么 LIKE 查询容易出问题

普通查询的注入防御思路很直接:用占位符把用户输入当数据传进去。但 LIKE 的语法要求通配符 %_ 必须出现在模式串里,而模式串又需要包含用户输入,于是不少代码就写成了这样:

$sql = "SELECT * FROM articles WHERE title LIKE '%" . $_GET['kw'] . "%'";

这段代码的问题和普通拼接注入完全一样。攻击者提交 kw=' OR '1'='1,语句就变成:

SELECT * FROM articles WHERE title LIKE '%' OR '1'='1%'

条件恒真,全表数据被拖走。更危险的是联合查询注入,攻击者可以构造 %' UNION SELECT username, password FROM users -- ,直接把敏感表的内容拼进结果集。

即使开发者用了参数化查询,如果写法不当,仍然会留下隐患。比如下面这种“伪参数化”:

$sql = "SELECT * FROM articles WHERE title LIKE '%?%'";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['kw']]);

占位符被引号包住,数据库会把 ? 当成普通字符而不是参数标记,预处理失效,实际执行的仍是拼接后的字符串。这是 LIKE 查询里最隐蔽的坑之一。

LIKE 注入的两种典型利用方式

第一种是布尔盲注。攻击者不直接看到数据,而是通过页面返回的“有结果/无结果”判断条件真假。例如提交 kw=test%' AND (SELECT SUBSTRING(password,1,1) FROM users LIMIT 1)='a' -- ,逐字符猜解管理员密码。LIKE 查询的结果集变化天然适合做这种布尔判断。

第二种是时间盲注。当页面没有明显回显差异时,攻击者用 SLEEP() 制造延迟:

SELECT * FROM articles WHERE title LIKE '%test%' AND IF(SUBSTRING(database(),1,1)='a',SLEEP(3),0) -- %'

响应时间的长短就成了一个比特的信道。这类攻击在 LIKE 场景下同样成立,因为注入点依然在 SQL 语句结构里。

安全写法一:参数化查询 + 通配符外置

正确的做法是把通配符拼在参数值上,而不是拼在 SQL 字符串里。以 PDO 为例:

$kw = $_GET['kw'];
$sql = "SELECT * FROM articles WHERE title LIKE :kw";
$stmt = $pdo->prepare($sql);
$stmt->execute([':kw' => '%' . $kw . '%']);

这里 SQL 模板是固定的,% 作为参数值的一部分被数据库当作数据处理,不会改变语句结构。Java 的 JDBC 同理:

String sql = "SELECT * FROM articles WHERE title LIKE ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "%" + keyword + "%");

关键原则只有一条:SQL 字符串里只出现占位符,用户输入和通配符都在参数值里拼接。

安全写法二:转义 LIKE 元字符

参数化解决了注入,但没有解决“语义注入”。用户输入 %_ 时,它们会被当作通配符,导致搜索结果不符合预期。比如用户搜 100%% 会匹配任意字符,返回大量无关记录。更严重的是,如果业务逻辑依赖精确匹配,元字符可能被用来绕过某些过滤规则。

因此对用户输入中的 %_ 以及转义符本身做处理是必要的。MySQL 默认转义符是反斜杠:

$kw = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $_GET['kw']);
$stmt->execute([':kw' => '%' . $kw . '%']);

也可以在 SQL 中显式指定转义符,避免依赖默认行为:

SELECT * FROM articles WHERE title LIKE :kw ESCAPE '\'

注意,转义元字符是业务正确性的要求,不能替代参数化。两者解决的是不同层面的问题:参数化防注入,转义防语义偏差。

安全写法三:白名单与长度限制

对于排序字段、匹配模式这类无法参数化的位置,必须用白名单。例如允许用户选择“开头匹配”“包含匹配”“结尾匹配”:

$modes = [
    'prefix' => '%s%%',
    'contains' => '%%%s%%',
    'suffix' => '%%s%',
];
$pattern = sprintf($modes[$mode] ?? $modes['contains'], $kw);

模式模板来自代码内部,用户只能选择键名,无法影响 SQL 结构。同时对关键词做长度限制(如 50 字符)和字符集校验,可以进一步压缩攻击面。

结合其他安全措施

LIKE 查询的安全不能只靠一处写法。结合以下措施效果更好:

  • 数据库账号最小权限,Web 应用账号不应有 FILEDROP 等权限;
  • 关闭详细错误回显,避免报错信息泄露表结构;
  • 对输出做 HTML 转义,防止存储型 XSS 与注入组合利用;
  • 使用 ORM 时确认其 LIKE 方法是否真正参数化,不要想当然。

小结

LIKE 查询的注入风险本质上是字符串拼接风险,只是通配符的存在让开发者容易放松警惕。安全写法的核心可以归纳为三点:SQL 模板固定、用户输入走参数、通配符和元字符在参数值层面处理。再配合白名单、权限控制和输出转义,就能把 LIKE 查询从常见的注入入口变成一道可靠的防线。

未经允许不得转载:任鹏个人博客 » LIKE 查询中的 SQL 注入风险与安全写法

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏