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>
Also on the page is some javascript (my own examples, not directly from the post)
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: <html>
and <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 <svg>
element.
The username
, from_name
and 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
/tmp
dir 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 xdm_p
(aka 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 xdm_e
/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 accounts.leagueoflegends.com
.
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 example@gmail.com%0d
(a carriage return at the end), but in the body of the request use example@gmail.com
. Due to normalization it would go ahead and treat your JWT as valid to the example@gmail.com
user name, allowing the attack to control the victims vehicle.