[HackerNotes Ep. 58] Youssef Sammouda - Client-Side & ATO War Stories

Youssef and the guys sit down during the LHE to discuss all things ATO and client-side madness.

Hacker TLDR;

  • Winning client-side races: Youssef is no stranger to client-side-based attacks. Having achieved ATO numerous times in companies such as Meta, he has used a few tricks to win client-side race conditions including:

    • Sending large postMessage requests, up to 5MB in size, to increase processing time to beat the race condition locally

    • Using caching techniques by caching a malicious HTML page with an event listener already listening, then redirecting the page receiving the post message right before and your cached HTML page is already listening to beat the request on the other side.

    • Using faster string-based methods to win against slower methods such as json.parse

  • Exploiting scroll-to-text functionality: By chaining numerous client-side-based bugs together, Youssef stumbled upon a 0-day in Chrome, allowing him to exploit scroll-to-text functionality. Some takeaways from this include:

    • Modifying character sets: If a character set isn’t specifically set in the header, the browser will automatically detect the character set based on the page content. This can be abused to allow for non-standard character sets, allowing for exploitation.

    • Monitor new feature releases: Keeping track of new browser releases as well as application-based releases for functionality updates and code changes can help when crafting client-side gadgets.

    • TAKE NOTES OF YOUR GADGETS: We’ve said it before but taking notes of ALL potential gadgets for a target is key to crafting higher-impact bugs.

  • When random isn't: Leaking 3-4 values of JS math.random can lead to you disclosing the seed value of the function, allowing you to predict future values. This can prove massively beneficial when a function or function names depend on it for sensitive functionality.

  • postMessage methodology: Youssef gives an overview of how he approaches postMessage, sharing some useful tips on the way. The origin parameter of a postMessage can be a function as well as a string, which could prove to be useful when looking to bypass checks.

  • Attacking OAuth: Youssef shares gadgets he looks for when approaching an OAuth implementation. Some of these include:

    • Any gadgets that can be used to leak window.href.

    • Any gadgets for self XSS to combine with login/logout CSRF gadgets.

    • Any place where authentication is being passed from one top-level domain to another top-level domain.

If you’re familiar with any of Youssef Sammouda’s work, you already know it’s seriously impressive. Youssef has come out as Meta’s #1 hacker a few times and has even found some 0 days in browsers.

When hunting, Youssef's methodology leans towards more client-side hacking, aiming to achieve maximum impact which, for client-side bugs, is usually ATO.

To achieve this, Youssef focuses on OAuth and postMessage functionality in applications. Numerous types of gadgets are often chained in his flows, combining self XSS, CSRFs on login/logout functionality, and open redirects to achieve ATO in the application.

He has some really good writeups on his blog which detail some of his findings. Let's jump into some of his more notable ones.

ATO in Facebook

One of Youseef's notable findings was an ATO in Meta. This was achieved by a client-side race condition via a postMessage, whereby the postMessage was responsible for changing the origin check to the server.

The script requests a third-party game page for the OAuth token for the specified application, which Meta then replies to with a token.

Now you might start to wonder about the OAuth checks, but the OAuth flow checks were being performed properly.

Youssef initially made the request with an Instagram origin, but a client-side function was present which changed the origin check. The check was an asynchronous request to the server, but while waiting for the response to the initial request, Youssef made a client-side post message to change the origin, which resulted in complete trust of the origin.

This is essentially a client-side race condition via postMessage, whereby the time taken for the check is abused by a quicker means of sending a postMessage, which is what is known as winning the race in this context.

The OAuth token is then sent back to the source window of the postMessage. This was taken off his blog post detailing it a bit more:

“…and the trick here is that we can send another register message while the application is still waiting for the response to the OAuth endpoint, and change “k” back to the attacker website. Now, notice in line 4, k would be chosen but “k” with the new value and not “fbconnect://success” which would allow us to get the first party access_token.”

The full writeup for this one can be found here, I highly recommend reading it for some additional context around the bug.

