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 submit our name. We try out the application by posting the name test. The page reloads and displays Hello test.
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
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>
The most simple exploit is passing a script tag in the form.
The attack works immediately.
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.
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.
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>
.
Hurray ! Our theory was correct and we got a popup !
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>";
}
?>
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 :
Now if we go back to the results, we see that some attacks are flagged. These attacks are the ones that probably worked.
If we try the following attack, we will get a popup :
<svg/onload=alert(1)>//INJECTX
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>";
}
?>
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();
?>