sql注入漏洞怎么处理
去年有个做电商的入漏朋友找我,说后台订单数据被人删了一半。洞处我问他怎么发现的入漏,他说有个买家下单后留言里带了一串奇怪字符,洞处当时没在意,入漏第二天数据库就炸了。洞处典型的入漏SQL注入,攻击者通过输入框把恶意SQL语句塞进查询里,洞处数据库乖乖执行了。入漏
这事儿的洞处根子在于:程序把用户输入直接拼接到SQL语句里。比如登录验证,入漏代码写成 "SELECT * FROM users WHERE name='" + userName + "' AND pwd='" + password + "'"。洞处用户在用户名框里输入 ' OR '1'='1,入漏整条语句就变成了永真条件,洞处密码都不用验直接进系统。入漏更狠的输入 '; DROP TABLE orders;--,分号结束原语句,后面跟上删表命令,两个横杠注释掉剩余部分。数据库照单全收。
参数化查询是正路
别想着自己写过滤函数去替换单引号,那玩意儿绕过的姿势太多了。十六进制编码、宽字节注入、注释符变形,你堵不完的。正确做法是让数据库把用户输入当数据,而不是当SQL代码的一部分。参数化查询就是干这个的。
Java里用PreparedStatement:
String sql = "SELECT * FROM users WHERE name=? AND pwd=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, userName); ps.setString(2, password); ResultSet rs = ps.executeQuery(); Python的sqlite3模块用占位符:
cursor.execute("SELECT * FROM users WHERE name=? AND pwd=?", (userName, password)) PHP的PDO同样:
$stmt = $pdo->prepare("SELECT * FROM users WHERE name=:name AND pwd=:pwd"); $stmt->execute(['name' => $userName, 'pwd' => $password]); 占位符位置传进去的值,数据库引擎会做转义处理,单引号会被当成普通字符。攻击者输入 ' OR '1'='1,数据库去找名字叫“' OR '1'='1”的用户,找不到就拒绝登录。逻辑上就堵死了。
框架和ORM不是万能药
有人觉得用了Hibernate、MyBatis就高枕无忧。我见过一个项目用MyBatis的${ }拼接参数,而不是#{ }。${ }是直接字符串替换,跟手动拼接没区别。攻击者照样注入。ORM里写原生SQL、用createQuery拼字符串,一样中招。工具本身不解决问题,用对方式才行。
还有个坑是存储过程。如果存储过程内部用动态SQL拼接参数,照样有注入风险。别以为把逻辑挪到数据库层就安全了。
纵深防御的几个点
参数化查询是底线,但光靠它不够。数据库账号权限要收窄,Web应用连数据库的账号只给CRUD权限,别用root。这样就算注入成功,攻击者也删不了表、读不了系统表。错误信息别直接吐给前端,把SQLException堆栈暴露出去等于给攻击者送地图。日志里记录异常就行,用户看到“系统繁忙”就够了。
WAF能挡一部分自动化扫描工具,但别指望它。我见过WAF规则被/*!50000SELECT*/这种内联注释绕过的案例。WAF是缓冲层,不是修复方案。
定期用sqlmap扫自己的接口。这工具自动化程度很高,加上--level=5 --risk=3参数能把各种注入点翻个底朝天。扫出来有问题就改代码,别想着改WAF规则糊弄过去。
代码审查时重点看拼接SQL的地方。搜索"SELECT" +、"WHERE" +、String.format里带SQL的、${ }在MyBatis XML里的。这些位置挨个过一遍,比事后补救省事得多。
那个电商朋友后来把整个DAO层重写了,所有查询走PreparedStatement。数据库账号也降权了,订单表加了软删除标记。他说早该这么干,之前觉得麻烦,出事之后发现重写也就两天的事。
(责任编辑:时尚)













