Everything started with a Tweet:
Check out our new XSS Challenge- $2,000 worth of prize money! Submit solutions to http://hackerone.com/bugpoc before 08/12. Rules: Must alert(domain), Must bypass CSP, Must work in Chrome, Must provide a BugPoC demo Good luck! #XSS #CTF #bugbounty #hacked
Finding attacker-controllable input
When dealing with XSS challenges the very first step is to find some attacker-controllable input that can be used as a vector to exploit the actual XSS.
This task is particularly easy in this challenge as the buggy calculator uses an
iframe as calculator display and sends content updates through the
How postMessage works?
To understand why the
frame.html page was chosen as our target it’s required to explain how
With the help of MDN web docs we can understand that:
window.postMessage()method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
So we can send a cross-origin message between two
Window elements. Nice!
Following the documentation we can understand that on the recipient
Window messages are handled by the callback function defined in the
EventListener (in our case
receiveMessage) and that the object passed to the callback contains the following properties:
- data - The object passed from the other window.
- origin - The origin of the window that sent the message at the time postMessage was called. This string is the concatenation of the protocol and “://”, the host name if one exists, and “:” followed by a port number if a port is present and differs from the default port for the given protocol.
- source - A reference to the window object that sent the message; you can use this to establish two-way communication between two windows with different origins.
Analysing the EventListener
Knowing that we can send messages with the
window.postMessage() API we should check what the
First it checks that the
origin of the message is compliant to a specific RegEx:
Then it checks if the content of the message is
off to turn on or off the display and finally if it’s different from the previous 2 cases it adds its content to the page via the
innerHTML API (which would give us
HTML injection as threats strings as trusted
HTML code) given the fact that the input does not contain the character
' and the character
Bypassing the event.origin check
As seen in the documentation the
event.origin property contains the protocol +
:// + the domain +
: + the port of the page sending the message. Such origin is checked via the following regEx
/^http:\/\/calc.buggywebsite.com/, which stands for
event.origin should start with
Can we bypass this check?
Of course! Anyone could create a subdomain which starts with
Ok, but I don’t want to buy a domain and I don’t have one…
That’s not a big deal online services like xip.io could be used to quickly reproduce this locally (i.e.
http://calc.buggywebsite.com.127.0.0.1.xip.io:8000/ could be used to point to a local
HTTP server on port
8000 with a domain which bypasses the check). Moreover, while submitting the final PoC through BugPoc.com I realized that it also allows us to change the subdomain of the PoC!
Given the aforementioned bypass, we can use our desired trick to host the following HTLM page, which embeds
http://calc.buggywebsite.com/frame.html in an iframe and sends a message to it containing
So now we have our working exploit to the HTML injection, we just need to send a simple
<img src=c onerror=alert(1)> to exploit the XSS, right?
Not so fast, as can be seen the the
<head> section a
<meta> tag declaring a Content-Security-Policy is present.
The aforementioned CSP has allows to:
- use the
self, aka the
Usually, the second point is useful when the target website allows us to upload arbitrary files, then we can upload a
<script src=/path/to/script.js></script>, but obviously a calculator doesn’t allow any file upload.
The other useful scenario is when we have some nice scripts hosted on the target domain which we can abuse and that’s the case! Going back to the homepage of the calculator we can spot the inclusion of
http://calc.buggywebsite.com/angular.min.js, which is
CSP bypass with AngularJS
Bypassing the CSPs with AngularJS is a well-known technique, in fact when AngularJS is loaded in a page and you have an HTML injection you can create a new
ng-app and write any AngularJS script between the curly brackets.
Unfortunately, we don’t have AngularJS loaded in the
/frame.html page, but we have an
HTML injection, so we can just inject a
<script> tag and load
angular.min.js right? Incorrect! Adding a
<script> tag with
innerHTML is basically useless.
What about creating a full new document?
Yeah, that’s the way! We can inject an
iframe inside the
iframe and set an arbitrary
HTML content via the
AngularJS sandbox escape and filter bypass
Fortunately for us a known sandbox escape for version 1.5.6 is available:
Unfortunately for us, we can’t use single quotes and we can’t even use the common
srcdoc trick to encode characters in
HTML entities as the
& character is blacklisted.
The only option is to use some
The first step is to create a dict having as key a
String and as value the
prototype of the
constructor of a
(+) (stolen from JSFuck) allowing us to easily rewrite the first part of the sandbox escape without
For the second part, we can retrieve from the
fromCharCode() function and create a string out of the
ASCII representation of characters.
Wrapping everything together we have our final exploit
To see it in action on BugPoc.com:
👋🏾 by smaury