Refer to the post start DVWA with Docker to learn how to start DVWA.
I will mostly use Burp Suite to solve the challenges. To configure Burp suite refer to the post configure burp suite for DVWA.
Click on the XSS (Stored) button on the left menu to access the challenge.
We reach a page with a drop-down list allowing us to choose a langage.
If we choose English
, a GET request is sent letting the server know what langage we chose :
GET /vulnerabilities/xss_d/?default=English
We can look at the request sent by the application in Proxy > HTTP history.
We the page has reloaded the default
parameter keeps the value
we gave it when we did our selection and the drop-down list
is automatically set on our selection:
http://localhost/vulnerabilities/xss_d/?default=English
The XSS reflected vulnerability is almost identical to the [XSS stored]({% post_url others/2019-05-05-dvwa--xss-stored- %}#stored) vulnerability. In this particular XSS attack we do not change the HTTP response but trick the Javascript code of the page into crafting a XSS injection for us.
Here is the OWASP's definition:
DOM Based XSS (or as it is called in some texts, “type-0 XSS") is an XSS attack wherein the attack payload is executed as a result of modifying the DOM “environment" in the victim’s browser used by the original client side script, so that the client side code runs in an “unexpected" manner.
Let's take a look at the code on the page:
<p>Please choose a language:</p>
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
As we can see, the code writes the drop-down list so that the default value is the one we selected previously.
The code retrieves the selected langage from the GET parameter
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8)
And then uses it to write the first option of the drop-down list:
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
In the same spirit as the SQL injection, we try to build a parameter so that the code writes the following HTML:
<option value='whatever'></option><script>alert(1)</script>
Here is the parameter we send instead of English
:
hello'></option><script>alert(1)</script>
And now a pop-up appears executing our script.
The issue was that the code trusts too much the user input, allowing it to add some arbitrary javascript code to execute.
A first and candid defense would be to look for <script>
tags and remove them from the user input.
When we examine the medium level, we can see that the requests, the responses, and the code are exactly the same as the ones in the low level.
However the XSS does not work anymore. This must mean that there is some server side
security that has been added to scout for XSS in the default
parameter.
Our strategy is to find the edge case that the developer hasn't thought of to find a way to execute our script.
To do so we test multiple XSS from the OWASP CheatSheet.
We craft our queries so that the code would write an HTML page looking like this :
<form name="XSS" method="GET">
<select name="default">
<option value="ourPayload">French</option>
</select>
<!-- OUR PAYLOAD WOULD BE WRITTEN HERE -->
<script>alert(1)</script>
<!-- END OF OUR PAYLOAD -->
<option value="" disabled="disabled">----</option>
<option value="English">English</option>
<option value="French">French</option>
<option value="Spanish">Spanish</option>
<option value="German">German</option>
<input type="submit" value="Select">
</form>
We try something random first scripthello
to see how the application responds.
We see that the random data injection worked properly.
When we perform our tests we notice that the script
tags are being excluded.
For instance if we try hello<script>hello
:
GET /vulnerabilities/xss_d/?default=hello%3Cscript%3Ehello HTTP/1.1
The server responds with location: ?default=English
which redirects us with to the page with the selection English
.
So we try with other tags from this short list of xss injections), and the working XSS is :
French</option></select><img src=a onerror=alert(1)>
On the server, the code looks like this:
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
The code looks for scripts tags and switch to a default choice if one is found. Whilst this is a first defense (acknowledging that an injection might be possible) this fails to take into account other ways to inject code in the page.
A better (but not yet perfect) way to solve the issue would be to convert all html special characters.
In this level, we try to inject some XSS with every tag possible but none of them work.
Actually even injecting some random data like hello
does not work.
What happens is:
location:
to redirect us and set a default choice.So it seems that the servers looks at the default=
parameter and compares it to the existing choices. If none of the choices are detected in the parameter, the server redirects us.
Even though the server code seems to filter every injection attempts, the javascript code is still very vulnerable. So if we find a way to bypass the server's filtering we will still be able to perform a cross-site scripting attack.
What we can try to use HTML anchors to pass some DOM XSS injection.
To do so we place our injection script after the common URL and behind the #
sign :
http://localhost/vulnerabilities/xss_d/?default=French#<script>alert(1)</script>
The first part http://localhost/vulnerabilities/xss_d/?default=French
will be used and sent to the server.
The anchor element, however, is used to provide a link within the page itself and is not sent by the server but will be used by the web browser.
So when the browser executes the following code, our injection is still in the URL and transmitted to the browser :
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8)
And we can see that we manage to display an alert popup.
.
Let's take a look at the server's code :
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
Our hypothesis was correct, the server validates the parameter's value.
If we look at the javascript code of the page we will also see that decodeURI
is used :
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
This is the reason why we were able to inject some javascript code. The <
and >
, passed as %3C
and %3E
in the URL are decoded and interpreted as tags by the browser.
The problem is that the javascript code is vulnerable to an injection but the php code is used to secure the application. This does not remove the vulnerability. To do so we need to secure the javascript code itself.
In the impossible level, the decodeURI
is not used anymore.
That way, if we try to use a XSS injection, the <
and >
parameters will stays as %3C
and %3E
and won't be interpreted by the browser.
.