参数化查询没防住SQL注入?我的代码哪里写错了?
最近在学参数化查询防注入,但测试时发现还是能被绕过。比如在Node.js用mysql模块写这个登录验证:
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password], (err, results) => {
// 处理结果
});
但当我用’ OR ‘1’=’1′– 作为用户名测试时,居然能绕过密码验证。难道参数化查询不能防这种条件注入?我是不是应该把参数单独写成对象?或者需要对特殊字符做额外转义?
?占位符用法没问题。但问题不在查询本身,而在你登录验证的逻辑。你那个查询如果被注入成功,会变成:
这条SQL会返回所有用户(因为
'1'='1'永远为真,后面的被注释掉了)。然后你代码里如果直接判断「有结果就登录成功」,那就已经被绕过了。说白了,攻击者不需要知道密码,只要能让查询返回一条记录就算「验证通过」。
修复方法很简单:查询只用来找用户,然后单独验证密码:
这样就算注入返回了一堆用户,也会在密码验证那一步挂掉。
另外提醒一下,密码别存明文,用bcrypt之类的加盐哈希存。
你这段代码本身没有问题,问题可能出在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 }, ...)
我当时调试了整整一下午才找到原因,记住一句话:永远不要相信任何外部输入。