sql注入论文

2011年夏天,入论我帮朋友看一个政府网站的入论后台。登录框输入 ' or 入论'1'='1,直接进了管理员页面。入论那是入论我第一次亲眼见到 SQL 注入,比任何教科书上的入论示意图都来得直接——数据库把用户输入当成了代码执行,仅此而已。入论

SQL 注入的入论本质是代码与数据边界模糊。程序员写了一条查询语句,入论比如 SELECT * FROM users WHERE name = '输入'。入论用户如果输入 admin' --,入论拼接后变成 SELECT * FROM users WHERE name = 'admin' --',入论注释符把后面的入论验证逻辑全吃掉了。数据库不关心谁是入论“用户输入”,它只认语法。入论这个漏洞从上世纪九十年代末开始被大规模利用,到今天仍然排在 OWASP Top 10 里,不是没有原因的。

为什么二十多年了还没消失

拼接字符串写 SQL 太顺手了。一个刚入行的后端,接到“查用户列表”的需求,第一反应往往是 "SELECT * FROM users WHERE id = " + id。代码能跑,测试通过,上线。问题在于,这种写法把安全责任推给了每一个调用点——只要有一处忘了过滤,整条防线就破了。参数化查询不是新东西,PDO 从 PHP 5.1 就有了,JDBC 的 PreparedStatement 更早。但直到今天,我审过的代码里,拼接 SQL 的比例依然不低。

另一个原因是注入的变体太多。报错注入、盲注、时间盲注、堆叠查询……防御方以为过滤了单引号就万事大吉,攻击方用 0x... 十六进制编码照样绕过。我见过一个系统,对 union 关键字做了黑名单,结果攻击者用 /*!50000union*/ 这种 MySQL 版本注释语法直接穿透。黑名单思路从一开始就是错的。

几个真实场景的细节

2015 年某电商平台的订单查询接口,URL 里有个 orderId 参数。正常请求返回订单详情,加个单引号报 500 错误,页面直接吐出了 MySQL 的语法错误信息,包括当前数据库名和版本。攻击者不需要任何工具,浏览器地址栏就是注入点。后来查日志,这个接口被扫了三个月,直到有人用 load_file() 读了 /etc/passwd 才被发现。

还有一次是内网系统。登录处用了存储过程,参数拼接在 EXEC 里面。开发觉得存储过程天然安全,其实 EXEC('SELECT * FROM users WHERE name = ''' + @name + '''') 和普通拼接没有区别。那台服务器上跑着 Oracle,攻击者用 utl_http.request 直接把数据外带到外部地址。内网不是保险箱,注入点加上足够的权限,一样能打通。

防御不是加一层 WAF 就完事

很多团队的做法是:代码照旧拼接,前面挂个 WAF 拦一拦。WAF 有用,但它是概率游戏。编码变形、分块传输、参数污染,绕过手法一直在更新。真正靠谱的还是参数化查询——让数据库预编译语句结构,用户输入永远只是数据,不参与语法解析。如果实在改不动老代码,至少把数据库账号权限降到最低,别用 root 连生产库。我见过一个系统,Web 应用的数据库账号有 FILE 权限,注入之后直接写 webshell 到网站目录,连提权都省了。

说回 2011 年那个政府网站。我后来告诉他们修一下,对方回了一句“内网系统,没事”。第二年拖库事件曝光,那个站也在名单里。SQL 注入不是技术难题,是态度问题。参数化查询写起来多花不了五分钟,但很多人就是不愿意。