Authentication Vulnerabilities: Lab 7: 2FA broken logic

Nov 7, 2025    #websecurity   #portswigger   #web-exploitation   #security-research   #authentication   #login   #username-enumeration   #response-timing   #portswigger-labs   #ctf-writeup   #2fa   #mfa   #python   #2fa-bypass  

Lab 7: 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.

Initial Reconnaissance/Discovery:

We have a standard login page as usual and access to an email server to receive our 2FA codes for our user.

Let’s login to view the process.

We are now prompted for our 2FA code.

Let’s get our 2FA code from the email client.

We are now logged in.

Breaking Down Authentication Flow:

/login

So we can see what is going on at the back end let’s break down the complete authentication flow by following it in burp.

If we look at our initial POST request of when we send our username & password we can see we get a 302 (redirect) to hit the endpoint /login2 and we can also see a cookie is set to the value of our username.

Set-Cookie: verify=wiener

/login2

Looking at the POST request for the endpoint /login2 where we supply our 2FA token we can see that we are passing the cookie with our name value, that was set previously, as well as the 2FA-code.

Weakness in the logic: Looking at this authentication flow we should be able to send the second request to intruder and set the cookie value to the name of the user we want to login as “carlos”, once this is done we should be able to then brute force the 2FA code.

Exploiting Flawed Login Logic To Bruteforce A Valid 2FA Code:

+Disclaimer+: So this took me a while to figure out as initially I just sent the POST request for /login2 to intruder and modified the cookie value to be “carlos’” name and bruteforced the 2FA code however this was not working; I was just getting standard 200 responses when I should of in-fact been getting 302 responses. This was when I realised “carlos” had not had a valid 2FA code issued. As we had not requested a 2FA code for “carlos” it would always fail as trying to bruteforce a value that doesn’t exist will never work. Instead what we need to do is trigger the website to generate an 2FA code for “carlos’” account and then bruteforce the 2FA code.

Requesting A 2FA Code For Carlos:

First let’s take the original GET request for /login2 and send this to repeater.

Now we modify the cookie value to contain “carlos’” name and send it, this will cause the site to generate a 2FA code for “carlos”, which should be sent via email to him.

Bruteforcing The 2FA Code Using Burp:

Now we can bruteforce the code by taking our valid POST request for /login2 and sending it to intruder.

We set the injection point to be the mfa-code value and also modify the cookie to contain “carlos’” name the value. For our payloads we can set the type as “number” and then set the values listed in the image, this ensures we step through every number from 0001 to 9999.

+Note+: The “Numbers” option is only available on burp pro & iterating through 9999 possible combinations will take a long while if using a burp community edition, so we can also script this in python too, which I will show.

Once we start our attack we can set our status code filter so that 302’s are at the top and we get a hit, meaning we have a valid code of 0808.

Bruteforcing The 2FA Code Using Python:

As I said above doing this with the community edition of burp will take a LONG time as they purposely throttle the requests intruder can make. There are ways around this, such as copying the POST request and using FFUF to bruteforce whilst proxying through burp, or proxying ZAP through burp (which does not limit requests), or scripting this ourselves in python and proxying through burp.

My python has gotten a little rusty as of late as I had found myself becoming a bit too reliant on LLM models for scripting basic things, so I have stopped using LLM’s for this as I want ensure I stay sharp. (If you use LLM’s that’s fine but for me I found I was losing my edge) so brace yourself for some ugly, sloppy yet still functional code.

Prep The Certificate:

If you want to proxy traffic through burp this is mandatory.

Open burp’s in built web browser and go to http://burpsuite & download the certificate by clicking on “CA Certificate” button on the top right corner.

Convert the certificate to the .pem format so the python requests module can use it.

openssl x509 -inform der -in certificate.cer -out certificate.pem
Python 2FA Bruteforcer:
import requests
import os
import time
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
proxy = 'http://127.0.0.1:8080'
os.environ['HTTP_PROXY'] = proxy
os.environ['HTTPS_PROXY'] = proxy
os.environ['REQUESTS_CA_BUNDLE'] = "certificate.pem"

proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
url="https://0afe00ec0370f1d6803adffc003c002f.web-security-academy.net/login2"
cookies={'verify' : 'carlos', 'session' : 'Z1XthxXeEPetLSeXVTNQIEeRhZtuH2xv'}

for i in range(9999):
    MFA=(f'{i:04}')
    try:
        request=requests.post(url, proxies=proxies, cookies=cookies, verify=False, timeout=3, data={
            'mfa-code': MFA
        })
        if request.status_code == 302:
            print(MFA)
    except requests.exceptions.HTTPError as errh:
        print ("Http Error:",errh)
    except requests.exceptions.ConnectionError as errc:
        print ("Error Connecting:",errc)
    except requests.exceptions.Timeout as errt:
        print ("Timeout Error:",errt)
    except requests.exceptions.RequestException as err:
        print ("OOps: Something Else",err)
Code Breakdown:

For all you nerds out there let’s break this down.

2FA code found Via Manual 2FA Brutforcer:

If we run the code we can see it works as we get a 302 response in burp, and that our 2FA code has a value of 1989.

+Note+: I scripted this after I had already completed the lab so the discovered 2FA code is different to the one I found via burp.

Logging In As Carlos:

Now that we have our code we actually need to login as “carlos”. To do this we will need to intercept valid requests and modify them in transit.

First we login as normal with our user “wiener” then when we get to the 2FA request we modify the cookie to contain “carlos” & we supply the 2FA token 0808.

Now the important part after we modify the previous request and send it on we will we make our request for the /my-account?id=[username] endpoint. For this we need to ensure we set the cookie value again to “carlos” and the forward the request.

Once done if we reload our page in our browser we can see we are logged in as “carlos” & we have solved the lab.



Next: Authentication Vulnerabilities: Lab 4: Broken brute-force protection, IP block