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旨在“作为开发人员可以使用的工具,以各种方式保护其应用程序,减轻内容注入漏洞, 而且本题所导致的 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> '; 
xss之CSP bypass

观察头xss之CSP bypass

$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, 结果如下:
xss之CSP bypass

可以明显的看到, 这个 javascript 的加载被 blocked 了(估计 Firefox 的插件 No-script 也是基于这个的). 既然不能从未指定的源加载 javascript, 那我可以从它信任的源, 比如 https://pastebin.com 中来加载. 可能有人不知道这个网站是什么, 我开始也不知道的, 但你打开就会发现这就是个共享粘贴板, 允许你粘贴任何文本内容.

那我们就可以在这个粘贴板网站中写入一段恶意 javascript, 比如 alert(123), 然后, 通过该网站的 raw 形式(原生)来显示, 我当时生成的网址是这个:https://pastebin.com/tV3VNjiB, 然后你就可以在 URL 栏中输入这个网址, 让浏览器将这个远程加载文本当作 javascript 来执行

结果如下:

xss之CSP bypass
xss之CSP bypass

看到嘛,在pastebin上保存的js代码被执行了。那就是因为pastebin网站是被信任的。攻击者可以把恶意代码保存在收信任的网站上,然后把链接发送给用户点击,实现注入。

Medium

通过浏览器或者Burp抓包可以看到:

xss之CSP bypass
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA='; 

这次使用了两个新的参数(self 不算哈). 其中 'unsafe-inline' 代表可以执行诸如 onclick 等事件或 script 标签内的内容这类 javascript, 而后者就是指如果你要使用 script 标签加载 javascript, 你需要指明其 nonce 值, 比如 <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert('hxss之CSP bypass
即:xss之CSP bypass

错误提示告诉我们, 因为头部指定了 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’ 形式的, 所以被限制执行了:xss之CSP bypass

xss之CSP bypass

既然如此的话, 那我就利用 src 吧,payload在下面。
Payload: POST方式传参include=<script src="source/jsonp.php?callback=alert('xss');"></script>

成功:
xss之CSP bypass
这个即使你不看源码, 你做几个测试也会发现, 那个 callback 参数可以被我们所操控而生成任何你想要得到的结果。这就是最后导致CSP被绕过而产生漏洞的原因:
xss之CSP bypass

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();     }); } 

参考链接

版权声明:玥玥 发表于 2021-08-10 17:13:27。
转载请注明:xss之CSP bypass | 女黑客导航