0x00 CSP Bypass
CSP(Content-Security-Policy),即内容安全策略,通俗地说就是开发者告诉客户端,只能执行来自哪里的外部资源,主要是为了缓解XSS和数据注入攻击用的。
开发者可以配置一定的策略,限制客户端浏览器只能执行一定范围内的脚本等资源。
如果设置了CSP但有的浏览器不支持,那浏览器就会直接忽略。
DVWA中的CSP Bypass主要是针对一些开发者对CSP的配置错误,导致攻击者依旧可以绕过防御策略。
0x01 Low
源码分析
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, jquery and google analytics.
header($headerCSP);
# https://pastebin.com/raw/R570EE00
?>
<?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>
浏览器看到页面是一个可以输入链接并提交包含。 可以看到源码在HTTP头中设置了CSP,如下:
"Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"
这表示只有当前网站、pastebin.com网站、jquery和google analytics这些来源的js脚本才可以执行。
我们可以查下https://pastebin.com 网站,发现是一个快捷发步信息的网站,可以写一个alert(1),然后点击Create New Paste按钮,就会生成并跳转到一个URL,链接返回的页面是我们所填的内容alert(1)。
可以看到源码中会将这个链接用前端script标签加载外部js脚本的方式,去把链接内容包含进来,所以我们将上面在网站https://pastebin.com(https://pastebin.com) 处生成的链接填写到Low级别的CSP Bypass页面输入框里,然后点击include即可弹窗。
解题思路
可以按照上面所分析的步骤:
- 在https://pastebin.com(https://pastebin.com) 网站上生成一个内容为alert(1)的消息。
- 复制生成的链接。
- 在DVWA CSP Bypass输入框中输入,点击Include按钮提交。
注:理论上如此做是可以弹窗的,但是实验的时候并没有。打开控制台提示”因为 MIME 类型(text/plain)不匹配(X-Content-Type-Options: nosniff)“,查了一下是pastebin.com那个网站返回的HTTP头中设置了X-Content-Type-Options: nosniff,直接限制客户端一定要按照设定的MIME类型(text/plain)去检查,所以现在是弹不了窗了,但原理理解了就好!
其实源码中还提供了官方做好的信息链接,但我试了也无法弹窗。
https://pastebin.com/raw/R570EE00
0x02 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>
';
可以看到CSP发生了变化,符合要求的js脚本来源包含:
- unsafe-inline(允许使用内联资源)
- 内联
- javascript:URL
- 内联事件处理程序(如onclick=’alert(1)’)
- 内联
- nonce-source(仅允许特定的内联脚本块)
- nonce=“TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=”
解题思路
根据资源限定,我们只需要构造script标签,并且携带nonce属性即可。 构造如下代码:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
直接输入输入框,弹窗成功!
0x03 High
源码分析
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>
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策略只允许“self”,也就是只允许当前页面的 js 脚本。
源码中js脚本文件中,按照流程是
- 监听click事件,当确认点击时,调用执行clickButton()函数
- clickButton()函数会写入script标签去导入jsonp.php,并且链接带有变量callback=”solveSum”
# jsonp.php
<?php
header("Content-Type: application/json; charset=UTF-8");
if (array_key_exists ("callback", $_GET)) {
$callback = $_GET['callback'];
} else {
return "";
}
$outp = array ("answer" => "15");
echo $callback . "(".json_encode($outp).")";
?>
- 调用solveSum(obj)函数将obj[‘answer’]=15插入到当前的HTML文件中。
从流程可以看出callback实际上是调用了一个js函数并执行。所以可以直接抓包改callback的值,改成自己想要的js函数即可。
解题思路
用Burpsuite拦截,将get请求中的变量callback的值修改为alert(1)即可,修改后如下:
GET /dvwa/vulnerabilities/csp/source/jsonp.php?callback=alert(1) HTTP/1.1
Host: 127.0.0.1:9000
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://127.0.0.1:9000/dvwa/vulnerabilities/csp/
Cookie: security=high; PHPSESSID=fbag9cl6ld7igjdjkdetgprhlv
修改后点击发送,弹窗成功!
0x04 Impossible
源码分析
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();
});
}
jsonp.php
<?php
header("Content-Type: application/json; charset=UTF-8");
$outp = array ("answer" => "15");
echo "solveSum (".json_encode($outp).")";
?>
简单对比一下High和Impossible级别,就只是去掉了容易被利用的callback变量,直接将answer=15写死在代码里了。
解题思路
无。
0x05 小结
防御方法:
- 能不给用户输入的就尽量不要给,特别是一些敏感数据操作时,尽量不要留有用户执行函数的机会。
- 实在需要用户输入的地方需要严格控制函数关键词,过滤危险函数。