sql注入漏洞怎么修复
去年有个做电商的入漏朋友找我,说后台订单数据被人翻了个底朝天。洞修我让他把数据库日志导出来一看,入漏攻击者用的洞修就是最基础的 ' OR '1'='1。他那个查询接口直接把用户输入的入漏订单号拼进了SQL语句里,连过滤都没做。洞修这种事每天都在发生,入漏修复方法其实不复杂,洞修但很多人就是入漏懒得改。
先说最根本的洞修一条:别拼字符串。我见过太多代码长这样——"SELECT * FROM users WHERE name = '" + userName + "'"。入漏这种写法等于把大门钥匙插在锁上还贴了张纸条说“欢迎光临”。洞修正确的入漏做法是使用参数化查询,也就是洞修预编译语句。拿Java举例,入漏用 PreparedStatement:
String sql = "SELECT * FROM users WHERE name = ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, userName); ResultSet rs = ps.executeQuery(); 这样数据库会把 userName 当成纯数据,就算里面塞了 ' OR 1=1 -- 也只会被当作一个普通的字符串去匹配,不会改变SQL结构。PHP里用PDO的 prepare 加 execute,Python里用 cursor.execute("... %s", (user_input,)),原理都一样。参数化查询是防注入的底线,没有之一。
但光靠参数化还不够。有些场景没法用参数化,比如动态表名、动态排序字段。这时候就得用白名单校验。比如用户传过来一个 sort=price,你不能直接拼 ORDER BY price,而是先检查这个值在不在允许的列表里:["price", "create_time", "id"]。不在就拒绝或者用默认值。表名同理,把可选的表名硬编码成一个数组,用户传什么就查什么,别让数据库去猜。
还有一个容易被忽略的点:数据库账号权限。很多应用连数据库用的是 root 或者 sa,这等于把整个数据库的钥匙都交给了前端。正确的做法是给每个应用单独建账号,只授予它需要的权限。比如一个只读的查询接口,账号就只给 SELECT,别给 DROP、UPDATE、DELETE。就算注入漏洞真的被利用了,攻击者也只能读到有限的数据,没法删库跑路。我见过一个案例,攻击者通过注入拿到了 mysql.user 表的哈希,就是因为那个账号有 SELECT 全库的权限。
输入验证要不要做?要做,但别指望它。很多人觉得把单引号过滤掉就安全了,结果攻击者用 %27 或者宽字节编码绕过去。过滤特殊字符只能挡住最笨的脚本小子,挡不住稍微懂点编码的人。所以输入验证是辅助手段,不是主要防线。真正该做的是在参数化查询的基础上,对输入做类型和格式的检查。比如订单号只允许数字,那就用正则 ^\d+$ 卡死,不符合的直接返回错误。
最后说一个实际项目里常见的坑:ORM框架。很多人以为用了Hibernate、MyBatis就万事大吉了,但如果你在MyBatis里写 ${ } 而不是 #{ },照样会注入。${ } 是直接拼接,#{ } 才是参数化。我见过一个团队用MyBatis,所有查询都写 ${ },理由是“方便调试”。结果上线三个月就被拖了库。所以用ORM也要看具体写法,别把安全责任全推给框架。
修复SQL注入没有银弹,就是老老实实把参数化查询用对,把权限收窄,把白名单加上。这些事做起来不酷,但能让你晚上睡得着觉。我那个电商朋友后来把整个DAO层重写了一遍,花了三天,之后半年再没出过事。三天换半年安宁,这笔账怎么算都值。
```(责任编辑:探索)














