Vulnerable applications: exploring the DVWA #1

5 minute read

The Damn Vulnerable Web Application was the first application I ran into that was made for the sole purpose of illustrating web application vulnerabilities. It seems only fitting to start this series of vulnerable applications with it.

The goal that I have with these posts is to illustrate how to use several techniques to break the defenses of poorly written applications.

Spoiler Alert: if you intend to give the challenges a try, don’t read on. I will tell you all there is to know to pass the challenges, so I will most definitly take the fun out of it. You’ve been warned!

Disclaimer

The things we are doing in this series can be quite damaging to real websites. What we do here is purely for education, in a lab environment. If you try this on the website/server of another party without permission you are probably breaking the law! Be responsible.

Lets get started!

After running the setup for your DVWA installation, the first thing you will see is the login screen. Obviously the credentials are part of the documentation, but what is the fun in that? Lets see if we can get into the application without actually knowing them.

login

When we fill in some credentials we will figure out what kind of mechanisms the application is using for its login proces. I have my firefox connected to a mitmproxy so that we can inspect every single request.

the request

What you see in the above screenshot is that we are sending the credentials, test/test along with a Login key/value and a user_token value to the /login.php script as a POST.

The user_token seems to be a anti-CSRF token, which will mean we might have a really hard time brute forcing the form if it is implemented correctly. Spoiler: it is not.

We are also sending a cookie which seems to identify our sessions in the request. We will need to take this cookie and send it along with our attack.

Lets take a lookt at what is returned.

response

The mechanism used by the application is to redirect the user after a login attempt. In this case, with incorrect credentials, we are redirected back to the login.php page. I guess it is safe to assume a successful login will redirect us to some other place.

The next thing to check is if there is some form of account lockout. After trying 5 times it is safe to say there is no lockout happening, so we can give the brute force attack vector a try.

Lets create a small script that will grab the user_token from the login page and store the cookie to a local file. curl is an exceptional tool for this, all it needs is some awk magic and we have both our anti-CSRF token and the PHPSESSID value from the cookie.

Next we will actually use the hydra brute forcing tool to try every available combination of usernames and passwords from the SecLists prebuilt lists. These lists are very good for these types of attacks as they have an extensive username list as well as 10 million passwords to use. We will actually be using the shortlist variants in our attempt.

As a Failure condition to hydra we will tell it that the attempt was unsuccessful when we get redirected back to /login.php. The following table explains the parameters to the hydra application.

paramterdescription
-Lfile with usernames to try (SecLists)
-Pfile with passwords to try (SecLists)
-tNumber of threads to use
-sport of the remote host, 32770 in our case
http-post-formsend the paramters as a form post to the server
username=^USER^^USER^ is the current username
password=^PASS^^PASS^ is the current password to try
user_token=${CSRF}the anti-CSRF token we pull from the login page
F=Location\: login.phpour Failure condition, being redirect to login.php
H=Cookie:...an addition Header for our cookie
#!/bin/bash
DVWA_HOST="172.17.0.1"
DVWA_PORT="32770"

CSRF=$(curl -s -c dvwa.cookie "${DVWA_HOST}:${DVWA_PORT}/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')

hydra -L /usr/share/seclists/Usernames/top_shortlist.txt -P /usr/share/seclists/Passwords/top_shortlist.txt -t 1 -s $DVWA_PORT 172.17.0.1 http-post-form "/login.php:username=^USER^&password=^PASS^&user_token=${CSRF}&Login=Login:F=Location\: login.php:H=Cookie: security=impossible; PHPSESSID=${SESSIONID}"

Lets give the script a try. The sad news it failed miserably.

[DATA] max 1 task per 1 server, overall 64 tasks, 275 login tries (l:11/p:25), ~4 tries per task
[DATA] attacking service http-post-form on port 32770
[STATUS] 97.00 tries/min, 97 tries in 00:01h, 178 to do in 00:02h, 1 active
1 of 1 target completed, 0 valid passwords found

When we re-run our script, but this time with the environment variable HYDRA_PROXY set to our mitmproxy we can actually see what is going on.

attack-1-requests

hydra is retrieving the login page after it has performed a POST. This does not seem like a problem, until we take a look at the response of one of the GET requests.

attack-1-response

The highlighted text shows what we already feared. The anti-CSRF token that we keep sending is refreshed on every request, causing a ==CSRF token is incorrect== error.

I wonder if this CSRF token is implemented correctly. In a good implementation it should be marked invalid as soon as it has been used, but here we get it returned in the GET request, so perhaps if we would drop a few of the GET requests in our run. If it is implemented incorrectly the first GET request that we let through should indicate ==Login failed== as it shows in the browser when you try to login.

Using mitmproxy this is very easy. As the only requests going through the proxy are ours we can just intercept the GET requests with the filter ~m GET. When the intercept is trigger we can delete the flow with the d key.

attack-2-requests

Lets take a look at the response.

attack-2-response

As you can see it shows ==Login failed==, this means the anti-CSRF token is not correctly implemented. We can get around this!

The mitmproxy (0.18.2) allows us to run some custom scripts that can actually drop requests like these. Lets create drop.py. Please note that the markdown parser of my blog breaks the entered code, so I put a space between the equality operator, please change them back when running the script, thanks!

"""
Drop GET requests to /login.php of DVWA
"""
from mitmproxy.models import HTTPResponse

def request(flow):
    # pretty_url takes the "Host" header of the request into account, which
    # is useful in transparent mode where we usually only have the IP otherwise.
    # The = = should be attached, but the markdown
    # parser messes this up, please do it manually, thanks :)
    if flow.request.method = = "GET" and flow.request.pretty_url = = "http://172.17.0.1:32770/login.php":
        flow.reply.kill()

We can now restart our proxy, but using mitmdump instead of mitmproxy.

mitmdump -s drop.py

When we rerun our attack the screen will start to scroll. The only requests actually going through are the POST requests to /login.php.

attack-3-mitmdump

When you let hydra run for a minute it will show you some beautiful credentials.

succes

To log in, all you need to enter is admin and password.

landing

There you have it; we successfully brute-forced our way into the DVWA.