diff --git a/_posts/2020-12-28-deep-dive-into-site-isolation-part-2.md b/_posts/2020-12-28-deep-dive-into-site-isolation-part-2.md new file mode 100644 index 0000000..e702866 --- /dev/null +++ b/_posts/2020-12-28-deep-dive-into-site-isolation-part-2.md @@ -0,0 +1,401 @@ +--- +title: Deep Dive into Site Isolation (Part 2) +author: Jun Kokatsu +date: 2020-12-28 9:25:00 -0800 +categories: [Vulnerabilities] +tags: [Site Isolation] +math: true +author_twitter: shhnjk +--- + +In the [previous blog post](https://microsoftedge.github.io/edgevr/posts/deep-dive-into-site-isolation-part-1/), I explained how Site Isolation and related security features help mitigate attacks such as UXSS and Spectre. However, security bugs in a renderer process are [really common](https://www.chromium.org/Home/chromium-security/site-isolation#TOC-Motivation), and therefore [Chromium's threat model](https://chromium.googlesource.com/chromium/src/+/master/docs/security/side-channel-threat-model.md#nastier-threat-models) assumes that a renderer process can be compromised and it can't be trusted. To align with this threat model, Chromium announced [improvements to Site Isolation](https://blog.chromium.org/2019/10/recent-site-isolation-improvements.html#:~:text=Containing%20Compromised%20Renderers) in 2019 to further mitigate the amount of damage a compromised renderer process can cause. In this blog post, I will explain details of those improvements and bugs found along the way. + +# What is a compromised renderer process? +Attackers may find security bugs in Chromium's renderer process, such as in the JavaScript engine, DOM, or image parsing logic. Sometimes, these bugs may involve memory errors (e.g., a "use-after-free" bug) which allow an attacker's web page to execute their own, arbitrary, native code (e.g. assembly/C++ code, as opposed to JavaScript code) in the renderer process. We call such a process a "compromised renderer" - by [Łukasz Anforowicz](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/0ei-UCHNm34/m/lDaXwQhzBAAJ#:~:text=What%20is%20a%20compromised%20renderer%20process?) + +This means a compromised renderer process can not only read an entire memory in the renderer process, but also write to it. Which for example, allow the attacker to fake [IPC](https://en.wikipedia.org/wiki/Inter-process_communication) messages from the renderer process to other processes. [This list](https://chromium.googlesource.com/chromium/src/+/master/docs/security/compromised-renderers.md) explains where those Site Isolation improvements have been added. + +# Finding first Site Isolation bypass to achieve UXSS +While looking for ways to bypass Site Isolation, I remembered a really interesting [UXSS bug](https://github.com/Bo0oM/CVE-2017-5124) found by [Bo0oM](https://twitter.com/i_bo0om). Site Isolation was still an experimental feature and disabled at the time, I wondered if the same bug could be used to bypass Site Isolation. + +So, I enabled Site Isolation and tested the UXSS bug, and it worked in an interesting way. While the origin was changed, the process from the previous site was reused. Trying to access cookie for example, would crash the renderer process, because Site Isolation would notice that the process should not request cookie for another origin. + + +This was a perfect bug to find a Site Isolation bypass because this behavior was similar to a compromised renderer where you can overwrite the origin information in the renderer process, but that wouldn't allow an attacker to bypass process isolation by Site Isolation. By using this bug, we can test which API wouldn't care about a faked origin and allow us to access other origin's information. So, while testing it myself, I also told [Masato](https://twitter.com/kinugawamasato) about this interesting behavior. And soon, he found a bug 😊 It turns out that you can create a Blob URL with a spoofed origin and navigating to that Blob URL would allow us to access cookie of the target site. + + + +While we were able to find a bug, we had to make sure that the bug still exists in the stable version because the UXSS bug had been fixed. To validate this, I just used WinDbg to change the origin before sending the IPC to make a Blob URL, and I was able to trigger the same bug on the stable version. + +[This bug](https://bugs.chromium.org/p/chromium/issues/detail?id=886976) was fixed by verifying the origin in the browser process when creating a Blob URL. + +# Spoofing IPC messages +From the previous bug, it became clear that the easiest way to test Site Isolation improvements against a compromised renderer would be to spoof IPC messages from a renderer process where it sends an origin or a URL information. But reading code to find such places and using [Mojo JS](https://chromium.googlesource.com/chromium/src/+/master/mojo/public/js/README.md) to send fake IPC messages seemed like a lot of work 😋 + +So, I created a JavaScript debugger extension for WinDbg called [spoof.js](https://github.com/shhnjk/spoof.js). Because _spoof.js_ will change the origin and the URL in a renderer's memory, I just need to make normal Web API calls to test IPCs. This also had an unintentional advantage that it can also spoof IPC messages implemented with [legacy IPC](https://chromium.googlesource.com/chromium/src/+/refs/tags/72.0.3586.1/ipc/README.md) instead of [Mojo](https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md) (which wouldn't be possible if I chose to test with the Mojo JS). + +## A bug in _postMessage_ +While testing with _spoof.js_, I noticed that I could send [_postMessage_](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) to a cross-site window/frame with the spoofed origin, and I could also receive a message that was sent with a different target origin by spoofing the origin. + + + +[This bug](https://bugs.chromium.org/p/chromium/issues/detail?id=915398) was fixed by validating the origin of _postMessage_ IPC in the browser process. + +# Address bar spoof with a compromised renderer +Unfortunately, I was only able to find the _postMessage_ bug with _spoof.js_. The next thing I tried was to think of a place where there might be a Site Isolation bypass and do a code review + testing. And I thought I will look into navigations 😊 + +If you study a little bit about how [navigations work](https://youtu.be/OFIvyc1y1ws?t=314) in Chromium, there is an interesting step where the renderer process will _commit_ the navigation and send an IPC to the browser process. This IPC message is interesting, because renderer process can tell which origin and URL the renderer process had committed the navigation to _after_ navigation has been started (i.e. the network process has already started downloading response). If validations in the browser process aren't strong enough, things can go wrong 😊 + +While testing for handling of navigations, I noticed that if the origin is an [opaque origin](https://html.spec.whatwg.org/multipage/origin.html#concept-origin-opaque), I could claim that the navigation has been committed to any URL from a renderer process. [This bug](https://bugs.chromium.org/p/chromium/issues/detail?id=918565) existed because any URL can be an opaque origin (with iframe/CSP sandbox) and doing normal origin vs URL check doesn't make sense. This check has been tightened to ensure that the address bar spoof isn't possible. + +# Abusing Protocol Handler +Another idea I had was, what if I can use [_registerProtocolHandler_ API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler) to navigate any protocol to some bad URL (e.g. Data URL)? So, I checked their implementation, and following restrictions were bypassable/spoofable. + +- The protocol/scheme must be in the [allow-list](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler#Permitted_schemes): + - This check was implemented inside a renderer process, and browser process only had deny-list check related to browser-handled schemes (e.g. _http:_, _https:_, etc). +- Destination URL has to be same-origin to the registering window + - This check was also inside a renderer process, thus can be bypassed. +- User has to accept the permission prompt + - The origin shown in the permission prompt was calculated using destination URL, which could be anything with #2 bypass. + - A cross-origin iframe can call _registerProtocolHandler_ API. + - The permission prompt shows no origin information if you pass a Data URL 😂 + + +With these bypasses, an attacker can bypass Site Isolation with the following steps: +1. Request a protocol handler permission with the following Data URL as the destination URL. +- `data:text/html,` +2. Clickjack a victim page which has a link to the custom protocol (e.g. _tel:_, _mailto:_, etc). +3. Clicking the link would navigate to the above Data URL, which would execute in the victim's renderer process. + + + +[This bug](https://bugs.chromium.org/p/chromium/issues/detail?id=971917) was fixed by adding appropriate checks in the browser process. + +# Finding bugs in Reader mode through a security review +When Edge started building the [Reading View](https://docs.microsoft.com/en-us/microsoft-edge/dev-guide/browser-features/reading-view) experience, we decided to use [DOM Distiller](https://chromium.googlesource.com/chromium/dom-distiller#dom-distiller) which powers Reader mode in Chrome. I was curious about how DOM Distiller is implemented, so I started testing it. + +Reader mode sanitizes HTML content from the site before rendering for good reading experience. While they tried to remove most of the dangerous tags (e.g. `script`, `style`, etc) many event handlers weren't properly sanitized (e.g. `