使用XSSI攻击获取敏感信息

in 漏洞 with 0 comment

*本文由本人原创,首发于云众可信微信公众号
CSP(内容安全策略)是防御XSS的绝佳方法,不同于以往过滤关键字的手段,它要求将内嵌脚本独立到外部文件中,并通过白名单的方式指定了哪些外部资源是可加载的。CSP在协议层面杜绝了XSS攻击,一度被誉为XSS终结者。但有趣的是,当敏感脚本被独立到文件中时,就很容易造成另一种漏洞——XSSI(跨站脚本包含)。当无法对XSS进行利用时,我们不妨对这类漏洞进行深入探究。

XSSI的原理

XSSI与XSS、CSRF同为针对客户端攻击的漏洞,它们的不同点在于,XSS是在受害者页面中放置恶意代码,CSRF是跨域在受害者页面执行一个恶意动作,而XSSI旨在跨域包含含有敏感数据的脚本以获取敏感数据。
众所周知,同源策略(SOP)是浏览器最核心最基本的安全策略,同源是指协议、域名、端口相同,非同源的资源不能互相读写。而script标签是允许跨域加载资源的,如果某个网站的动态脚本、文件或响应中包含某些敏感信息(比如唯一标识符、个人资料、防御CSRF的Token),便有信息泄露的风险。传统的XSSI攻击场景如下:恶意页面B使用script标签包含了目标网站A用来储存敏感数据的信息源C(可能是动态脚本、文件或响应),当攻击者引导受害者访问B时,由于受害者此时在A处于登录态,B可以轻松获取C中包含的受害者的敏感信息。
耳熟能详的JSON和JSONP劫持其实是XSSI最为容易利用的一种漏洞场景,本文对此不做深入讲解。在更多的场景中,我们想要获取的敏感信息无法直接被Javascript读取,需要精心构造Payload以窃取它们,甚至强行将其内容变为可读的Javascript变量,这也是下文将重点讨论的部分。

获取JS脚本中的敏感信息

根据XSSI的定义,静态JS文件也可能泄露敏感信息,比如一个Secret_key:

<!--恶意页面-->
<script type="text/javascript" src="http://127.0.0.1:8802/leaked/js1.js">
    //注意这里是被恶意包含的文件
</script>
<script type="text/javascript">
    $('#leaked_content').text(Secret_key);
</script>

恶意页面地址为127.0.0.1:80/index.html,端口号不同已经跨域。
2.png
不过显然这样的XSSI意义不大,因为直接读取JS文件也可获得。XSSI多针对动态JS文件进行攻击,因为这样的脚本通常在用户处于登录态时容易包含敏感信息。比如这样的形式:

//储存敏感信息的js
var session = getSession();

在敏感数据存在于全局变量中时,这种窃取极为容易进行。当然,敏感数据更多的是存在于局部变量中。

//储存敏感信息的js
(function(){
    var session = getSession();
    doSomeThing(session);
})();

很多网站会将一些基本的功能函数写入一个JS文件,以便在写业务逻辑时方便重用,如上图的doSomeThing函数,我们可以通过重写此函数的方式获取敏感数据。

<!--恶意页面-->
<script type="text/javascript">
    window.data = '';
    function doSomeThing(d){
        window.data = d
    }
</script>
<script type="text/javascript" src="http://127.0.0.1:8802/leaked/js1.js">
    //注意这里是被恶意包含的文件
</script>
<script type="text/javascript">
    $('#leaked_content').text(window.data);
</script>

如图,传入doSomeThing函数的内容已经被泄露:
3.png
如果没有自定义函数可以进行重写,我们可以通过重写原型链来获取数据。如果读者对JS没有研究,可以暂且粗劣的理解为内置函数。比如下面的情况:

//储存敏感信息的js
(function(){
    function setInfo() {
        var session = getSession();
        var sid = session.split(/;|=/)[1];
        //...
        //do some thing...
    }
    //...
    setInfo();
    //...    
})();

由于session是String类型的,在split时会调用String原型链中的split方法,此时我们只需重写此方法即可。

