173 - Remotely Controlling Hyundai and a League of Legends XSS
The core vulnerability here is a case where a DOM clobbering attack could be used to hijack a service worker.
The flow is a complicated, you start off with your web-app loading, on that page is an element, something like:
<div id="cdnDomain" style="display:none">example.com</div>
let cdn = document.getElementById("cdnDomain").innerText navigator.serviceWorker.register("/sw.js?host=" + cdn)
And then finally in the service work itself would import scripts based off the
host param. So if one could control the
cdnDomain value they could control where the server worker loaded the core of its code from.
The most interesting part of the post comes in a couple discoveries regarding quirks of
getElementById. I’m not sure if these are entirely novel, but often with DOM clobbering attacks you want your DOM element that is doing the clobbering to exist earlier in the DOM than the one being clobbered. this is not always the case though, the author calls out a couple tags that will take precedence even if they occur later in the page:
<body> tags. Even if it doesn’t quite make sense for those tags to be present, if injected it seems they will clobber other elements with the same ID. This includes if they are present inside of an
password fields of the SMTP server configuration accept new-line characters that will be printed directly into the resulting configuration file. Using this it is possible to include configuration parameters that are not normally exposed. Using this one can set the
rendering_args for the Grafana Image Rendering plugin which through the
--renderer-cmd-prefix argument can result in command injection.
A race condition in snap-confine, which is a suid root binary that’s installed by default on Ubuntu. The
must_mkdir_and_open_with_perms() function is used for making a directory and opening it for temporary directories for snap-confine. If the given directory already exists, as long as it isn’t root-owned, it will be renamed to a random directory and the function will attempt to make the dir again. Ultimately, this allows you to get a directory you don’t own renamed, and can race to get snap-confine to use a directory you control for its operations to get a root file write.
Their overall strategy involves running two instances of snap-confine in parallel.
- Run once instance and block it (using single-step trick)
- Run another instance with an instance name that collides with the temporary name of the first instance
- Kill the second instance immediately after it renames the directory
- Manually recreate the directory and resume execution of the first snap-confine instance
- First instance will now read the
/tmpdir inside of that root directory you now own and will follow symbolic links.
Taking advantage of this was tricky for a few reasons. For one, it was hard to get the symlink followed by snap-confine as the function that would mount the namespace bind-mounts a read-only squashfs into the root directory. If you create the symlink before hand it gets covered, and you can’t create it afterwards where it’s root only. They circumvented this by mounting a FUSE filesystem onto the root dir immediately when re-creating it, which then allows them to unmount any subsequent bind-mounts.
The other issue was the fact that snap-confine used AppArmor, which prevented bind-mounting
/tmp onto an arbitrary directory on the system. They used a pre-existing vulnerability in
multipathd as an AppArmor escape and chain with this bug to create a custom AppArmor policy that gives them free reign.
Finally, they exploit the bind-mount against the multipath lib (since they already rely on it for AppArmor bypass), implant their own shared library, and restart
multipathd using another DOS bug in multipath to get their library ran as root.
A long chain of issues that leads to XSS in the league of legends (LoL) account subdomain via easyXDM, which is a developer focused JS library that provides an interface for doing cross-origin communication using various protocols. easyXDM consists of a producer-consumer setup, where a producer page exports functions for the consumer page to invoke. In LoL’s case, they exported methods to send requests and responses cross-origin, as well as get and set cookies via the
pm.html page. These are dangerous functionalities of course, so there’s some protections here. The
document.referrer is checked against an allowlist of domains owned by riot games or their partners. On top of that, the message origin reported by easyXDM is also verified against this same list.
Open redirect to bypass referrer check
They managed to bypass the referrer check via an open redirect issue in easyXDM’s
FrameElementTransport.js class, which is used for passing variables using the
frameelement property on gecko browsers. One of the things it allows you to do is set the window top location to this
xdm_e parameter (aka
config.remote). They could force
FrameElementTransport to be used by setting
config.protocol to 5). They abused this via the
apollo consumer, which is an allowlisted consumer.
Bypassing origin check
Now they could get attacker controlled code loaded, but it was still subject to the origin check by easyXDM. The second bug was one in
HashTransport for communicating across iframes via the window
location.hash. It’s a hacky technique which is fundamentally flawed somewhat as it’s impossible for the parent page to know who updated the
location.hash, and so they assume messages came from
config.remote, which can be controlled by an attacker.
This puts the attacker in a catch-22 though. They can set
config.remote to an attacker domain to get their code loaded, but they’ll fail the origin check. They could set it to an allowed domain to bypass the origin check, but then they forfeit loading attacker code. So they had to revisit their original referrer bypass and rework it a bit. They essentially used
HashTransport again to get their page loaded in a nested iframe under the
apollo consumer and switch the parent location source to the
pm.html page after the fact. This allowed them to send requests to
pm.html, though they couldn’t get responses back.
Bypassing signing/verification + getting XSS easyXDM additionally tries to sign and verify incoming messages to prevent iframes being abused in this way. The problem is, they sign using a secret that’s set by the iframe assuming an attacker doesn’t have control of the second level iframe from the start. So an attacker can simply set their own secret and still gain access to those methods mentioned earlier.
XSS was achieved by using the ability to make XHR requests, and the fact that that jquery would implicitly load a URL as a JSONP call if it ends in
=? (at least until jquery v4). This finally got them XSS in
An email normalization issue allowing for remote control of a vehicle.
Hyundai’s remote control API would use a JWT for authorization, the JWT would contain the user’s email. For the API request to unlock a vehicle, the JWT would be sent in a header, and in the body of the request would take a
userName field containing the same email and the
vin for the target vehicle.
It would check that the
userName (email) matched the email in the JWT and throw and error if not. The vulnerability comes from this comparision, as it would do some normalization, and registration would not require confirming ownership of the email.
So one could register with the email
email@example.com%0d (a carriage return at the end), but in the body of the request use
firstname.lastname@example.org. Due to normalization it would go ahead and treat your JWT as valid to the
email@example.com user name, allowing the attack to control the victims vehicle.