Authentication is the front line of an app's security. If it's breached, the authorization and cryptography beyond it become meaningless. As PortSwigger says, "authentication vulnerabilities allow attackers access to sensitive data," and if a privileged account is taken, the whole app is hijacked. This article explains those attack techniques faithfully to the official source.
An absolute premise: brute force, enumeration, and reset poisoning all carry strong invasiveness. Execute only within a legal lab or a scope authorized in writing. Unauthorized login attempts are themselves an act of unauthorized access (→ the legal guide). The map is the pillar.
1. Username enumeration — the first stage of the attack
Before brute force, confirming "valid usernames that exist" makes efficiency jump. Apps often leak the existence of a user through slight differences.
| Leak signal | Example |
|---|---|
| Message difference | "Invalid username" vs "Invalid password" |
| Response-time difference | Only existing users trigger a hash check, so the response is slower |
| HTTP status / subtle wording difference | Presence or absence of a trailing period, a different redirect target |
# Burp Intruder で username を辞書にして送り、レスポンスの差分(長さ・文言・時間)を観察
POST /login HTTP/1.1
Host: lab.example
Content-Type: application/x-www-form-urlencoded
username=§candidate§&password=wrongpass
With a Sniper attack in Burp Intruder, cycle only the username position and pin down valid users from outliers in response length or response time.
2. Brute force and bypassing protections
Once a valid username is known, you brute-force the password. The problem is flaws in the protection mechanisms.
2.1 Bypassing IP-based rate limits
"Block after a certain number of failures from the same IP" can be bypassed by header spoofing or IP rotation.
# X-Forwarded-For を毎回変えて、IPベースのカウンタを欺く(脆弱な実装で有効)
X-Forwarded-For: 1.2.3.4
2.2 The account-lockout loophole — password spraying
"Lock after N failures for one user" is bypassed by password spraying — trying the same weak password once each against many users. Each user's failure count never reaches the lockout threshold.
# 縦に弱いパスワードを固定し、横に大量ユーザーを試す(Pitchfork/Cluster bomb的)
user001 : Password1!
user002 : Password1!
... ← どのユーザーもロックされない
Furthermore, the "you've been locked out" response itself becomes an enumeration signal that "this user exists."
3. Multi-factor authentication (2FA/MFA) bypass
MFA is powerful, but it works only when implemented correctly. Typical holes PortSwigger lists:
3.1 Missing verification logic (skipping the second stage)
When the session issued after the first stage (password) succeeds can reach protected resources even with the second stage incomplete. Skip the 2FA input screen and hit the destination URL directly, and it goes through.
# 2FAページを経由せず、ログイン後のページへ直接アクセスして通るか確認
GET /my-account HTTP/1.1
Host: lab.example
Cookie: session=<1段目だけ通したセッション>
3.2 OTP brute force
If the verification code (OTP) has no attempt limit or rate limit, you can brute-force the 4–6 digits. It can be broken just by cycling 0000–9999 in Burp Intruder.
3.3 Verifying the wrong user
An implementation mistake where the second-stage verification accepts a code for a different user than the first stage's user. It happens when account identification is left to the client.
4. Other mechanisms — reset, Remember Me, change
4.1 Password-reset poisoning (the back door)
Password reset "temporarily relaxes identity verification," so it easily becomes authentication's back door.
- Guessable tokens: sequential, timestamp, or short tokens are predictable.
- Host-header poisoning: if the reset-link generation uses the
Hostheader, an attacker can sendHost: evil.exampleand plant a link to the attacker's domain in the email sent to the victim. When the victim clicks, the token goes to the attacker. - Identifier swapping: if the
username/userIdin the reset-confirm request can be changed to someone else, you can set another person's password.
# Hostヘッダ汚染:被害者宛リセットメールのリンクが攻撃者ドメインを指すよう仕込む
POST /forgot-password HTTP/1.1
Host: evil-attacker.example ← サーバーがこれを信頼してリンク生成すると致命的
Content-Type: application/x-www-form-urlencoded
username=victim
4.2 Remember Me / password change
If the "keep me logged in" token is guessable (a hash of the username, etc.), it's forged. If a password change doesn't verify the current password, it's used for persistence after session hijacking.
5. [Defender side] Root-cause defenses
Once you understand the attack surface, close it by design. The essence of PortSwigger and the OWASP Authentication Cheat Sheet:
// ✅ ユーザー名列挙を防ぐ:成否で「同一の」メッセージ・同等の処理時間を返す
async function login(username: string, password: string) {
const user = await findUser(username);
// ユーザーが存在しなくても必ずハッシュ照合を走らせ、応答時間を一定化(タイミング差を消す)
const hash = user?.passwordHash ?? DUMMY_HASH;
const ok = await verifyPassword(password, hash); // Argon2id 等
if (!user || !ok) {
// 列挙を許さない一定の文言(どちらが違うかを明かさない)
throw new AuthError("ユーザー名またはパスワードが正しくありません");
}
// ...
}
Design principles:
- Close enumeration: return the same message, equivalent response time, and same status for success and failure.
- Robust rate limiting: per-account + global, not just per-IP. Don't trust
X-Forwarded-For. Correct rate limiting on serverless is in the dedicated guide. - Implement MFA correctly: don't let it reach protected resources until the second stage is complete. Put an attempt limit on the OTP. The code should be short-lived, single-use, and bound to the first-stage user.
- Make password reset safe: tokens that are unguessable (CSPRNG), long, short-lived, single-use, and user-bound. For link generation, don't use the
Hostheader; trust the server-side canonical URL (a defense against HTTP Host header attacks). - Password strength and hashing: store with Argon2id, etc., and reject weak passwords. Details in the password-hashing/cryptography guide.
- Sessions: regenerate the session ID after authentication, and add
HttpOnly/Secure/SameSite.
For the choice of leaning the authentication foundation itself toward managed (Cognito/Auth0/Clerk/Supabase Auth), see authentication-platform selection. "Don't implement authentication yourself" is the safest call in many projects.
6. Summary
- A two-stage enumerate→brute-force setup: first identify valid usernames from message and time differences.
- Protection mechanisms get bypassed: IP limits by header spoofing, lockout by password spraying.
- MFA depends on implementation: watch for second-stage skip, OTP brute force, and verifying the wrong user.
- Reset is the back door: guessable tokens, Host-header poisoning, identifier swapping.
- Root-cause defenses: close enumeration with constant responses + robust rate limiting + correct MFA + safe reset + strong hashing.
Next, head to the complete conquest of SSTI, which hijacks the template engine to reach RCE.