This post is a "how to" guide for Damn Vulnerable Web Application (DVWA)'s brute force module on the medium security level. It is an expansion from the "low" level (which is a straightforward HTTP GET form attack), and then grows into the "high" security post (which involves CSRF tokens). There is also an additional brute force option on the main login screen (consisting of POST redirects and a incorrect anti-CSRF system).
Once again, let's pretend we do not know any credentials for DVWA.
Let's play dumb and brute force DVWA... again ...again!
TL;DR: Quick copy/paste
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Objectives
- The goal is to brute force an HTTP login page.
- GET requests are made via a form.
- The web page is in a sub folder.
- Low
- Straight forward HTTP GET brute force attack via a web form.
- Bonus: SQL injection (
See here for more information).
- Medium
- Extends on the "low" level - HTTP GET attack via a web form.
- Adds in a static time delay (3 seconds) on failed logins.
- High
- Extends on the "low" level - HTTP GET attack via a web form.
- This time uses a random time delay (between 0 and 4 seconds) instead.
- Uses an anti Cross-Site Request Forgery (CSRF) token.
Impossible- Submits data via HTTP POST via web form
- Accounts will lock out after 5 failed logins.
- Time delay before becoming unlocked (15 minutes).
- Unable to enumerate users on the system.
- Possible "Denial of Service (DoS)" vector.
PHPIDS- Does not protect against this attack.
- All attack methods are still the same!
Setup
- Main target: DVWA v1.10 (Running on
Windows Server 2012 Standard ENG x64
+IIS 8
).- Target setup does not matter too much for this -
Debian
/Arch Linux
/Windows
,Apache
/Nginx
/IIS
,PHP v5.x
, orMySQL
/MariaDB
. - The main target is on the IP (
192.168.1.44
), port (80
) and subfolder (/DVWA/
), which is known ahead of time. - Because the target is Windows, it does not matter about case sensitive URL requests (
/DVWA/
vs/dvwa/
).
- Target setup does not matter too much for this -
- Attacker: Kali Linux v2 (+ Personal Custom Post-install Script).
Both machines are running inside a Virtual Machine (VMware ESXi).
Tools
- cURL - Information gathering (used for viewing source code & to automate generating sessions).
- THC-Hydra v8.1 - A brute force tool.
- Patator v0.5 - An alternative brute force tool.
- SecLists - General wordlists.
- These are common, default and small wordlists.
- Instead of using a custom built wordlist, which has been crafted for our target (e.g. generated with CeWL).
Creating a Session Cookie
This was covered in the first post, low level, which will act as a "base" to this post. I'm not going to cover this again, so if things are not clear, I highly suggest you read over the previous post first.
The command has not changed; the target has not changed, so the output and result will be the same as the levels below.
1 2 3 4 5 6 |
|
Note, depending on the web server & its configuration, it may respond slightly differently (in the screenshot: 192.168.1.11
is Nginx,192.168.1.22
is Apache & 192.168.1.44
is IIS). This is a possible method to fingerprint an IIS web server.
Information Gathering
Form HTML Code
The first thing is, to try and find out what is different (without looking at the server side PHP source code). Using the same commands as before, let's see what the HTML code page is for the web form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Does not look too different from before, but let's check.
1 2 3 4 5 |
|
There is nothing different between the pages. Okay, now what happens if we compare responses when logging in incorrectly, with an invalid user credential?
1 2 3 4 5 6 7 8 9 |
|
Looking at just the output, other than the core DVWA information, there is not any difference in the brute force module displayed output. However, making the request, there was a noticeable time difference in response times.
1 2 3 4 5 6 7 8 9 10 |
|
It is clear that there is now a three second time delay when trying to login with incorrect credentials. This by itself will not offer brute force protection; however, it might mean it will slow down the brute force speed, increasing the time needed for the attack. The question is, will it also have the time delay on a successful login? Without a valid user credential to login as with, we cannot know this (there is not a "register"/"sign up" page and we were not given any in our pretend scope). What we should now do is setup a cloned environment in a test lab (as the project is free and open source) and either look at the source code or login with our admin account. However, we are going to pretend it is a custom built application so we cannot duplicate the setup. We are also going to skip the "view source" function which is in-built to DVWA's core, allowing us to see how the module works.
Attack Vectors
There are two possible ways we can approach this now. The first method, just as before, is the same brute force command, however, this time make sure the "timeout" value is able to cope with the additional wait delay from DVWA (the 3 seconds cool down) as well as all the threads/system load produced. This means there could be a significant time increase to perform the brute force. Alternatively, what happens if we set the timeout value to be less than three seconds? We would not be able to use a large number of threads (a larger thread count, means more requests, more requests result is the system working harder, therefore taking longer to process our request). This is a possible option and only an option because by using the rest of the site, all our requests take less than a second to load (so it depends on the network connection speed and target performance), therefore we are assuming (and taking an educated guess) that there would not be a delay with a valid login.
So what happens if we make four requests, one after another? This would mean request #2-#4 needs to be sent inside the three second cool down of request #1. Does request #4 only take three seconds to respond back or does it have to wait the additional time of the all the other request's cool down time too? We can find out by doing the following:
1 2 3 4 5 6 7 |
|
Before we execute this, I will explain what is happening (as it will get a little more complex when executed). All of this is a single "command" (even though it is on multiple lines, it is chained together). It can be broken down like so:
date
- This will display the date/time for our starting point.;
- Once the previous command finishes, regardless of the exit code, run the next command.curl -s ...
- Make an invalid login attempt to the target.&
- "background" the previous command, allowing the rest of the commands to keep on running without waiting.\
- instructs bash there is an additional line and not to execute the command just yet.curl...
+&
- these are repeated another 3 times (requests #2-#4).&&
- Signals to wait for the previous command to finish. If it was successful, to move onto the next command.date
- Display the end date, when request #4 finishes.sleep 30s
- Wait 30 seconds before doing the next loop. This is to allow all the other requests to finish.
TL;DR: Display the time before starting. On the 4th (and final?) request, display the time again after the cURL command finishes executing. Compare both timestamps.
It is always good practice to repeat a test multiple times, to make sure the results are consistent (Let's forget: "Insanity: doing the same thing over and over again and expecting different results." ~ Albert Einstein).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
...Looks like we are not going too crazy, as there were different results!
- Loop 1: 12 seconds.
- Loop 2: 3 seconds.
- Loop 3: 9 seconds.
Unfortunately, this means when we do our brute force attempt we need to allow for the maximum possible wait time. There are no shortcuts; otherwise we may not get reliable results (thus wasting all of our time).
Just because we have finished making requests or brute forcing it, does not mean the web application is still processing requests/cooling down.
Minimum Wait Time
We can calculate the minimum value for the wait time as follows:
1 2 3 |
|
The 2 seconds for the leeway amount is a "safe net" in case of there being an additional lag (would need to be higher if based on the slowest point in the attack). Note, in the low security posting the value was set to 3 seconds.
This means there could be a possible extra 11 seconds of wait time between "low" and "medium" when using 4 threads. This is because there is a guaranteed of at least 3 seconds delay for each thread.
Again, if the wait time is too low, it will not find a successful login. See the "low level" for a deeper explanation.
Note, if this was a "live" box in production, which means other users could be using it, what happens if they are failing to login at the same time as we brute force? This might have an effect; is the cool down based upon session values, IP, or complete web application?
Brute Forcing
Hydra
We are using the same arguments as in the low level, however their values may be different.
Debug/Test command:
- (
1 thread
*3 seconds sleep
) +2 seconds extra
=5 second wait time
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Main Command:
- (
5 threads
*3 seconds sleep
) +2 seconds sleep
=17 seconds wait time
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Patator
We are using the same arguments as in the low level, however their values may be different.
Debug/Test command:
- (
1 thread
*3 seconds sleep
) +2 seconds leeway
=5 seconds wait time
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Main Command:
- (
5 threads
*3 seconds sleep
) +2 seconds safe net
=17 seconds wait time
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
|
See how the numbers in the "num" column are different? That is the thread ordering finishing. You can see on the timestamp, in the first column which increases every three seconds. The successful login is indicated by having a different page size (5285
) and content length amount (5016
).
Summary
Benchmark
Same targets and setup as before with the low level:
192.168.1.11
- Raspberry Pi v1 "B" // Nginx v1.8.0 // PHP v5.6.14 // MariaDB v10.0.21192.168.1.22
- Raspberry Pi v2 "B" // Apache v2.4.10 // PHP v5.6.13 // MySQL v5.5.44192.168.1.33
- Windows XP SP3 (VM: 1 Core/512 MB) // Apache v2.4.10 (XAMPP v1.8.2) // PHP v5.4.31 // MySQL v5.5.39192.168.1.44
- Windows Server 2012 (VM: 1 Core/2048 MB) // IIS v8.0 // PHP v5.6.0 // MySQL v5.5.45
Increasing the thread count here is not going to have a major effect. The key here is the wait time being long enough (for the built in delay for the web app cool down and thread count). The rest is down to "luck" with the ordering of threads finishing. As long as there is always a request/thread which is waiting to be processed, due to the three second cool down, there cannot be any performance benefit by increasing the threads. If anything, having a larger amount of threads here will only make it more complicated.
A possible way to attempt to speed up the attack is to use a "better" wordlist; e.g. targeted towards the target, sorted by popularity, have only base-words in the list (i.e.: password
rather than password1999
), no leading/trailing spaces/tabs and no duplicate entries.
Results: (With 15 seconds timeout)
HYDRA | 192.168.1.11 | 192.168.1.22 | 192.168.1.33 | 192.168.1.44 |
---|---|---|---|---|
1 Thread | 19 mins 09 secs | 20 mins 01 secs | 18 mins 26 secs | 17 mins 50 secs |
2 Threads | 19 mins 03 secs | 17 mins 40 secs | - | 17 mins 43 secs |
4 Threads | 19 mins 00 secs | 17 mins 43 secs | - | 17 mins 46 secs |
------------- | ------------------ | ------------------ | ------------------ | ------------------ |
PATATOR | 192.168.1.11 | 192.168.1.22 | 192.168.1.33 | 192.168.1.44 |
---|---|---|---|---|
1 Thread | 18 mins 19 secs | 17 mins 30 secs | 17 mins 44 secs | 17 mins 24 secs |
2 Threads | 18 mins 12 secs | 17 mins 35 secs | - | 17 mins 25 secs |
4 Threads | 18 mins 15 secs | 17 mins 39 secs | - | 17 mins 28 secs |
------------- | ------------------ | ------------------ | ------------------ | ------------------ |
192.168.1.33
is still unable to handle multiple threads, just like in the low security level.
Why is Hydra slower than Patator?
TL;DR: Hydra makes an additional unwanted GET request.
So both in the low level and medium level, Patator has outperformed Hydra. It is time to try and look into why.
Looking back at the help screen for Hydra, man hydra
:
1 2 |
|
That makes sense. Now, let's use a proxy (Burp Suite), and hook in both Hydra and Patator and attack the same target. We will use the same password wordlist, wait times, thread count and total possible combinations to try. Because we are using a proxy, we can monitor; what is being sent and when it happens, then compare the results. The top image is Hydra, the bottom is Patator. The values highlighted in orange show the requests which contain our GET parameters, used to brute forced.
So there are two things which stands out:
- Hydra is making double the amount of requests compared to Patator.
- Half the requests are with GET parameters (which is what we would expect to see), and the rest is without any parameters set (undesired?).
- The request styles are being alternated, one with GET parameters, one without, one with, without...
- So it seems they are being sent out as a "pair" of requests.
- The timings of the first two requests (aka the first "pair") are different from all other sets.
- These two requests are being sent out at the same time as each other.
- All the other requests wait for the previous one to timeout.
- This is why Hydra takes much longer, having to wait for the extra (unused?) GET request (without any parameters) to timeout.
So there are two seconds between each attempt to brute force, however, we will only wait for one second to see if there is a reply back (then sit around for the same amount of time "doing nothing, wasting time"). Increasing the thread count is not going to speed this up!
What does this mean? How could we predict the maximum amount of time needed to-do the brute force attack?
- Hydra
- (
possible combinations amount
+ (possible combinations amount
-1 pair sending at the same time
)) =total amount of requests
*timeout value
=total time
. - (
5 combinations
+ (4 unwanted requests
)) =9 requests
*1 second
=9 seconds to complete
.
- (
- Patator
possible combinations amount
=total amount of requests
*timeout value
=total time
.5 combinations
=5 requests
*1 second
=5 seconds to complete
.- The reason why
time
reported Patator took 6 seconds, there is a little delay between Patator opening up and closing down. Patator has its own inbuilt counter, which displays it took 5 seconds.
Hydra can be sped up, by creating a drop rule for the undesired GET request. An example of this can be found when we did the main login page.
Note, normally the logic would be for every thread wait 3 seconds (1 thread
* cool down seconds
). However, because Hydra's extra GET & wait, every thread waits 1.5 seconds (1 thread
* cool down seconds
/2). This did trip me up, as I was able to do more threads without having to increase the timeout value.
Conclusion
This attack is very similar to the low level, as a result we are able to use the same PoCs (bash / python) without any alterations. Adding a cool down time on failed logins is not a deterrent for brute force and is not adequate protection. The wait delay will only increase the time needed to perform the attack, slowing down the attack speed.