一个下午,注入我的攻击同事老张的网站后台突然多了一个管理员账号。密码是注入“hacker123”。他查了三天日志,攻击最后发现罪魁祸首是注入搜索框里一段不起眼的代码:<script>alert('xss')</script>。没人点击它,攻击但服务器还是注入执行了——因为他的代码把用户输入直接拼进了SQL查询,而那个查询结果又被渲染成了HTML。攻击这不是注入段子,这是攻击javascript注入攻击最典型的死法。
javascript注入的注入本质很简单:攻击者把恶意脚本塞进你的页面,你的攻击浏览器傻乎乎地执行了它。但真正可怕的注入是它藏得深。你以为用户输入只是攻击字符串,可当它被拼进innerHTML、注入eval、document.write,或者被塞进URL参数再反射到页面上时,它就变成了代码。就像你往自动售货机里投硬币,结果投进去一张写着“给我一罐可乐”的纸条,机器还真吐出来了——因为售货机把纸条当成了指令。
最常见的注入点就是那些看起来人畜无害的地方。评论区、搜索框、用户昵称、甚至URL的查询参数。攻击者不需要什么高深技术,他们只是往这些地方扔一段<img src=x onerror=alert(document.cookie)>,然后等着你的页面把这段代码渲染出来。一旦成功,他们就能偷走你的cookie、模拟你的操作、篡改页面内容,甚至直接接管整个会话。我见过最骚的操作是有人把整个键盘记录器塞进了一个在线文档编辑器的“分享链接”功能里,所有打开链接的人,敲的每个字都被悄悄发到了攻击者的服务器上。
防御手段其实不复杂,但大多数人栽在侥幸心理上。第一道防线是转义输出——把用户输入里的<变成<,把>变成>,让浏览器把它们当纯文本看。第二道防线是输入校验,白名单比黑名单好用一万倍。比如用户名只允许字母数字和下划线,长度限制在20个字符以内,其他一律拒绝。第三道防线是Content Security Policy,告诉浏览器“这个页面只能加载我自己域名的脚本”,哪怕攻击者把恶意代码插进来了,浏览器也会直接拒绝执行。
但光靠技术手段不够。我见过太多开发者在写代码时想“这个输入只有管理员能填,没事的”,结果管理员账号被盗,整个后台沦陷。也见过有人用第三方库处理用户输入,但库本身就有漏洞,更新不及时,照样被钻空子。真实世界里,javascript注入攻击往往不是被“防住”的,而是被“发现”的——等发现时,数据已经泄露了。
有一次我帮朋友审计他的电商网站,发现商品评论功能里,他用了innerHTML直接渲染评论内容。我问他为什么不用textContent,他愣了一下说:“这样写起来方便啊。”方便,这两个字就是大多数安全漏洞的温床。后来我帮他改成了textContent,再往评论里塞<script>,页面上只显示一行纯文本,攻击脚本原形毕露。
说句实在话,javascript注入攻击不会消失,因为只要人类还在写代码,就会有人图省事把输入当代码用。但你要真想在web开发里活得久一点,记住一个原则就够了:永远不要信任用户的输入。不管是来自表单、URL、cookie还是HTTP头,所有数据在进入你的代码之前,都当它是恶意构造的。做到这一点,你至少能挡掉八成攻击。剩下的两成,靠的是持续更新依赖、定期做安全测试,以及——别在凌晨三点写代码的时候偷懒用eval。