IN 查询中的 SQL 注入风险与参数化写法

在 Web 安全领域,SQL 注入始终是 OWASP Top 10 的常客。很多开发者已经养成了使用参数化查询的习惯,但在遇到 IN 查询时,却常常不自觉地退回到字符串拼接的老路。这篇文章就来聊聊 IN 查询中那些容易被忽视的注入风险,以及正确的参数化写法。

为什么 IN 查询容易出问题

普通的等值查询很容易参数化:

SELECT * FROM users WHERE id = ?

IN 查询的占位符数量是动态的,取决于数组长度。很多开发者不知道如何为可变数量的参数生成占位符,于是选择了最"省事"的做法——直接拼接:

$ids = implode(',', $_GET['ids']);
$sql = "SELECT * FROM users WHERE id IN ($ids)";

这段代码看起来简洁,却把用户输入直接送进了 SQL 语句。攻击者只需传入 1) UNION SELECT username,password FROM admin--,就能读取敏感数据;传入 1); DROP TABLE users--,甚至可能直接删库。

问题的根源在于:IN 后面的括号里,本质上是 SQL 语法结构的一部分,而不是数据。当你把用户输入拼进去时,用户输入就不再是"数据",而变成了"代码"。

常见的错误写法

错误一:直接拼接数组

$ids = implode(',', $ids);
$sql = "SELECT * FROM users WHERE id IN ($ids)";

即使对每个元素做了 intval,也不推荐这种习惯。一旦某个环节漏掉了类型转换,或者字段类型从整型变成了字符串,漏洞立刻出现。

错误二:手动加引号

$ids = "'" . implode("','", $ids) . "'";
$sql = "SELECT * FROM users WHERE name IN ($ids)";

如果数组元素本身包含单引号,比如 O'Reilly,拼接就会破坏 SQL 结构,既可能报错,也可能被利用。

错误三:用 addslashesmysql_real_escape_string

这两个函数在特定字符集下存在绕过风险,而且它们只处理字符串,对数字型注入无能为力。转义永远只是权宜之计,参数化才是根本解法。

正确的参数化写法

核心思路是:根据数组长度动态生成占位符,参数仍然通过绑定传入

PHP PDO

$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);

array_fill 生成与数组等长的 ?execute 时按顺序绑定。用户输入从头到尾都只是参数,不参与 SQL 解析。

Python(psycopg2 / pymysql)

ids = [1, 2, 3]
placeholders = ','.join(['%s'] * len(ids))
cursor.execute(f"SELECT * FROM users WHERE id IN ({placeholders})", ids)

注意 %s 只是占位符,具体的驱动会负责转义和绑定,不要把它和 Python 的字符串格式化混淆。

Java(JDBC)

String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE id IN (" + placeholders + ")");
for (int i = 0; i < ids.size(); i++) {
    ps.setInt(i + 1, ids.get(i));
}

Node.js(mysql2)

const placeholders = ids.map(() => '?').join(',');
const [rows] = await conn.execute(
  `SELECT * FROM users WHERE id IN (${placeholders})`, ids);

这里拼接的只是 ? 字符,不含任何用户数据,因此是安全的。

几个容易踩的坑

空数组。如果数组为空,IN () 是非法语法。需要提前判断,直接返回空结果或跳过查询。

超长数组。MySQL 对预处理语句的参数数量有上限(默认 65535),超大数组需要分批查询。

类型不一致。如果字段是字符串而绑定了整数,某些数据库会隐式转换,可能导致索引失效。绑定前确认类型匹配。

占位符与参数顺序。如果 SQL 中还有其他参数,要保证绑定顺序与占位符出现顺序一致,否则会张冠李戴。

与其他安全话题的关联

IN 查询的注入只是 SQL 注入的一个缩影。要系统防御,还需要配合其他措施:数据库账号最小权限、关闭详细错误回显、对输入做白名单校验。如果你在写前端,别忘了 XSS 的常见方式和前端如何防御同样重要——注入类漏洞往往在前后端之间互相配合。运维层面,参考 Linux 服务器安全加固清单,把 Web 服务、数据库、系统账户的权限收紧,能大幅降低漏洞被利用后的影响面。

小结

IN 查询的注入风险,本质上是"动态 SQL 结构"与"用户数据"混在一起造成的。解决办法并不复杂:用占位符拼结构,用绑定传数据。无论用哪种语言、哪种数据库驱动,这个原则都适用。养成习惯后,你会发现参数化写法并不比拼接麻烦多少,但它挡住的是可能让整个系统沦陷的风险。

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

赞 (0) 打赏

评论 0

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

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

支付宝扫一扫打赏

微信扫一扫打赏