Starting the challenge

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.

Low Level

Understanding the application

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.

xss dom page

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:



The XSS reflected vulnerability is almost identical to the XSS 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.

Javascript code

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">
            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>");
    <input type="submit" value="Select" />

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:


xss dom low attack

And now a pop-up appears executing our script.

xss dom low popup


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.

Medium level

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>
    <!-- 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">

Exploiting the vulnerability

We try something random first scripthello to see how the application responds. We see that the random data injection worked properly.

xss dom medium injection

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)>

xss dom medium attack

Vulnerable code

On the server, the code looks like this:


// 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");


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.

High level

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:

  1. We try our data injection.
  2. The server responds with a location: to redirect us and set a default choice.
  3. We are being redirected and our injection failed.

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.

Exploiting the vulnerability

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 :


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.

xss dom high attack.

Vulnerable code

Let’s take a look at the server’s code :


// 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
            header ("location: ?default=English");


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.

Impossible level

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.

xss dom impossible attack.