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 内容安全策略绕过

" XSS易容术---bypass之编码混淆篇+辅助脚本编写"

javascript CSP-bypass.js

Xss Bypass