DVWA - XSS Stored

5 May 2019

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 post a comment. We try out the application by posting a comment.

xss stored page

If we look at the request sent in Burp Suite Proxy > HTTP history here is what we get :

POST /vulnerabilities/xss_s/ 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_s/
Content-Type: application/x-www-form-urlencoded
Content-Length: 63
DNT: 1
Connection: close
Cookie: PHPSESSID=nbnj6k8ebi8g4j23697mmbne77; security=low
Upgrade-Insecure-Requests: 1

txtName=sammy&mtxMessage=Anyone+here+%3F&btnSign=Sign+Guestbook

We can see that our message is being transmitted through the txtName and mtxtMessage parameters :

txtName=sammy&mtxMessage=Anyone+here+%3F&btnSign=Sign+Guestbook

Once our message has been posted we can find it the list of comments on the page. Here is the HTML code of the comment section once our comment has been posted.

<div id="guestbook_comments">Name: test<br>Message: This is a test comment.<br></div>
<div id="guestbook_comments">Name: sammy<br>Message: Anyone here ?<br></div>

Low Level - XSS Stored vulnerability

The principle behing the XSS stored vulnerability is to inject some javascript in the web page. To do so we try to pass some javascript code to an input to see if we can get it to execute on the page.

In our example, instead of filling in the comments with a message we pass the text :

<script>alert("hello")</script>

If the page is vulnerable to a javascript injection, the source code of the page in the comment section should look like this:

<div id="guestbook_comments">Name: test<br>Message: This is a test comment.<br></div>
<div id="guestbook_comments">Name: sammy<br>Message: Anyone here ?<br></div>
<div id="guestbook_comments">Name: xss<br>Message: <script>alert("hello")</script><br></div>

The result is that the script tag is interpreted and executed by the browser and a pop-up appears with the text hello.

Low Level - Exploiting the vulnerability

Now that we know the basics of a XSS injection, let's try it ourselves. To do so we fill in a new comment with the following message :

<script>alert("hello")</script>

xss stored attack

When we click Sign the guestbook the page is refreshed automatically and our comments are loaded. A pop up immediately appears saying hello : our attack worked !

xss stored popup

Here is the HTML source code of the interesting comment section :

<div id="guestbook_comments">Name: xss<br>Message: <script>alert("hello")</script><br></div>

Low Level - Vulnerable code

Let's take a look at the PHP server code to understand the issue.

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

    // Sanitize message input
    $message = stripslashes( $message );
    $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Sanitize name input
    $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Update database
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    //mysql_close();
}

?>

As we can see from the code, the input is sanitized for SQL injection but not for XSS injections. A first line of defense would be removing the <script> tags from the user input.

Medium level

We setup the DB with Setup / Reset DB > Create / Reset Database and change the level to Medium.

The application is the same but if we try our exploit, it doesn't work. The script tag is removed from our comment.

script removed from comment

Medium Level - Exploiting the vulnerability

We suspect the server code of parsing the user input and removing the instances of <script>. Our first theory is that the php code looks like this :

str_replace( '<script>', '', $name );

If that is the case, the developer hasn't taken into account the case sensitivity of the str_replace function. If we try to pass the following injection as our message we should be able to trigger a pop up :

<Script>alert("gotcha")</script>

xss attempt

The result is disappointing, the message is stripped from every tag. What we didn't try yet is an injection in the name field; however, you will see that the character length of the name field is limited in the web application form.

To bypass the limitation we craft our request in Burp Suite. To do so :

  1. We go in Proxy > HTTP history and select the POST request that was sent.
  2. We right-click on the request.
  3. We select Send to Repeater.

send to repeater

Now we go in the Repeater tab and modify the txtName parameter so that it is identical to the mtxtMessage parameter.

The request should look like this :

txtName=%3CScript%3Ealert%28%22gotcha%22%29%3C%2Fscript%3E&mtxMessage=%3CScript%3Ealert%28%22gotcha%22%29%3C%2Fscript%3E&btnSign=Sign+Guestbook

Now that this is done we can click on Go to send the request.

xss attempt

This time it works and we get our popup.

xss popup

Medium Level - Vulnerable code

The developer's error was forgetting that str_replace is case sensitive. If the developer had used str_ireplace our injection would have been impossible. Here is a simplified version of the vulnerable code:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

    // Sanitize message input
    $message = strip_tags( addslashes( $message ) );

    // Sanitize name input
    $name = str_replace( '<script>', '', $name );
}
?>

High level

We change the level to High and clear the guestbook with the button Clear Guestbook. The application is exactly the same. Our previous exploits don't work, the script tags are removed whether or not we are using caps. We suspect that the developer has hardened the code and now takes into account the case sensitivity. That being said, thinking of every possible injection and preventing them can be quite hard. Injections can be done with <img> tags, <input> tags, <svg> tags and more.

To find a XSS, we will try a lot of these injections with the Burp Intruder and then see what worked. To configure the Burp Intruder, please refer to the post Configuring the Burp Intruder

We use the following parameters :

ParameterValue
Request sent to intruderPOST request from signing the guestbook
Payload positionstxtName and mtxtMessage values
Attack typeSniper
Payload typeSimple list
Payload OptionsLoad this file from IntruderPayloads

Once the attack has finished, we just have to reload the page and see if one or multiple popups appear. In our case a bunch of popup appear.

xss popup

To find one that triggered the popup we can try a dichotomy search or simply take a look at the comment section and look for anything unusual. When we see a broken image, we suspect it might be one of the injections that worked.

<div id="guestbook_comments">Name: <img src=x onload=prompt(1) onerror=alert(1) onmouseover=prompt(1)><br />Message: ee<br /></div>

High Level - Vulnerable code

If we look at the vulnerable code we can see that the developer failed to protect properly the $name variable. A better approach would have been to completely remove the tags from the name, or to convert all applicable characters to HTML entities with the function htmlentities or htmlspecialchars.

<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );
    // Sanitize message input
    $message = strip_tags( addslashes( $message ) );

    // Sanitize name input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
}
?>

Impossible level

In the impossible level, the developer secured the code properly by using htmlspecialchars. Tags will be converted so that they cannot be interpreted by the browser.

<?php
    $message = stripslashes( $message );
    $message = htmlspecialchars( $message );

    $name = stripslashes( $name );
    $name = htmlspecialchars( $name );
?>