参数化查询没防住SQL注入?我的代码哪里写错了?

俊杰酱~ 阅读 256

最近在学参数化查询防注入,但测试时发现还是能被绕过。比如在Node.js用mysql模块写这个登录验证:


const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password], (err, results) => {
  // 处理结果
});

但当我用’ OR ‘1’=’1′– 作为用户名测试时,居然能绕过密码验证。难道参数化查询不能防这种条件注入?我是不是应该把参数单独写成对象?或者需要对特殊字符做额外转义?

我来解答 赞 9 收藏
二维码
手机扫码查看
2 条解答
Mc.俊俊
Mc.俊俊 Lv1
参数化查询本身是能防注入的,你代码里 ? 占位符用法没问题。

但问题不在查询本身,而在你登录验证的逻辑。你那个查询如果被注入成功,会变成:

SELECT * FROM users WHERE username = '' OR '1'='1'--' AND password = ''


这条SQL会返回所有用户(因为 '1'='1' 永远为真,后面的被注释掉了)。然后你代码里如果直接判断「有结果就登录成功」,那就已经被绕过了。

说白了,攻击者不需要知道密码,只要能让查询返回一条记录就算「验证通过」。

修复方法很简单:查询只用来找用户,然后单独验证密码:

// 先根据用户名查用户,别带密码条件
const query = 'SELECT * FROM users WHERE username = ?';
db.query(query, [username], (err, results) => {
if (err || results.length === 0) {
return res.status(401).send('用户不存在');
}

const user = results[0];
// 用bcrypt或你用的方式验证密码
bcrypt.compare(password, user.password, (err, match) => {
if (match) {
// 登录成功
} else {
// 密码错误
}
});
});


这样就算注入返回了一堆用户,也会在密码验证那一步挂掉。

另外提醒一下,密码别存明文,用bcrypt之类的加盐哈希存。
点赞
2026-03-12 13:12
诸葛羽墨
我当时也卡在这,后来发现是用了拼接字符串的姿势不对。

你这段代码本身没有问题,问题可能出在username或者password变量来源上。如果直接用了req.body.username或者URL参数这种未经处理的原始输入,中间件或者库可能已经帮你做过一次unescape了。

举个例子:
假设请求时传的是username=' OR '1'='1'--
到了后端变量里其实会变成username=" OR '1'='1'--

这时候你再用参数化查询,实际执行的语句就变成了:
SELECT * FROM users WHERE username = " OR '1'='1'-- " AND password = ?

看到问题了吗?引号闭合被绕过了。

解决办法很简单,在获取参数的时候手动加一层escape:
username = connection.escape(req.body.username)
password = connection.escape(req.body.password)

或者用Object形式传参:
db.query(query, { username: req.body.username, password: req.body.password }, ...)

我当时调试了整整一下午才找到原因,记住一句话:永远不要相信任何外部输入。
点赞 20
2026-02-06 13:03