SQL 注入还没死,只是换了地方

上周帮人看一个后台,登录框没做验证码,顺手在用户名里打了个单引号,页面直接甩出一段 MySQL 的语法错误。

2026 年了。

SQL 注入被讨论了二十年,教程多到能堆成山,预编译语句几乎所有框架默认就开。可只要你还在接外包、还在看别人的老站,这件事就一定会碰上。这篇不讲「什么是注入」,讲点实际的东西:它为什么还活着,哪些手段还有用,哪些已经不好使了。

它还活着,原因很无聊

三个,按出现频率排。

一、老代码。我见过的出问题的站,后台大多是 2015 年前后写的,那会儿很多教程就是这么教的:SELECT * FROM admin WHERE user='$user' AND pass='$pass'。代码没坏,一直在跑,只是没人回头改。

二、拼接。新项目也一样。参数化写多了,总有一处需要「动态」:排序字段、表名、时间范围。这些地方占位符放不进去,于是又拼回去了。拼接不是原罪,不加白名单的拼接才是。

三、ORM 给的错觉。用了 ORM 就安全,这话只在「全程用 ORM 提供的方法」时成立。一旦有人写 whereRaw、orderBy(request('sort')),防线就没了,而且代码看起来还挺「框架化」,评审时特别容易滑过去。

手工注入还值得学吗

值得,但目的变了。

十年前学手工注入是为了打站。现在绝大多数场景里,你手打的速度比不过 sqlmap,而且真正的目标往往有 WAF,手打也是被拦。

它现在的价值在另一个地方:看懂工具在干什么。

sqlmap 跑完给你一句「Parameter ‘id’ is vulnerable」,你要能判断它凭什么下的结论,是报错回显,还是布尔差异,还是时间延迟。判断不了,你就没法区分「真漏洞」和「WAF 返回的假页面」。我见过太多报告里写着高危注入,实际是对方拦截页统一返回 200 导致的误报。

手工的价值还有一层:判断这个洞能不能用。读得到数据、能写文件、能堆叠查询,是完全不同的三个等级。工具不会替你判断业务影响。

几种手法,哪些还灵

先说结论:最稳的是时间盲注,最快的是报错注入,最难用的是联合查询——因为页面上通常没有能对齐的展示位。

报错注入(MySQL 的 extractvalue、updatexml)一次能带出 32 个字符,效率很高。但它依赖数据库把错误原样吐给前端。生产环境关掉 display_errors 之后,这条路基本断了,很多 WAF 也专门盯着这几个函数名。

联合查询要求原语句的列数和回显位都匹配。老站常见,现代站少。

时间盲注最慢,也最烦人:一个字符一次请求,一个 32 位哈希要跑几分钟。但它最不容易被拦,因为它看起来就只是一个慢查询——WAF 很难从「这条请求让数据库睡了 5 秒」里判断出恶意。

布尔盲注介于两者之间,快,但依赖页面有稳定的差异(比如「用户不存在」和「密码错误」文案不同)。这种差异现在越来越少了。

顺带说一句 WAF 绕过。内联注释、大小写、编码、分块传输这些手段,在 2018 年很有效。现在主流 WAF 的规则库里全都有,能绕过去的往往是配置问题(比如某条路径没接入防护),而不是技巧问题。花两小时构造一个只对某个老版本 ModSecurity 生效的 payload,性价比很低。

sqlmap 用得对不对,看两个参数

大部分人用 sqlmap 就是 -u "http://..." 加回车,然后等结果。这样能用,但经常白跑。

--level 和 --risk 是被误解最深的两个参数。它们不是「难度」,是「愿意发多少请求」和「愿意冒多大风险」。

--risk 3 会开启 OR 型注入测试,某些情况下会改写数据。对着一个真实业务站开 risk 3,是有可能把表数据搞乱的。默认的 risk 1 已经能覆盖大部分情况。

--level 5 会测 Cookie、User-Agent、Referer 这些头部。如果你的注入点在 Cookie 里,不提高 level 根本测不到——但很多人不知道,然后得出「这个站没有注入」的结论。

还有一个细节:默认情况下 sqlmap 遇到 302 跳转、或者页面内容变化大的情况,判断会不稳。参数 --string、--not-string 是用来校准的,比盲目提高 level 有用得多。

参数化也不是万能的

这是我觉得最该讲清楚的一点。很多人以为上了预编译就完事,实际上有四个地方参数化管不着:

位置 为什么不能参数化 正确做法
表名 占位符只能替换值,不能替换标识符 白名单映射,别用用户输入
列名 同上 白名单
ORDER BY 的字段 同上 白名单,不要拼字符串
LIMIT 的偏移量 部分驱动不支持 强制转成整数再拼

这四个位置,正是现在实际出现注入最多的地方。?sort= 后面跟一个字段名,几乎每个后台列表页都有。

我见过一个把 ORDER BY 做成下拉框的站,选项值直接传后端。这种设计里,参数化帮不上忙,只能靠白名单。

说点防御的实话

WAF 有用,但它是兜底,不是防线。我见过的绕过,八成不是技术绕的,是配置漏的:新上的接口没接规则、静态资源路径放行了整个目录、备份文件忘了删。

真正管用的顺序是:参数化 + 四处白名单(上面那张表)+ 数据库账号最小权限 + 关掉详细报错。前两条挡住了绝大多数,后两条让漏网的那一两个不至于直接变成拖库。

最小权限这条最容易被忽略。很多站的数据库账号是 root,一个只读的注入点能直接变成 LOAD_FILE 读服务器文件。给它一个只能 SELECT 特定库的账号,损失立刻小一个量级。

最后

这东西没什么新东西可讲,之所以还在写,是因为它还在发生。

如果你在维护一个跑了五年以上的站,建议做一件事:把所有拼 SQL 的地方 grep 一遍,特别是 order by 和 limit。我打赌你能找出至少三处。

找出来之后别急着改,先看看那个参数有没有白名单——很多「注入点」其实是参数化写对了的,只是看起来像拼接。


本文只讨论自有系统与授权范围内的安全测试。对未授权目标做任何注入尝试都属于违法行为。

© 版权声明
THE END
喜欢就支持一下吧
点赞9赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容