XSS challenge — The XSS rat & BugPoc

holme
9 min readJan 21, 2021

--

Not in the mood for reading? Fair enough, here’s the solution:
PoC URL
: https://bugpoc.com/poc#bp-DAPAxYtZ
Password: huMANEemu69

Well would you look at that. A *wild* XSS challenge has appeared and it looks like my weekend plans has to be scrapped.

Let’s play a game… or maybe just pop an XSS?

Let’s start by checking out the challenge page:

The page appears to be a game where you have to pick three cards, one from each pile, and get a sum of 18. While the game looks fun, that’s not why we’re here is it? Let’s get to work and see if we can pop that beautiful little white box we all love!

When we click a card, a new window opens up:

And it looks like the card we picked via the new window somehow get’s transferred back to the parent window:

Notice that the info of the card is somehow send back

Hmm, is this magic? No it’s actually just a message received by listening for the message event:

Let’s check out the page that the cards are shown in! After clicking a random card we see a request is made to: https://cards.buggywebsite.com/popup.html#indexes=2|0|0&name=Player%201&autoClose=1. Let’s navigate to that URL:

We see a card, but it’s not turned around with the cool animation we saw before… Let’s check for errors in the console:

Look’s like the page is trying to send a message to the opener window (the page that opened the window) by using postMessage function. But that’s not possible since no page opened the window as we just manually navigated to it. But we now understand how the two windows can communicate with each other. Our trained hacker eyes notice one thing which is worth noting down: the second parameter given to the postMessage function, the targetOrigin, is set to ‘*’ (think wildcard). This means that the function doesn’t care who receives the message. This is (often) bad practice as it can also be read in the MDN Web Docs:

Always provide a specific targetOrigin, not *, if you know where the other window's document should be located. Failing to provide a specific target discloses the data you send to any interested malicious site.

But who cares if we could potentially read the cards picked by a victim? Cards might not be that interesting, but maybe it’ll come in handy later. Let’s note it down and move on.

Checking the source code

The ‘Pop up’ page uses the script script.js to take care of all the functionality. In the bottom of the script we cans see the following:

It looks like loadPage is the ‘main’ function of this script, and we can see that it’ll be called every time the page loads and every time the URL fragment is changed. The loadPage function starts by retrieving the parameters specified in the URL fragment and stores them in an object called params

The parseFragment function looks as follows:

The approach seems to be to construct a JSON object and parse it with JSON.parse to create a JavaScript object. The challenge creator has been kind enough to tell us where they got this function from, Stack Overflow, and provide us with a link, so let’s check that out. One of the comments to the accepted answer is a bit interesting:

Seems like we can rather easy get this approach to fail, eg. by using multiple ‘=’ chars without any ‘&’. But that’s probably also why the approach has been wrapped in a try...catch statement. But hey, the code in the catch statement looks vulnerable! It sets the innerHTML property of a div to include whatever we specified in the URL fragment!

HTML injection

Let’s try to create a URL fragment which should hopefully make JSON.parse fail so we can reach the catch statement. Let’s also make sure it contains some HTML payload. The following fragment should do the job: ==<h1>hihi making the URL: `https://cards.buggywebsite.com/popup.html#==<h1>hihi`

https://cards.buggywebsite.com/popup.html#==<h1>hihi

Cool! It worked! We successfully injected a HTML payload into the page. Let’s just pop an XSS now using a payload like: <img src=x onerror=alert(1)>

https://cards.buggywebsite.com/popup.html#==<img src=x onerror=alert(1)>

Ehh, no alert box? Let’s check the console:

XSS? No, CSP

Oh. CSP strikes again. Let’s use Google’s CSP evaluator to test this website’s CSP policy:

Hmm. It looks annoyingly strong. We can’t execute inline JavaScript and we can observe that script tags needs to provide a nonce to be executed.

Briefly explained, a nonce is a cryptographic unique value generated by the server to ensure that the client will only execute the intended scripts. In other words, if a script doesn’t provide this value, it won’t be executed. All this makes it practically impossible for use to just directly inject a XSS payload.

More source code

