是什么

XSS(Cross-Site Scripting,跨站脚本):攻击者把恶意 JavaScript 注入到网页中,当其他用户浏览该页面时,脚本在其浏览器中执行。

危害:

  • 窃取 Cookie / 会话令牌(进而冒充用户);
  • 读取页面上的敏感信息;
  • 以用户的身份发起请求(转账、发帖、改密码);
  • 重定向到钓鱼网站;
  • 植入挖矿脚本或键盘记录器。

三种类型

一、反射型 XSS(Reflected)

恶意脚本作为请求参数的一部分,被服务器"反射"回页面中。

https://example.com/search?q=<script>alert(1)</script>

如果服务器把 q 的值直接插入 HTML 响应,脚本就会执行。

特点:需要诱导用户点击特制的链接(通常通过钓鱼邮件、社交工程)。

二、存储型 XSS(Stored)

恶意脚本被保存在服务器上(如评论区、用户资料、论坛帖子),之后每次有用户访问该页面都会执行。

危害最大——因为不需要诱导点击特定链接,访问正常页面就会中招。

经典案例:早期社交网站的"蠕虫"——用户在个人资料中植入脚本,访问其主页的人被感染,并以他们的名义继续传播。

三、DOM 型 XSS

漏洞存在于客户端 JS 代码中——脚本直接从 location.hash、document.referrer、或 DOM 中读取数据,未经处理就写入页面。

// 危险
document.getElementById('msg').innerHTML = location.hash;

特点:恶意数据可能根本不经过服务器(如在 URL 的 # 片段中),因此服务端过滤对它无效。

防御

一、输出编码(最核心)

根据输出上下文,对数据进行相应的编码。

这是最重要的原则——XSS 的本质是"数据被当成代码执行",编码就是让数据"保持为数据"。

上下文需要转义
HTML 元素内容< > & " '
HTML 属性同上,且属性值要加引号
JavaScript 中按 JS 字符串转义,或用 JSON.stringify
URL 参数URL 编码
CSSCSS 转义

使用现代框架:React、Vue、Angular 等默认对插值内容进行 HTML 转义——这自动防御了大部分 XSS。

但要注意例外:

  • React 的 dangerouslySetInnerHTML;
  • Vue 的 v-html;
  • Angular 的 bypassSecurityTrustHtml。

这些"逃生舱口"会绕过转义,使用时必须确保内容可信或已净化。

二、内容安全策略(CSP)

通过 HTTP 响应头告诉浏览器:只允许加载和执行来自特定来源的脚本。

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com

关键特性:

  • 禁止内联脚本(<script>...</script> 和 onclick= 等内联事件处理器)——这是 XSS 的主要载体;
  • 禁止 eval();
  • 限制脚本来源白名单。

如果必须允许内联脚本,可以用 nonce(每次响应生成一个随机值)或 hash 来精确授权。

CSP 是很强的纵深防御——即便存在 XSS 漏洞,也能阻止恶意脚本执行或外传数据。

三、HttpOnly Cookie

Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Lax

HttpOnly 使 JS 无法读取该 Cookie——即使发生 XSS,攻击者也无法窃取会话令牌。

注意:这不能阻止 XSS 的其他危害(如以用户身份发请求),但能防止最直接的凭据窃取。

四、输入验证与净化

对 HTML 内容(如富文本编辑器),用专门的净化库(如 DOMPurify、sanitize-html)移除危险的标签和属性。

不要自己写正则去"过滤"——HTML 的绕过方式太多,自己写的过滤器几乎必然有漏洞。

注意:输入验证是补充,不能替代输出编码——因为同一个数据可能输出到多个不同的上下文。

五、避免危险 API

  • 避免 innerHTML、outerHTML、document.write();
  • 需要插入文本时用 textContent;
  • 避免 eval()、new Function()、setTimeout('string')。

一个现实观察

现代前端框架的普及,大幅降低了 XSS 的发生率——因为默认转义。

XSS 现在更多出现在:

  • 使用了 dangerouslySetInnerHTML / v-html 的地方;
  • 服务端渲染的模板中漏掉转义;
  • 富文本内容的展示;
  • 第三方脚本的引入(这属于供应链风险)。

一句话

永远不要把不可信数据当作代码执行。 在输出时按上下文编码,是第一道也是最有效的防线。

说明:本文为 Web 安全的知识整理。