[This lab](https://portswigger.net/web-security/authentication/multi-factor/lab-2fa-bypass-using-a-brute-force-attack) is about a **weak 2FA verification endpoint that can be brute-forced** because it doesn’t enforce meaningful rate limiting. The only “defense” is that after two wrong codes you get logged out, but that’s not real protection—Burp can **auto-log you back in before every guess** using a macro + session handling rule, letting Intruder grind through `0000–9999` until the correct code hits.
---
## 0) What you’re exploiting
* The 2FA endpoint (`POST /login2`) accepts a **4-digit code**.
* The app enforces a *tiny* “anti-bruteforce”: after **two wrong codes**, it **kills your session** (logs you out).
* If you brute force normally, most requests will be **unauthenticated** after the second wrong attempt.
* Solution: **before each Intruder request**, run a macro that logs in again so each guess is made with a fresh valid session.
---
## 1) Capture the 2FA flow and identify the target request
1. In the browser, go to the lab and **log in** with:
* `carlos : montoya`
2. You’ll land on a page asking for a **4-digit security code**.
3. In Burp **Proxy → HTTP history**, find:
* `POST /login` (username/password)
* `GET /login2` (2FA page)
* **`POST /login2`** (submits the 2FA code)
**Reasoning:** `POST /login2` is what you’ll brute force. The macro must reproduce the steps that get you back to the “enter code” stage.
---
## 2) Create a macro that reliably returns you to the 2FA prompt
4. **Settings (⚙️) → Sessions → Session Handling Rules → Add**
5. **Scope tab → Include all URLs**
**Reasoning:** Intruder will hit `/login2` repeatedly. Keep scope broad so the rule actually applies.
6. **Details tab → Rule actions → Add → Run a macro**
7. Under **Select macro → Add** (Macro Recorder), select these **3 requests** (in this order) from history:
* `GET /login`
* `POST /login` (contains `carlos/montoya`)
* `GET /login2`
8. Click **OK** to save the macro, then **Test macro**.
What you want in the **final response**:
* The page asking for the **4-digit security code** (the 2FA form)
**Reasoning:** If the macro ends anywhere else, Intruder will start guessing codes while not in the correct authenticated 2FA state.
> Practical tip: If the macro fails, it’s usually because you didn’t record the exact request sequence the app expects, or you recorded a stale CSRF token. Re-record using the freshest requests from history.
---
## 3) Make sure Intruder requests actually use the macro’s fresh session
In the same Session Handling Rule, add (or verify) a second action:
9. **Rule actions → Add → Use cookies from Burp’s cookie jar**
(Apply to the same scope / all URLs)
**Reasoning:** The macro receives new `Set-Cookie` values. Intruder must send the **updated session cookie** with each `POST /login2` attempt. This action is the glue that applies the macro-updated cookies to the outgoing Intruder request.
(Depending on your Burp version/config, “Run macro” may already update the cookie jar, but explicitly adding “Use cookies from cookie jar” makes it robust.)
![[Pasted image 20251212181237.png]]
---
## 4) Configure Intruder on `POST /login2`
10. Send **`POST /login2`** to Intruder.
11. In Intruder → **Positions**, put the payload marker **only** around the `mfa-code` value.
Example (typical body):
```http
POST /login2 HTTP/2
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
mfa-code=§0000§
```
**Reasoning:** You want one variable: the 4-digit code. Everything else stays constant so differences in response are meaningful.
![[Pasted image 20251212181208.png]]
---
## 5) Payload setup: 0000 → 9999 (with fixed width)
12. Intruder → **Payloads**
* Payload type: **Numbers**
* Range: **0** to **9999**
* Step: **1**
* Min integer digits: **4**
* Max fraction digits: **0**
This generates: `0000, 0001, 0002, ... 9999`.
**Reasoning:** The app expects exactly 4 digits. Fixed-width avoids “123” vs “0123” mismatch.
![[Pasted image 20251212181145.png]]
---
## 6) Critical reliability: set concurrency to 1
13. Intruder → **Resource pool**
* Put the attack into a resource pool
* Set **Maximum concurrent requests = 1**
![[Pasted image 20251212181300.png]]
**Reasoning (important):**
* If you run multiple concurrent attempts, you’ll create race conditions:
* multiple guesses share/kill sessions unpredictably
* macro cookies might get overwritten mid-flight
* Concurrency 1 ensures: **macro → single guess → repeat**, clean and deterministic.
---
## 7) Run, detect success, and log in
14. Start the attack.
15. Look for a request that returns **HTTP 302** (instead of “invalid code” response).
16. Right-click that request → **Show response in browser** → open the URL.
17. Go to **My account** to confirm you’re Carlos and solve the lab.
**Reasoning:** Correct code typically triggers a redirect (302) to account/home, and sets the authenticated state.
![[Pasted image 20251212182528.png]]
---
## 8) If you don’t hit it (because the code rotates)
The lab warns: the verification code may reset *during* your run. If that happens, it might reset to a number you already tried earlier in the attack.
What to do:
* **Repeat the Intruder attack** (often works after 1–3 runs).
* To increase your odds, restart from a different offset occasionally (e.g., stop at 2500, restart, etc.).
**Reasoning:** You’re racing a changing target; repeated coverage eventually overlaps a “fresh code” you haven’t tried yet in the current run.