xss之CSP bypass
Posted J0hnson666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了xss之CSP bypass相关的知识,希望对你有一定的参考价值。
0x01 庐山真面目 —— 何为CSP
看到标题,是否有点疑惑 CSP 是什么东西。简单介绍一下就是浏览器的安全策略,如果 标签,或者是服务器中返回 HTTP 头中有Content-Security-Policy
标签 ,浏览器会根据标签里面的内容,判断哪些资源可以加载或执行。
为了研究CSP(Content Security Policy)对XSS攻击的防护作用,他们做了对CSP安全模型的首次深入分析,分析了CSP标准中对web缺陷的保护能力,帮助识别常见的CSP策略配置的可能错误,并且展示了三类能使CSP无效化的绕过方法。
这次研究所采用的材料基于从Google搜索的索引文件中所提取到的CSP策略,从语料库中提取了大约1060亿页的页面,其中39亿是受CSP保护的,其中确认了26,011个独立的策略。他们发现,由于策略配置错误和白名单条目不安全,这些策略中至少有94.72%无法缓解XSS攻击。基于这样的研究结果,他们建议在实践中部署CSP时,使用基于nonce的方法而不是传统的白名单。并且,他们提出了名为“strict dynamic”的新特性,这是当前在Chromium浏览器中实现的CSP3规范的一个新特性。以下会详细讲述为何要使用这种策略和特性。
首先,何为CSP?我们知道,内容安全策略(CSP)是一种声明机制,允许Web开发者在其应用程序上指定多个安全限制,由支持的用户代理(浏览器)来负责强制执行。CSP旨在“作为开发人员可以使用的工具,以各种方式保护其应用程序,减轻内容注入漏洞的风险和减少它们的应用程序执行的特权”。当前,CSP还处在快速的发展期,目前正在进行规范中的版本是CSP3,CSP标准由用户代理选择实现。例如,Chromium具有完整的CSP2支持,并且实现了CSP3的大部分工作草案,仅在某些情况下可能会落后于实验中的某些特性,而MozillaFirefox和基于WebKit的浏览器则刚刚获得了完整的CSP2支持。在实际使用中,CSP策略在Content-Security-Policy HTTP响应头或元素中提供。
简单来说CSP
CSP(Content Security Policy) 是一种用来防止 XSS 攻击的手段, 通过在头部添加 Content-Security-Policy 的相关参数, 来限制未知(不信任)来源的 javascript 的执行从而防止 XSS 攻击. 本题并不是要介绍这种技术有什么漏洞, 而且本题所导致的 XSS 攻击都是因为开发者的不正当配置所致. 所以本题仅仅让你能成功执行一个弹框的 XSS 注入即可.0x02 DVWA-low
如果不看源码的话。看检查器(F12),也可以知道一些被信任的网站。首先我们来查看一下源码
<?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>
';
观察头信息,罗列允许JavaScript的网站 当然你也可以从这里开发者工具来看到这个头
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;";
这个头部信息的具体含义可以参考这个参考文档:https://content-security-policy.com/, 这里直接理解就是能从 https://pastebin.com 等网站加载 javascript, 你可以试试从自己的搭建的服务器中加载 javascript, 结果如下:
可以明显的看到, 这个 javascript 的加载被 blocked 了(估计 Firefox 的插件 No-script 也是基于这个的). 既然不能从未指定的源加载 javascript, 那我可以从它信任的源, 比如 https://pastebin.com 中来加载. 可能有人不知道这个网站是什么, 我开始也不知道的, 但你打开就会发现这就是个共享粘贴板, 允许你粘贴任何文本内容.
那我们就可以在这个粘贴板网站中写入一段恶意 javascript, 比如 alert(123), 然后, 通过该网站的 raw 形式(原生)来显示, 我当时生成的网址是这个:https://pastebin.com/tV3VNjiB, 然后你就可以在 URL 栏中输入这个网址, 让浏览器将这个远程加载文本当作 javascript 来执行
结果如下:
看到嘛,在pastebin上保存的js代码被执行了。那就是因为pastebin网站是被信任的。攻击者可以把恶意代码保存在收信任的网站上,然后把链接发送给用户点击,实现注入。
Medium
通过浏览器或者Burp抓包可以看到:
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';
这次使用了两个新的参数(self 不算哈). 其中 'unsafe-inline' 代表可以执行诸如 onclick 等事件或 script 标签内的内容这类 javascript, 而后者就是指如果你要使用 script 标签加载 javascript, 你需要指明其 nonce 值, 比如 <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert('hacked')</script>
这个就能正常加载, 从而造成 XSS 注入.
但需要注意的是, 如果你测试 <img src="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=" onerror=alert('hacked')>
这类在标签内执行的 javascript, 会得到如下结果:
即:
错误提示告诉我们, 因为头部指定了 nonce 值, 所以自动忽略了 'unsafe-inline' 这个参数. 因此可以判断这两个参数是不能共存的, 而且如果共存, 后者nonce 值是会覆盖前者'unsafe-inline' 的.
这里还有个有意思的地方, 你看那个 nonce 的值很明显是个 base64 编码, 我就无聊去解码了一下, 嗯... 原文"TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA="转换后是 "Never going to give you up", 还是给出题者 666. 不过呢, 正确的防御方式下的 nonce 值不应该是个固定值, 而是应该是个随机生成的值, 这样才能真正达到防止 XSS 的目的.
High
这个级别已经没有输入框了, 不过题目已经给了足够多的提示. 首先先看一下 CSP 头, 发现只有script-src 'self';
, 看来只允许本界面加载的 javascript 执行. 然后研究了一下这个点击显示答案的逻辑(逻辑在
source/high.js
里), 大致如下:
在点击网页的按钮使 js 生成一个 script 标签,src 指向 source/jsonp.php?callback=solveNum
。document 对象使我们可以从脚本中对 html 页面中的所有元素进行访问,createElement() 方法通过指定名称创建一个元素,body 属性提供对 <body>
元素的直接访问,对于定义了框架集的文档将引用最外层的 <frameset>
。appendChild() 方法可向节点的子节点列表的末尾添加新的子节点,也就是网页会把 “source/jsonp.php?callback=solveNum
” 加入到 DOM 中。
源码中定义了 solveNum 的函数,函数传入参数 obj,如果字符串 “answer” 在 obj 中就会执行。getElementById() 方法可返回对拥有指定 ID 的第一个对象的引用,innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。这里的 script 标签会把远程加载的 solveSum({"answer":"15"})
当作 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();
});
}
本来应该是没办法修改在服务器的 jsonp.php 文件的(除非结合别的漏洞, 拿 shell 后修改). 然而, 我后来在查看服务端源码的时候发现了这个:
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
# 剩余的显示代码
666, 竟然还偷偷用POST方式来接收 include 参数的值(不清楚是不是作者复用了之前 Medium 的代码). 总之, 这肯定能作为一个注入点, 我开始打算用简单粗暴的 <script>alert(‘hacked’)</script>
来搞定的, 谁知道, 这种是属于 ‘unsafe-inline’ 形式的, 所以被限制执行了:
既然如此的话, 那我就利用 src 吧,payload在下面。
Payload: POST方式传参include=<script src="source/jsonp.php?callback=alert('xss');"></script>
成功:
这个即使你不看源码, 你做几个测试也会发现, 那个 callback 参数可以被我们所操控而生成任何你想要得到的结果。这就是最后导致CSP被绕过而产生漏洞的原因:
Impossible
该级别主要还是修复了 callback 参数可被控制问题(毕竟这是问题根源):
这个级别是 high 级别的加强,JSONP.php 调用的回调函数callback是硬编码的,CSP 策略被锁定为只允许外部脚本。服务器
<?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>
';
客户端
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();
});
}
参考链接
以上是关于xss之CSP bypass的主要内容,如果未能解决你的问题,请参考以下文章
Security ❀ CSP Bypass 内容安全策略绕过
Security ❀ CSP Bypass 内容安全策略绕过
Security ❀ CSP Bypass 内容安全策略绕过