Some notable takeaways from the guys when it comes to exploiting these types of bugs:

  • Client-side races rely on some action in the flow having a delay, taking lots of time to do. If you can cause a delay or respond quicker than the intended response, you can win the race.

  • Another trick is to cache your malicious HTML page with an event listener already listening, then redirect the page receiving the post message right before and your cached HTML page is already there listening to beat the request on the other side.

  • If there is a lock mechanism in the function that waits until the request is fulfilled to try and prevent these things from happening, try sending lots of data over to it to increase processing time. Youssef used large post messages, up to 5MB in size, to massively increase the processing time to trick the lock mechanism from locking.

  • String index methods are a lot faster than methods such as json.parse - this can be useful if one frame is using json.parse and you need to win the race by using a faster method.

  • The older method of postMessage (before postMessage was a thing) - window.name can be used on apps that are heavily protected by a CSP to transfer data across windows.

  • eventListeners should be viewed as a source when reviewing JS. Any different parts of the URL can end up in an event listener - such as location.hash, window.location, and so on.

Exploiting scroll-to-text functionality

Now sometimes when we approach a hardened target we have to get creative, thinking of vectors that probably weren’t considered internally as a risk.

Youssef managed to do exactly this by abusing the functionality that's responsible in the browser for scrolling to text fragments and identified a 0-day in the process.

You can read the Chromium issue that was made off of the back of this bug by Youseef here.

By chaining a gadget previously noted gadget in Facebook where he could set window.opener to a certain URL, for example, make the page return window.opener = <https://facebook.com/certainendpoint> along with the scroll-to-text functionality, it was possible to exfiltrate information from the target page.

There's some nuance to this one, as the hash value that is used to scroll to the text needs to come from the same origin domain, and equally, how can you detect the scroll in an iframe?

This is where the bug in Chrome comes into play. The bug meant the parent window also scrolled, not just the iframe window. This allowed him to detect the scroll, and a scroll would mean a positive result (the specified text was found from the hash) . This confirms if the text was found in the iframe or not.

Think of the abuse cases here. You could essentially abuse this to exfil any text on the page by detecting if the window scroll is happening!

There were challenges encountered on the path to developing a high-impact POC. Here are some insights to that could help when crafting proof of concepts:

Being able to modify character sets can help in exploitation:

When faced with the ability to manipulate or influence any aspect of a response, Youssef utilized UTF-16 characters to circumvent limitations surrounding standard character sets within Chrome's scroll-to-text feature, which solely identified complete words separated by spaces. In cases where the webpage does not explicitly define the character set in the header, Chrome automatically interprets the character set based on the page content. In this scenario, Youssef injected Chinese characters, prompting Chrome to interpret the entire page as UTF-16 encoded. This then allowed for a full PoC to be crafted due to the additional flexibility of the character set in this context.

Monitor new feature releases:

If a new feature comes out, check out the features standards page in GitHub or Chrome. This will detail what's been updated, upcoming releases and so on. Sometimes, discussion threads are publicly accessible detailing potential fixes and functionality. You’ll even be able to find documents on how protections against certain vulnerability classes are implemented, and sometimes they even have accepted risk stated in the document or thread.

TAKE NOTES OF YOUR GADGETS!

Even if you find something that isn’t immediately exploitable, note it down. When applied in a different context, it could be the difference between exploitable and unexploitable behavior.

Youssef postMessage and OAuth Methodology

Youssef didn’t stop dropping valuable insights from his experience hunting throughout the pod. We’ve condensed his personal approach to postMessage bugs, what he looks for when attacking OAuth, and areas he pays special attention to when hunting into a cheatsheet.

A brief list of functionality and behaviour he looks out for includes:

  • OAuth flows

  • postMessages

  • Domain handoff of information - Ie top level domains handing off sensitive information to other top-level domains

  • Cookie bridges

  • Hash changes

  • DOM generated HTML

postMessage

Youssef clearly is no stranger to postMessage and OAuth-based bugs. Fortunately, he detailed his approach when looking at postMessage implementations and ATO on targets for us throughout the pod:

  • Uses Frans Rosens browser extension to identify where listeners are defined

  • Goes through the listeners one by one, reading the associated JavaScript functions for them and how they are used.

  • Ignores postMessages from third parties that he’s tested before such as Google Authenticator, or third parties that don’t have any sensitive data associated with them.

  • If a third-party postMessage such as a marketing tracker is used and the parent page is sending something such as to the iframe, window.location.href, if he can find an XSS in that domain to hijack the iframe to receive the window.location.href he will use it as a gadget in something like an OAuth flow to redirect to the page with the listener and leak it from there

  • Tip - If you see math.random being used as a means of providing a window or function a name which you need to interact with, if you can leak 4-5 of these values without a page refresh, you can derive the seed the function uses. This can be used to figure out the rest of the sequence and future sequences from the function. Check out this research from Donut detailing it here.

