游客发表
去年冬天,入漏一个做电商的入漏朋友半夜给我打电话,说后台莫名其妙多了一批0元订单。入漏我让他把最近的入漏用户反馈截图发过来,一眼就看到了问题——商品评论区的入漏昵称里,藏着一段带<script>的入漏代码。这就是入漏典型的XSS注入。
XSS,入漏全称跨站脚本攻击。入漏原理不复杂:网站把用户输入的入漏内容直接输出到页面上,没做任何处理。入漏攻击者往输入框里塞一段JavaScript,入漏这段代码就会在其他用户的入漏浏览器里执行。执行什么?入漏偷Cookie、改页面、入漏跳转钓鱼网站,甚至直接模拟用户操作转账。
很多人以为XSS只是弹个alert(1)的玩具漏洞。早年间确实有人这么玩,弹窗证明存在漏洞就收工。但现在的XSS利用链已经成熟到令人不安。我见过一个真实案例:攻击者在一个论坛的签名档里植入了一段经过混淆的脚本,专门检测访问者的浏览器指纹,如果是管理员权限的账号,就静默发送一个请求,把管理后台的CSRF token偷出来。整个过程用户毫无感知,页面没有任何异常。
反射型XSS最常见。你点了一个带参数的链接,参数里藏着脚本,服务器原样返回,浏览器执行。这种攻击需要诱导点击,单次生效,像钓鱼。
存储型XSS更危险。脚本被存进数据库,所有访问该页面的用户都会中招。我见过一个博客系统,留言板的“网站”字段没有过滤,攻击者填了一个javascript:alert(document.cookie),结果每个鼠标划过这条留言链接的人都触发了脚本。存储型XSS不需要诱导,它像一颗埋在页面里的雷。
DOM型XSS比较特殊,服务器端甚至看不到攻击载荷。前端JavaScript从location.hash或document.referrer里取数据,直接塞进innerHTML。这类漏洞在单页应用里特别多,因为路由参数经常被直接渲染。
很多人以为把<转成<就安全了。不够。不同上下文需要不同的编码方式。HTML标签内、HTML属性内、JavaScript代码块内、CSS样式内、URL参数内,各有各的转义规则。把用户输入放进href属性时,光转义尖括号没用,javascript:协议照样能执行。
我自己的习惯是:输出到HTML用HTML实体编码,输出到JS用\x十六进制转义,输出到URL用encodeURIComponent。别偷懒,别混用。
另一个有效手段是CSP(内容安全策略)。设置Content-Security-Policy: script-src 'self',浏览器就只执行同源脚本,内联脚本和外部恶意脚本全被拦。CSP不是万能的,配置不当反而会绕过,但它确实能大幅抬高攻击成本。
HttpOnly标记也值得提。给Cookie加上HttpOnly,JavaScript就读不到document.cookie。这样即使XSS成功执行,攻击者也偷不走会话凭证。当然,如果攻击者直接发请求模拟操作,HttpOnly拦不住,但至少能挡住最常见的那类利用。
富文本编辑器是XSS的重灾区。用户需要发加粗、链接、图片,就不能把HTML全转义。这时候得用白名单过滤,只允许特定标签和属性。但白名单库本身也可能有漏洞,比如老版本DOMPurify就被爆过mXSS(变异型XSS)——浏览器解析HTML时会把某些看似安全的标签重新组合成可执行脚本。过滤和解析之间的差异,是攻击者最爱钻的空子。
我现在的做法是:富文本内容在服务端用成熟库过滤一遍,前端渲染时再用CSP兜底。两层防御,不指望任何单一措施绝对可靠。
XSS活了二十多年,依然排在OWASP Top 10里。不是因为它难防,是因为太多开发者觉得“我转义了尖括号就没事了”。漏洞往往藏在那些你以为已经处理过的地方——一个没过滤的URL参数、一个直接拼接的模板字符串、一个忘了加引号的HTML属性。写代码时多问一句:这个数据从哪来,到哪去,中间有没有人动过手脚。
友情链接