In this blog post, I will explain how I was able to bypass some client-side based XSS so called “protection”.
While I was looking for cheap flights, I recalled that United offer a bug bounty program that rewards free mileage to researchers who report security vulnerabilities.
As I started looking testing their websites, I found a couple of bugs and reported them, then I came accross the subdomain http://checkin.united.com.
Visiting the above link redirected me to another page on the same subdomain, with a GET parameter called “SID”. I started testing that parameter and noticed that it’s value gets reflected in the document 60+ times, none of which is well sanitized against special characters, allowing me to break out of the tags it reflects in in 100% of the times it does.
I simply entered “> to get the alert box I’m looking for, but weird enough, no alert boxes were there at all, I then inspected the source of the page and found that my injection actually lands untouched exactly the same 60+ times, yet the JS payload doesn’t execute.
|Some of the payload reflections in the document|
I started digging more into the script tags and what codes do they contain, until I reached the source of my misery, a JS file that is included into the page and contains the following code:
|JS code that caused the trouble|
Basically, the code overrides the native alert(), confirm(), prompt(), unescape(), and document.write() functions and nullifies them, so calling them does absolutely nothing. This was implemented as an “XSS protection”.
So after some research, I managed to restore document.write() to it’s default state by calling document.write = HTMLDocument.prototype.write;document.write(‘STRUKT’);, but again, what good does that do with all the main functions I want to access sabotaged.
|Using document.write() to print into the document|
I started playing around with the help of my friend and teacher @brutelogic, he provided me with this link, which talks about the JS defense in place. The article also mentioned that we could get the overridden functions back to their defaults by using the word delete. We tried the keyword and it happened to be blacklisted as well, then I had an idea. What if I inject empty iframe tags (without the src attribute) and then set the main window’s alert() function to any of these iframes’ native alert functions, it will then be reset to the default alert() function any document has.
I tried the new idea and it actually worked, bypassing all the “XSS protections” in place and circumventing the overrides implemented by the developers.
|The beloved alert box finally popping up|
Then my friend @brutelogic managed to optimize the payload to a much shorter one, with the ability to work in Chrome and bypass Auditor (Because there’s also an unsanitized reflection in a tag context).
|Brute’s payload working in Firefox|
|Brute’s payload bypassing Chrome’s XSS Auditor|
Then I’ve decided to go further and check if United’s main website contains any flaws. After spending less than 10 minutes of investigation, I found out that the exact same vulnerable path found on http://checkin.united.com exists on United’s main website, with absolutely the same imported libraries and the 60+ vulnerable reflections, killing two birds with one stone.
|XSSing United’s main website with the same payload|
Finally, I would like to thank my teacher and friend @brutelogic for his continuous support and generosity in providing me with brilliant and unexpected information.
See you in another post 😉