Content Security Policy (CSP) Bypass
前言
内容安全策略, 全称: Content Security Policy (CSP)。
为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。
以白名单的机制对网站加载或执行的资源起作用。在网页中,这样的策略通过两种方法定义:
1. HTTP 头信息
Content-Security-Policy
的字段2. 网页的meta 元素定义
CSP虽然提供了强大的安全保护,但是他也造成了如下问题:Eval及相关函数被禁用、内嵌的JavaScript代码将不会执行、只能通过白名单来加载远程脚本。这些问题阻碍CSP的普及,如果要使用CSP技术保护自己的网站,开发者就不得不花费大量时间分离内嵌的JavaScript代码和做一些调整。
更多关于CSP的请参考: Content Security Policy 入门教程
CSP之所以可以绕过,是因为策略的制定不够合理,从而让攻击者有了可乘之机
下面分析四种不同等级的漏洞:
-
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>
';
服务端中定义了CSP策略 (允许的脚本来源) :
self
,(本地)
https://pastebin.com
,
example.com
,
code.jquery.com
,
https://ssl.google-analytics.com
将过滤后的include参数作为script脚本执行 (加载外部资源)。
且可以看到在响应头中有Content-Security-Policy
的字段, 里面包含了允许加载的外部资源:
漏洞利用
先说明一下策略 self, 他是允许服务端加载自身的网站资源, 如果不允许加载本网站的资源, 这样网站也没啥用了..
访问本地网站目录下的xss0A.js文件, 内容如下
alert(1);
然后看看CSP策略中允许的其他资源-----https://pastebin.com
这个被信任的网址是可编辑脚本的 !!! (在实战中, 要攻击A, 可以转为攻击A的CSP策略中的网址B, 从而进一步拿下A )
我们编辑一个js:
然后使目标机include该网址:
https://pastebin.com/raw/88f0vL4U
这样我们就可以在被目标网站信任的网站上做好攻击脚本, 诱使受害者点击即可以完成攻击。
-
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>
';
- 引申知识点:
除了常规值,script-src
还可以设置一些特殊值。注意,下面这些值都必须放在单引号里面。
unsafe-inline
:允许执行页面内嵌的<script>
标签和事件监听函数unsafe-eval
:允许将字符串当作代码执行,比如使用eval
、setTimeout
、setInterval
和Function
等函数。- nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行
- hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
nonce值的例子如下,服务器发送网页的时候,告诉浏览器一个随机生成的token。
Content-Security-Policy: script-src 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='
页面内嵌脚本,必须有这个token才能执行。比如服务端中的:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
服务端正是引用了nonce和unsafe-inline来防止攻击
漏洞利用
只要我们在注入js标签中加入nonce属性即可:
<script nonce='TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='>alert(1)</script>
不过这样直接攻击太明显了, 用户不会去点击模式的js标签的, 所以我们可以进一步对其进行包装等
-
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>
';
在High级别中, CSP策略只允许加载本地资源。
漏洞利用
既然CSP不允许请求外部资源, 我们只能从内部入手了, 可以看到在服务端的代码中执行本地的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();
});
}
-
clickButton()
:点击按钮后,在页面上创建<script src="source/jsonp.php?callback=solveSum"></script>
。 -
solveSum(obj)
:将answer写入页面。
获取按钮,点击触发clickButton
事件,这个受到 jsonp.php 的callback参数控制, 当参数值为solveSum函数的时候, 将会调用 solveSum函数进行计算。
攻击者可以通过修改callbakc的参数值为恶意脚本, 从而完成攻击。
比如通过注入将上面high.js代码第三行的
s.src = "source/jsonp.php?callback=solveSum"; 改为: s.src = "source/jsonp.php?callback=alert(1)";
上面是思路, 接下来我们来具体分析网站:
点击solve the sum按钮, 抓个包:
将参数callback的值改为:
然后forward, 弹窗成功:
-
Impossible
服务端核心代码:
<?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级别中, 和high级别也是差不多, 区别就在于是用impossible.js
我们可以看看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();
});
}
可以看到, 在clickButton函数中, 去掉了high级别的缺陷----应用callback参数, 转而应用jsonp_impossible.php
直接把js写固定了, 杜绝了注入漏洞。
上一篇: SQL Injection
下一篇: C/C++ CGI处理文件上传