2017年9月,注入Equifax公司(美国三大信用报告机构之一)承认遭遇大规模数据泄露,漏洞1.43亿消费者的注入姓名、社保号码、漏洞出生日期和地址被窃取。注入事后调查显示,漏洞攻击者利用的注入正是其网站一个Apache Struts框架的SQL注入漏洞——一个早在同年3月就被公开并修复的漏洞。这起事件最终让Equifax付出了超过14亿美元的漏洞代价,也再次证明SQL注入不是注入教科书里的过时概念,而是漏洞至今仍在互联网底层流动的致命暗流。
SQL注入的注入本质,是漏洞攻击者把恶意SQL代码“注入”到应用程序的查询语句中,让数据库执行非预期的注入命令。说得更直白些:你的漏洞网站本应问数据库“这个用户的密码是多少”,但攻击者通过输入框塞进一句“顺便把整个用户表删掉”,注入数据库照做了。这不是黑客技术有多高深,而是开发者在拼接SQL字符串时,忘了区分“代码”和“数据”。
举一个最常见的例子。一个登录表单背后通常有这样的逻辑:
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码' 如果开发者直接把用户输入拼进这个字符串,攻击者在用户名框里输入 ' OR '1'='1,查询就变成了:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '任意内容' 由于 '1'='1' 永远为真,这条语句会返回表中第一条用户记录——通常就是管理员。攻击者甚至不需要知道密码,就这么轻松“登录”了。更狠的玩法是用 UNION SELECT 从其他表里拖数据,或者用 ; DROP TABLE users; -- 直接把表删了。
我见过不少“老练”的开发者也栽在这上面。他们觉得只要过滤了单引号就安全了,但没想过数字型参数、编码绕过、堆叠查询这些变种。有个做电商的朋友,后台订单查询接口因为用了拼接SQL,被一个脚本小子用 sleep(10) 拖慢了整个数据库——那小子图好玩,没真偷数据,但系统瘫痪了整整一个下午。
真正让人头疼的是,SQL注入的入口远不止登录框。搜索框、排序参数、分页参数、HTTP头里的User-Agent、Cookie值——任何能被程序读取并用于构建SQL语句的地方,都可能成为攻击面。有些注入甚至藏在JSON请求体里,肉眼根本看不出来。
防御思路其实很清晰,核心就一条:永远不要信任用户的任何输入。具体做法首选参数化查询(PreparedStatement),让数据库把输入当纯数据处理,而不是SQL代码。其次是严格校验输入类型,比如数字参数就强制转成int,字符串就限制长度和字符集。存储过程也能减少拼接,但要小心动态执行的部分。最后是数据库权限最小化——就算被注入了,一个只有SELECT权限的账号也翻不了天。
但现实往往比技术更复杂。很多遗留系统里,几千行拼接SQL的代码像老房子的电线一样缠在一起,说改就改不现实。我见过最务实的做法是加一层Web应用防火墙(WAF)拦截明显恶意请求,再逐步用ORM框架重构代码——虽然慢,但至少不会让Equifax的悲剧重演。记住,安全不是一次性的功能,而是持续对抗的过程。