漏洞考古学:利用陈年未知服务端浏览器的技术探险

本文详细记录了作者在漏洞赏金项目中,如何通过用户代理检测、WebKit版本识别、历史漏洞挖掘,最终利用JSArray::sortCompactedVector的UAF漏洞实现远程代码执行的完整技术链,涉及内存操作、JIT机制绕过等深层技术细节。

漏洞考古学:利用陈年未知服务端浏览器

作者:Alex Chapman
发布日期:2024-05-08
标签:漏洞利用

在近期参与漏洞赏金项目时,我发现了一个有趣的API端点:它能够渲染用户提供的HTML并执行其中包含的JavaScript。由于服务端浏览器漏洞利用是我过去几年的研究重点,我立即开始探索这个新发现的功能。本文将详细记录我如何研究并最终利用了一个距今近十年的服务端浏览器漏洞。

侦察阶段

1
Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) Safari/538.1

这个结果信息量有限。通常至少会看到Chrome/124.0.0.0或Firefox/125.0等标识浏览器引擎和版本的信息,但这次一无所获。

由于具备JavaScript执行能力,我可以通过浏览器特定API具体识别引擎,例如Chrome的Error.captureStackTrace、Firefox的document.preferredStyleSheetSet以及WebKit(Safari)的webkitConvertPointFromPageToNode。这次目标似乎是基于WebKit的引擎。

基于此,我的第一个(后来证明是错误的)假设是目标可能使用了PhantomJS。我曾为CVE-2014-1303编写过利用代码,于是将其打包发送到HTML服务中。没有反应……尽管为了保险起见我又尝试了两三次(甚至七次)。

无奈接受目标不是PhantomJS的事实后,我开始确定其运行的WebKit大致版本。caniuse.com的浏览器对比功能在此非常有用。选择几个Safari版本后,我可以轻松比较各版本的功能支持情况:

![浏览器功能对比截图]

caniuse.com和MDN的数据存在一些差异,但结合这两个来源可以创建基础脚本来确定使用的WebKit大致版本:

1
2
3
4
5
6
7
var version;
version = !version && typeof fetch == "function" ? "Safari WebKit 10.1" : version;
version = !version && typeof String.prototype.includes == "function" ? "Safari WebKit 9" : version;
version = !version && typeof document.currentScript == "object" ? "Safari WebKit 8" : version;
version = !version && typeof window.requestAnimationFrame == "function" ? "Safari WebKit 7" : version;

console.log(version);

结果显示服务使用的WebKit对应Safari约8版本,绝对时间在2014至2015年左右。是的,近十年前的技术!是时候动手使用我的漏洞挖掘工具了。

研究阶段

已知大致的WebKit版本和代码开发时间范围后,我可以开始挖掘旧的漏洞报告、概念验证(PoC)和利用代码。搜索该时期的WebKit漏洞出现了大量关于索尼PlayStation越狱的结果。PS Vita于2011年发布,PS4于2013年发布,越狱这些设备的常见入口点就是WebKit。这对我来说是个好消息,因为有许多PS越狱利用的正是我目标时期的WebKit版本。

经过数小时阅读各种越狱社区网站、论坛、wiki和GitHub仓库,我发现了几个可行的漏洞候选,作者包括qwertyoruiop、Fire30和RKX1209等。不幸的是,这些2015年PS越狱场景中的主要角色倾向于编写难以理解的利用代码,使用最少(有时完全无法理解)的注释和未定义的静态偏移量。部分代码确实来自另一个时代。

最终我挖掘出一个看起来可行的利用代码,作者是名为xyz的黑客,附有清晰的说明。该利用针对WebKit JSArray::sortCompactedVector函数中的释放后使用(UaF)漏洞。进一步搜索未发现与此问题相关的CVE,因此我带着些许谨慎继续探索。

xyz关于JSArray::sortCompactedVector利用的说明包含一个基础PoC,可用于确认目标服务是否易受攻击:

1
2
3
4
5
6
7
8
var almost_oversize = 0x3000;
var foo = Array.prototype.constructor.apply(null, new Array(almost_oversize));
var o = {};
o.toString = function () { foo.push(12345); return ""; }
foo[0] = 1;
foo[1] = 0;
foo[2] = o;
foo.sort();

我将此代码放入 comments powered by Disqus