Let’s move along and proceed to check the loadPage function.

After the params object is created, three parameters are extracted. The indexes parameter, which is split in to an array using ‘|’ as a separator, name which is assigned to the playerName variable, and autoClose.

After this, the functiongenerateCards is called which, generate a bunch of cards and stores them in a 3-dimensional array.

Then the function indexIntoMultidimentionalArray is then called with the 3-dimensional array as one argument and the indexes array as another.

The function looks very interesting. It starts by setting the variable item to the 3-dimensional card array. It then iterates over the user defined indexes array and sets the variable item to elementi of the variable item. Let’s look at a example. If we say that that the indexes array consists of three elements: ‘a’, ‘b’ and, ‘c’, the following would be the result:

indexes = ["a","b","c"];result = indexIntoMultidimentionalArray(array, indexes);
//result will be:
//result = array["a"]["b"]["c"];

This seems like very interesting behavior since we control the indexes array.

Afterwards, the returned value is properly escaped by the function stringify before being packed into an object and send with postMessage via the custom sendMsg function.

Time to exploit

Now that we got an understanding of what’s going on, it’s time to exploit! We already know that we should be able to receive the message sent with postMessage on our own attacker-controlled site, since the targetOrigin is set to ‘*’, but let’s test it in pratice:

Note: You need to allow the pop-up

Yup, it worked just as excepted (if you allow the pop-up). But this standard info is really boring… What would be the best info for us to get? We’ve already found a way to inject HTML and the only thing stopping us from an XSS is that annoying nonce value. But would it maybe be possible to fetch that and set it back!? That would be great! Let’s try and explore which info we can access via the indexIntoMultidimentionalArray function. To do so we can set a breakpoint in the function and reload the page:

We know from our investigation earlier that we can access anything inside the item variable, so let’s check what it contains by using the console:

The 3-dimensional array is made up of text nodes, each representing a different card. These nodes have a bunch of properties. One property stands out though, the ownerDocument. This property returns the top-level document object. This means we get access to read from document node! This could be very interesting, let’s check ownerDocument:

Well what do we have here? The scripts from the page! Let’s see which properties these contain:

Bingo! The nonce value! Just what we needed. Let’s verify we can access the nonce value via the item array:

Yup, indeed that’s possible. Now let’s try to extract this value from the page and send it back to our attacker-controlled website! To do so, the URL fragment should be: indexes=0|0|0|ownerDocument|scripts|0|nonce&name=bla. Let’s test:

Yes! We got the nonce value! Now we can create an XSS payload using the nonce value to avoid being blocked by CSP! “But what can we use the nonce value for when we’ve already sent our payload and the page has loaded?” you may ask. Well, remeber this:

The loadPage function will be called when the URL fragment get’s modified. And you know what? Modifying the URL fragment won’t cause the page to reload! Let’s create an exploit which does the following:

  1. Send a payload to extract the nonce value
  2. Dynamically create an XSS payload with extracted nonce
  3. Send a XSS payload in an URL fragment which will cause JSON.parse to fail and pop the sacred alert box

Our exploit code will be:

Ehh, so that was a bit weird. Our exploit looked like it was successful, but it doesn’t look like our injected script was executed. That’s pretty sad. Oh but wait. Remeber that our payload get’s inserted into the DOM with the use of innerHTML?

Well it turns out that HTML5 won’t execute scripts inserted into the DOM via innerHTML:

script elements inserted using innerHTML do not execute when they are inserted.

(https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0)

Hmm, so what do we do about that?

Iframes to the rescue

To overcome our struggle we can simply use the iframe tag and it’s beautiful attribute: srcdoc. This attribute allows us to specify HTML we want to have embedded into our very own browsing context created by the iframe tag. A payload as the following will do the job: <iframe srcdoc="<script nonce=ETRACTED_VAL>alert(origin);</script>">

Let’s try!

How cool is that!? We successfully solved the challenge! Thanks for reading along! I hope you maybe learned something new or at least just had fun reading :)

Thanks to The Xss Rat and Bug PoC for the fun challenge!

@holme_sec

--

--

holme
holme

No responses yet