[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.