BugPoc - Buggy Calculator
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 window.postMessage()
API.
frame.html
frame.js
How postMessage works?
To understand why the frame.html
page was chosen as our target it’s required to explain how window.postMessage()
works.
With the help of MDN web docs we can understand that:
The
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 EventListener
does.
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 on
or 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 http://calc.buggywebsite.com
.
Can we bypass this check?
Of course! Anyone could create a subdomain which starts with calc.buggywebsite.com
(i.e. calc.buggywebsite.com.attacker.tld
).
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 test
.
Challenge solved!!1!
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 frame.html
<head>
section a <meta>
tag declaring a Content-Security-Policy is present.
CSP analysis
The aforementioned CSP has allows to:
- use the
eval
andeval
-like JavaScript functions - load arbitrary JavaScript scripts from the
self
, aka thecalc.buggywebsite.com
domain
Usually, the second point is useful when the target website allows us to upload arbitrary files, then we can upload a JavaScript
script and load it via <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 AngularJS 1.5.6
.
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 srcdoc
attribute!
AngularJS sandbox escape and filter bypass
AngularJS from version 1.0 to version 1.5.9 has a sandbox, which was removed in version 1.6. The idea of the sandbox was to prevent attackers able to inject AngularJS code to automatically obtain arbitrary JavaScript injection. After an infinite list of bypasses the AngularJS team just removed the sandbox. 🤷🏾♂️
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 JavaScript-fu
and refactor the code not to use those characters!
The first step is to create a dict having as key a String
and as value the prototype
of the constructor
of a String
.
In JavaScript, we can create an empty string with ([]+[])
(stolen from JSFuck) allowing us to easily rewrite the first part of the sandbox escape without '
and &
.
For the second part, we can retrieve from the String
constructor
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:
URL: https://bugpoc.com/poc#bp-jOA9FIM9
Password: enoUgHHOrse25
👋🏾 by smaury