XSS introduction and multi-level challenge writeup.
9 minutes to read
A few months ago I took part in a multi-level XSS challenge organized by @haxel0rd with @ObscurityApp and later was asked to explain my solution. The challenge was divided into 10 levels with increasing difficulty. Almost each level was about exploiting different XSS context, which was great for the sake of learning. In this post I will describe each solution and as well as schematics behind them, so let’s dive in.
The challenge:
Can you beat all 10 levels..? #XSS #WAF #FilterEvasion #Challenge, from Beginner => Hard, pure XSS, no fantasy vectors. Will post first 7 solvers on my twitter. The challenge starts at login (no-xss), good luck. #Hacking #HackIt #CTF https://u.nu/hackit CodedBy: @ObscurityApp 💪 pic.twitter.com/VLSJ4mPZqy
— haxel0rd (@haxel0rd) April 12, 2019
Logging into the challenge was an entry obstacle itself. It was a simple SQL Injection - admin' OR 1=1-- t
- did the job, bypassing the authentication.
Brief XSS methodology:
To start exploiting XSS you first have to find out if and where our input is reflected on the attacked page(this is so-called injection context
). Once identified, we have to check what transformations are being made to our payload by the application, giving us the information how CSS/HTML/JavaScript sensitive characters are treated and what possibilities to inject malicious code are left unsecured. To do this in one request let’s use an XSS probe:
aaaaaa'">xsshere
I type this to the interesting input field, submit, and check the response for xsshere
string as shown in Lvl01 below.
The probe is sent via HTTP/S proxy like Burp and with opened Developer Console in browser to observe JavaScript errors.
Lvl01:
By following the steps from “Brief XSS methology” we can see here that our probe broke the rendering of the HTML - this is usually a good sign for a pentester and not that good for a developer :) You can read it as: some of the probe’s characters are not properly encoded/escaped before returning them to the client and therefore interpreted by a browser as a legitimate code, having an influence on the final look/JavaScript workflow of the page.
<input type="text" class="form-control input-lg" id="search-church" id="xss" value='aaaaaa'">xsshere' name="xss" placeholder="xss">
’
character closed an attribute, >
closed the tag and the rest was rendered as text.
Here, we have an HTML attribute injection context
. The HTML attributes can be enclosed by characters: '
, "
or they can appear without anything - up to the first white character so injecting '
will close the attribute and allow us to add a new one or just close HTML tag with >
character. This happened in the example above - input
tag was closed, so we can inject a new tag. To achieve our goal - execute alert(1)
we have to inject <script>alert(1)</script>
.
Final payload:
Add a dummy attribute value inside an input, close the attribute, close the input tag, add a script tag with JavaScript code.
x'><script>alert(1)</script>
which translates to:
<input type="text" class="form-control input-lg" id="search-church" id="xss" value='x'><script>alert(1)</script>' name="xss" placeholder="xss">
Lvl02:
At the level 2, the probe from Lvl01 also ended up in the HTML attribute context
but this time the >
character is properly escaped to its HTML equivalent: >
therefore, it makes it hard to escape from the context but it doesn’t mean we can’t inject new attributes. To achieve our goal we have to use HTML events which as value takes JavaScript.
<div class='aaaaaa'">xsshere'>like Aldus PageMaker including versions of Lorem Ipsum.</div>
Solution:
x' onmouseover=alert(1) x'
and this is how our payload looks in HTML:
<div class='x' onmouseover=alert(1) x''>like Aldus PageMaker including versions of Lorem Ipsum.</div>
alert(1)
will be executed after triggering the onmouseover
event. It is important to take care of the last '
character simply adding a dummy x
attribute.
Lvl03:
Sometimes the input to the application is passed via URL, we have to identify the parameters which are used in the JavaScript source code(static analysis).
$('#hmmm').append("<li ${_text}>Hello world!</li>");
the above line is vulnerable to XSS, again the HTML attribute context
but we can’t use any of interesting characters because of filtering:
function escapeOutput(toOutput){
return toOutput.replace(/\&/g, '&')
.replace(/\</g, '<')
.replace(/\>/g, '>')
.replace(/\"/g, '"')
.replace(/\'/g, ''')
.replace(/\//g, '/');
}
Luckily we do not have to use them. Browsers are so kind that they try to fix “mistakes” for us:
Payload:
xss=onmouseover=alert(1)
the value alert(1)
of the event is automatically enclosed with "
character.
Lvl04:
This level was solved with an unintended solution and introduces the next injection context - JavaScript context
.
Developer console shows the following JavaScript error:
This tells us that the execution of the JavaScript was interrupted because of the injected payload. In a scenario like this we usually want to:
- Close all opened strings, parentheses, remember that the JavaScript before injection must be “satisfied”.
- Add
;
as we want to start new instruction. - alert(1)
- Deal with code that was there before injection making it as a comment with
//
.
Solution:
xsshere'); alert(1)//
Intended solution:
Lvl05:
This level is very similar to the previous one - we are once again in the JavaScript context
.
function escapeOutput(toOutput){
return toOutput.replace(/\&/g, '&')
.replace(/\</g, '<')
.replace(/\>/g, '>')
.replace(/\"/g, '"')
.replace(/\'/g, ''')
.replace(/\//g, '/');
}
let input = `aaaaa'">xsshere`;
$("#hello-xss").append(`Nothing intresting found? Input = '${escapeOutput(input)}'`);
But there is a filter - we can’t start a new script
tag. There are three strings literals in JavaScript: '
, "
and `
. Following schematics from the previous level, we craft a payload like: xsshere ` ; alert(1)//
and that is the solution for the current level.
TBC
The rest of 10 levels to be continued.
Credits:
@haxel0rd @ObscurityApp for creating the tasks
Posted by Maciej Piechota on