<!--恶意页面-->
<script type="text/javascript">
    window.data = '';
    String.prototype.split = function(param) {
        window.data = this.toString();
        return [null, this];
    }
</script>
<script type="text/javascript" src="http://127.0.0.1:8802/leaked/js1.js">
    //注意这里是被恶意包含的文件
</script>
<script type="text/javascript">
    $('#leaked_content').text(window.data);
</script>
</html>

如下图,我们已经成功的获取了Session值。
4.png
针对不同的情况,我们可以使用不同的方法来获取敏感数据,以上仅列举了其中几种情况。那么应该如果快速分辨一个JS文件是否为动态JS文件?当有Cookie和无Cookie时请求所响应的文件内容不同时,即可确定这是一个动态JS文件了,当然并不是每一个动态JS文件都可以被利用。我们可以使用Burpsuite的插件DetectDynamicJS来完成这项工作,此插件已在github上开源。
链接地址:https://github.com/portswigger/detect-dynamic-js

获取其他文件或响应中的敏感信息

响应中的敏感信息主要涉及到JSON劫持,这里仅提一个tip:对于一些开源框架的get注入,若注入点存在于需要身份验证才能访问的页面(比如后台),可以通过读取注入后返回的JSON从而拿到敏感数据。
下面将详细探讨如何获取文件中的敏感信息。文件内容并不能直接作为一个Javascript变量的值读取,以及文件是有多行的,这使得获取文件内容十分困难。
有研究员在Paper中提出使用script标签的charset属性将包含的文件编码为UTF-16,其目的在于强制文件的所有内容连为一体,变为一个未定义的Javascript变量。然后通过在window域内使用onerror捕获错误信息(此错误信息一定为已编码的文件内容 is not defined),再进行解码即可。此举其实是为了防止符号会引起Javascript出现其他异常,例如英文逗号会截断文件内容,报错只会显示逗号前的内容未定义;而中文逗号则会直接提示非法字符,从而获取不到任何敏感信息。这其实和在利用PHP伪协议读取文件源代码时先base64编码有异曲同工之妙。然而此种方法如今在主流浏览器上已经无法成功了。
此处我们使用另一种方法来获取敏感信息:倘若敏感文件中有我们能够操控的字段,我们就可以利用JS的语法来构造函数或多行字符串变量以窃取可控字段之间的内容。
假设有两个字段是可控的:

//源文件
此处是可控变量1
搜索到t1ddl3r的身份证号码为1234567890,电话为987654321。
以上为t1ddl3r的所有信息。
此处是可控变量2

我们可以使用ECMAScript 6引入的箭头函数来包含它们:

//源文件
//经过我们注入两个变量构造后的文件
此处是=i=>/*
搜索到t1ddl3r的身份证号码为1234567890,电话为987654321。
以上为t1ddl3r的所有信息。
此处是*/i

可以看到我们已经定义了一个叫做此处是的函数,可以使用toString方法查看它的内容:
假设可控字段只有一处,为下方t1ddl3r处:

搜索到t1ddl3r的身份证号码为1234567890,电话为987654321。
以上为t1ddl3r的所有信息

此时我们可以利用反引号来构建多行字符串,将t1ddl3r替换为=`,t1ddl3r

搜索到=`,t1ddl3r的身份证号码为1234567890,电话为987654321。
以上为=`,t1ddl3r的所有信息

如图,我们已经定义了一个名为搜索到的多行字符串变量:
5.png
当然此种利用方式较为苛刻,不仅要有可控变量,还要保证我们存值的变量前没有任何异常语法干扰,这是因为Javascript一旦遇到异常就会停止往后解析。比如下面这种情况:

s_name,s_number=`
test,1234567890
s_name,s_number=`

由于我们存入多行字符串的可控变量s_number前的s_name被解析为一个未定义变量,会引发错误而停止向下解析:
6.png

结语

永远不要将敏感数据存在某些文件中,响应敏感JSON的API也应该仅接受POST请求。或许XSSI不是那种可以获取服务器权限的漏洞,但其所造成的敏感信息泄露的安全问题是不可忽视的。

Responses