Now, something worth mentioning around postMessage is, if you aren’t familiar, the syntax looks like the below postMessage(message, targetOrigin, transfer)

Something that might not be common knowledge is the targetOrigin parameter is often assumed to be and passed as a string. However, you can also pass the targetOrigin as an object.

Often the target origin is checked, but only checked if it's passed in as a string. If it's an object the check will likely not be performed, allowing you to set the target origin as you want providing it's in an object format.

This can be used to bypass checks targeted toward string-based checks.

This is another example of a type of type confusion attack. Time and time again, we see checks being performed which make assumptions on the passed data being a certain type. If we are able to pass in a completely unexpected type, ie an object instead of a string, this could be really useful to us as hunters.

If you are looking at exploiting this type of behaviour and need a way to get a reference to a specific window to do so, check out this blog post detailing all the ways to get a reference to send a postMessage to:

Be sure to keep an eye out for type-specific checks.

OAuth

When looking at OAuth flows, Youssef looks at both mobile and application flows when hunting, and has found critical bugs in both for Meta.

Some of his key takeaways from looking at OAuth flows:

  • When looking at a flow, if it's possible to redirect to any endpoint on the target domain, he then looks for endpoints that pass all URL parameters through - things like open redirects and so on.

  • Any gadgets that can be used to leak window.href

  • Any gadgets for self XSS to combine with login/logout CSRF gadgets.

  • Any place where authentication is being passed from one top-level domain to another top-level domain.

  • Tip - Its Easier to exploit when a hash fragment is used to send the token as the browser auto-carries it through requests, so you can look for any open redirect to exfiltrate it.

  • Tip - X-frame-options allow from has been deprecated meaning certain browsers ignore it. This could be a valuable gadget in a bigger PoC.

JS Monitoring and Client-Side Routing

To keep up to date with changes in targets, Yousseef implements JS monitoring and spends time understanding client-side routing, all tailored to the target.

Fortunately, for Facebook, there's an endpoint available that returns new modules individually.

For other targets, he downloads JS files and gives interesting functions he wants to monitor a hash. He will then continuously keep monitoring that function against the hash for any changes.

Each function is monitored with a reference to what packages they correspond to to minimize false positives. The main focus of the monitoring is on comparisons from old versions to cut down the attack surface to focus on.

The process, all in all, looks like:

Downloads package → Breaks it into functions → Compares new functions via hashes from previously downloaded functions via regex

An important thing to note - If you hash web-packed variables, the variable name might have changed even though it is the same function. This is important to know if you plan on implementing JS monitoring to reduce unnecessary noise.

Another method used by Justin is using tools capable of parsing JS into ASTs (abstract syntax trees). The benefit of this is you can interact with the AST as if it were an object to parse out specific things such as functions, variables, and so on without the need for regex.

DigitalOcean has a good write-up detailing how this works if you’re unfamiliar with the concept.

Aside from automated analysis, Youssef spends time on each target understanding the client-side routing. For each target, he enumerates all client-side routes associated with an application to extract the routes and associated functions to review manually.

All frameworks have their way of defining routes, so this step can be time-consuming to fully understand.

If you know of any tools or a good browser plugin to link client-side routes to a specific class drop us a message in the Discord chat!

MiTM Proxy

Youssef has customized his setup a bit for his style of hunting. Instead of using BurpSuite or Caido, he uses mitmproxy with an upstream configured to Burp.

The benefits of doing this are that all content from requests is saved and stored in mitmproxy, but if he needs to do manual analysis on something Burp is ready to go.

Mitmproxy also supports plugins written in Python. Youssef uses custom Python plugins to perform analysis on traffic such as additional JS and HTML parsing. This saves him from using Burp to walk the whole app, capture the project file, export the project file, and then parse manually.

With MiTM proxy, everything is written in Python - so if you want to port over or spin up your own plugin, it's relatively straightforward.

Yousseef is a pro when it comes to client-side bugs - this episode was littered with nuggets of advice.

Be sure to check out his Twitter and blog to stay up to date with his research and bugs.

As always, keep hacking!