sql注入漏洞修复

 人参与 | 时间:2026-10-17 07:38:20

去年某电商平台被拖库,入漏起因是洞修一个两年前的旧接口没下线。开发早跑了,入漏代码里明晃晃写着 SELECT * FROM orders WHERE id = ' + request.getParameter("orderId") + '。洞修攻击者用 1' OR '1'='1 直接拉走全部订单。入漏这事儿不新鲜,洞修但每次听到都让人冒火——明明能防,入漏偏要等出事。洞修

SQL注入的入漏本质就一句话:用户输入被当成代码执行了。你写 WHERE name = ' + input + ',洞修数据库看到的入漏是拼接后的完整语句。攻击者塞个单引号进去,洞修语句结构就崩了。入漏崩了之后能干嘛?洞修读数据、改数据、入漏删表,甚至通过 LOAD_FILE() 读服务器文件。MySQL的 INTO OUTFILE 还能写webshell。别觉得数据库权限小就没事,横向提权的手段多的是。

修复的核心思路只有一条:让用户输入永远是数据,永远别碰代码层。具体怎么做,分几个层面说。

参数化查询是底线

不管用什么语言,预编译语句必须用上。Java的 PreparedStatement,Python的 cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)),PHP的PDO prepare。原理是把SQL模板先发给数据库解析,参数后传,数据库知道哪些是结构哪些是值。占位符 ? 或 %s 的位置,数据库压根不会把它当代码解析。

有人问:我用了 PreparedStatement 但拼接了表名,安全吗?不安全。表名、列名、ORDER BY 后面的字段名,这些没法用占位符。得用白名单校验——只允许 [a-zA-Z0-9_],且必须在预设列表里。见过一个项目用 ORDER BY 注入,攻击者用布尔盲注把管理员密码一位位猜出来,就因为这地方没管。

存储过程不等于安全

很多人以为调存储过程就没事了。错。如果存储过程内部用 EXEC('SELECT * FROM ' + @table) 动态拼SQL,照样注入。安全的是参数化调用,不是存储过程本身。我见过一个老系统,存储过程里拼字符串拼得飞起,审计报告还写着“已使用存储过程,无注入风险”。这种报告害死人。

ORM不是万能药

Hibernate、MyBatis这些框架默认用预编译,但MyBatis的 ${ } 和 #{ } 区别能坑死新手。#{ } 走预编译,${ } 直接拼字符串。有人写 ORDER BY ${ column} 图省事,结果被注。ORM的HQL注入也有,比如 from User where name = '" + name + "',HQL最终转成SQL时一样中招。框架只是工具,脑子得清醒。

输入验证是辅助,不是主力

过滤 union、select 这些关键词?绕过的姿势太多了:大小写混写、内联注释 /*!select*/、URL编码、宽字节。输入验证用来挡掉明显异常请求可以,但别指望它当主防线。真正管用的是参数化加最小权限。

最小权限原则别忽略

Web应用连数据库的账号,只给必要的CRUD权限。别用root。别给 FILE、EXEC 权限。一个查资讯的接口,账号只能读资讯表,就算被注了,损失也有限。见过一个后台,数据库账号能 DROP 任意表,攻击者一个 '; DROP TABLE users; -- 直接删库。权限收窄,伤害减半。

WAF和RASP

WAF能挡一部分自动化扫描,但别当救命稻草。绕WAF的payload满天飞。RASP在运行时拦截,比WAF准,但性能有损耗。我的看法:参数化是根治,WAF是补丁,RASP是保险。顺序别搞反。

最后说个真事。某次代码审计,看到一个查询接口参数叫 sortField,开发用 String.format 拼进SQL。我随手试了 sortField=id,(select*from(select(sleep(5)))a),页面卡了五秒。开发在旁边看着,脸都白了。修复方案?白名单加预编译,两小时改完。早干嘛去了。

SQL注入不是技术难题,是态度问题。参数化查询写起来不比拼接麻烦,但能省掉半夜被叫起来应急的麻烦。别等被拖库了才想起来改。

顶: 81423踩: 21