DVWA - XSS Reflected

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 access an application allowing us to submit our name. We try out the application by posting the name test. The page reloads and displays Hello test.

xss reflected page

If we look at the request sent by the application in Proxy > HTTP history we can see that a GET request is sent with our input in the parameter name:

GET /vulnerabilities/xss_r/?name=test HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost/vulnerabilities/xss_r/
DNT: 1
Connection: close
Cookie: PHPSESSID=nbnj6k8ebi8g4j23697mmbne77; security=low
Upgrade-Insecure-Requests: 1

XSS Reflected

The XSS reflected vulnerability is almost identical to the XSS stored vulnerability. The only difference is that with the reflected XSS, the injection is not stored on the server.

To manipulate a user into executing our script we will have to make him visit our crafted link, for instance :

http://site.com?name=<script>alert("a")</script>

Exploiting the vulnerability

The most simple exploit is passing a script tag in the form.

xss attack

The attack works immediately.

xss attack

Vulnerable code

On the server side, the code does not check if the user is attempting a XSS injection. It simply pastes the user input in the HTML code.

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?> 

A first line of defense would be removing the script tags.

Medium level

We change the security level to medium. The application is the same but is not vulnerable to our previous exploit. The script tags seems to be removed from the user input.

xss attack

Exploiting the vulnerability

From the server response we suspect the code to remove all instances of script in the user input.

A common mistake is to forget to take into account the case sensitivity when doing this removal.

Let’s try our hypothesis with the string <Script>alert("hello")</Script>.

xss attack

Hurray ! Our theory was correct and we got a popup !

xss popup

Vulnerable code

On the server side, the code uses str_replace to remove all instances of the string <script> but fails to take into account that <Script> or <sCRipt> or <scriPt>,… are also valid tags !

A better approach would have been using the function str_ireplace that is case-insensitive.

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = str_replace( '<script>', '', $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
}

?> 

High level

We change the security level to High. The application is identical but our previous tricks do not work anymore.

If we try to enter <Script>alert("hello")</Script> we only get the answer Hello >.

It appears the developer has evolved and is now stripping all instances of the script tag.

We will also have to step up our game and get out the big guns : the Burp Intruder. XSS injection can be done through the script tag but also a multitude of other tags such as img, svg, input, iframe, etc…

To test every possibility with use the Burp intruder.

To configure the Burp Intruder, please refer to the post Configuring the Burp Intruder

We use the following parameters :

Parameter Value
Request sent to intruder POST request from submitting our name
Payload positions name value
Attack type Sniper
Payload type Simple list
Payload Options Load this file from IntruderPayloads

We can then start our attack. But how are we supposed to know if an attack worked ? One way is to check if our injection is present in the server response. To do so we configure our attack to match against our pre-URL encoded payload in the server response :

  1. Go the the Options tabs, Grep -Payloads options.
  2. Check “Search responses for payload strings”.
  3. Check “Match against pre-URL-encoded payloads”.

intruder grep config

Now if we go back to the results, we see that some attacks are flagged. These attacks are the ones that probably worked.

intruder grep results

If we try the following attack, we will get a popup :

<svg/onload=alert(1)>//INJECTX

Vulnerable code

The code protects itself from script tags but fails to take into account other tags. A proper way of sanitizing HTML input is to use a function like htmlspecialchars.

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
}

?> 

Impossible level

The impossible level uses the function htmlspecialchars to protect itself from XSS attacks. It is also using an anti-CSRF token which certainly helps preventing a phishing attack.

<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $name = htmlspecialchars( $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>