阻止跨站点脚本怎么处理
一个搜索框,阻止用户输入 <script>alert('xss')</script>,跨站页面直接把它渲染成 HTML,点脚弹窗就出来了。本处这不是阻止漏洞演示,这是跨站很多网站每天都在发生的事。跨站脚本(XSS)的点脚本质就一句话:用户提供的数据被浏览器当成了代码执行。阻止它的本处核心思路也很简单——让数据永远是数据,别让它变成代码。阻止
最有效的跨站办法是输出转义。用户提交的点脚内容存进数据库时不用动,但在拼接到 HTML 页面的本处那一刻,把 < 变成 <,阻止把 > 变成 >,跨站把引号也转掉。点脚浏览器看到 <script> 只会显示成文本,不会执行。不同语言都有现成的函数,PHP 里是 htmlspecialchars(),Java 里可以用 HtmlUtils.htmlEscape(),Python 的 html.escape() 也能干这事。关键是别偷懒,每一个输出点都要过一遍。
但光靠转义不够。有些场景你确实需要允许部分 HTML,比如富文本编辑器。这时候就得用白名单过滤,而不是黑名单。黑名单永远漏,今天禁了 <script>,明天人家用 <img src=x onerror=alert(1)> 照样弹。白名单只允许 <p>、<b>、<i> 这些安全标签,其他一律转义或删掉。OWASP 的 Java HTML Sanitizer 或者 DOMPurify 这类库就是干这个的,别自己写正则,写不过攻击者的。
还有一个常被忽略的点:Cookie 的 HttpOnly 属性。就算 XSS 真的发生了,攻击者想用 document.cookie 偷 session,HttpOnly 能让他拿不到。这不算阻止 XSS,但能大幅降低危害。我见过一个后台系统,XSS 过滤做得稀烂,但所有敏感 Cookie 都设了 HttpOnly 和 Secure,攻击者折腾半天只能弹个窗,拿不到实际权限。
Content Security Policy(CSP)是另一层防线。在 HTTP 响应头里加 Content-Security-Policy: default-src 'self',浏览器就只执行同源脚本,内联的 <script> 和 onclick 这类事件处理器直接拒绝。这招对反射型和存储型 XSS 都有效,缺点是配置麻烦,老代码里一堆内联脚本得慢慢改。但新项目强烈建议上 CSP,哪怕先设成报告模式,观察一段时间再强制。
反射型、存储型、DOM 型,三种 XSS 的触发点不同,防御的侧重点也不一样。反射型主要靠输出转义和 CSP;存储型还得在入库前做一次过滤,因为数据可能被其他系统读取;DOM 型最麻烦,问题出在 JavaScript 里,比如 innerHTML 直接赋值用户输入。这种就得改代码,用 textContent 代替 innerHTML,或者用 createElement 拼 DOM。
实际项目里,我习惯在框架层面统一处理。比如 Spring Boot 里配一个全局的 XSS 过滤器,或者用 Thymeleaf 默认的转义机制。前端 React 和 Vue 默认也会转义,但 dangerouslySetInnerHTML 和 v-html 是例外,用的时候得心里有数。别指望一个中间件解决所有问题,输出点千奇百怪,该手动转的地方一个都跑不掉。
测试的时候,别只用 alert(1)。试试 <img src=x onerror=alert(1)>,试试 "><script>alert(1)</script>,再试试编码绕过,比如 %3Cscript%3E。工具可以用 Burp Suite 的扫描器,或者开源的 XSStrike。但工具报出来的不一定都是真漏洞,得手动验证。我见过扫描器把 <b> 标签当成 XSS 报出来的,也见过真正的 DOM 型 XSS 被漏掉的。
说到底,阻止 XSS 没有银弹。转义是基本功,CSP 是保险绳,HttpOnly 是止损线,白名单过滤是特殊场景的补丁。四样都做,基本能挡住 99% 的攻击。剩下的 1%,靠的是代码 review 时多问一句:这个变量从哪来,到哪去,中间有没有被当成代码?