Some Cross-Site Scripting (XSS) vectors arise from strict but allowed possibilities, forming tricky combinations. It’s all about contexts and sometimes the interaction between different contexts with different filters lead to some interesting bypasses.
Although in the same document (or page), usually the source code of a HTTP response is formed by 3 different contexts: HTML, Javascript and CSS. They have their own syntax and different filters are applied to the output of user input to avoid XSS situations.
So in order to understand how filters can be bypassed in some particular, multi injection scenarios, let’s start with an exercise/challenge tweeted some time ago.
Train your filter+WAF skills!#XSSmehttps://t.co/Pk6gceJqPa pic.twitter.com/rRJdPR5jhX
— Brute Logic (@brutelogic) February 16, 2020
URL is here and full source code follows:
Besides filtering there’s also a WAF (Web Application Firewall) to make it a little harder to pop the alert box.
There are filters both in HTML and JS contexts.
Alone, they can do their filtering job perfectly: the first one, on input tag, makes it possible to break out of it, but no valid XSS vector can be built since it scrapes the “=” sign needed for almost all HTML-based XSS vectors. It also scrapes the SCRIPT tag in a case insensitive manner (notice the str_ireplace PHP function), the only remaining vector that does not require the equal sign.
The second filter makes sure none of the 2 ways to break out from a JS string value work. Greater than sign is replaced by its HTML entity (not allowing </script> breakout) as well as single quote (not allowing string delimiter breakout).
But together they open an avenue to bypass based on SVG tag. SVG is XML-based markup language for describing two-dimensional based vector graphics and browsers do some kind of “double decoding” with HTML entities inside them. A scheme like <svg>[some tags]<script>[encoded code]</script> works fine .
So solution comes in a form of a multi-injection vector which appears in both places, breaking out from input tag to open a <svg> tag and forcing a similar delimiter breakout technique on JS code.
">'-alert(1)-'<svg>
It’s a combo of the following ones:
"><svg>
'-alert(1)-'
But WAF blocks it:
Using an universal way to bypass regex-based devices placed between attacker and target and some little tweaking in JS injection we end up with solution.
">';alert(1);'<=svg>
Bypassing JSON Encode
The following multi context XSS cases come with a different yet more common scenarios: different entry points (“p” & “q” parameters) and Javascript context with JSON correctly encoded, giving no room for a bypass (with single reflection).
We start with a simple and straightforward case, again filtering both entry points properly.
URL is here.
The trick here is to use the fact that inside JSON encoding, a proper HTML tag is possible:
First filter doesn’t make possible to open a HTML tag to start a HTML-based XSS vector but it allows HTML comments.
With that in mind, to XSS this all we need is the following payload:
p="><!--
q=--><svg onload=alert(1)>
Which comments all the code down to script block which is not a script block anymore since the script tag is under comments.
Here is a different scenario which requires a similar trick:
URL is here.
An inline injection with some event handler “on[anything]” is not possible neither a tag breakout to inject comments like in previous scenario. So the way to XSS this is to use a malformed arbitrary HTML attribute:
p="1='
q='><svg onload=alert(1)>
Which works pretty much like in our previous case.
A slight variation of that case can be seen here.
Finally, another variation, this time in CSS context which works with both HTML and JavaScript contexts is left here as an exercise for the reader.
#hack2learn
P.S.: thumbs up to solvers of tweeted challenge, @RootEval, @po6ix and @Huuuuu_Hundan. Congrats!