A password-reset endpoint that uses request.get_host() without validation. Inject a foreign domain into the Host or X-Forwarded-Host header and watch the reset link point at your domain.
HTTP requests carry a Host header that tells the server which virtual host to serve. When an application uses that header to construct absolute URLs — such as password reset links — without validating it against a known-good list, an attacker can send Host: evil.com and the server embeds evil.com in the reset email. When the victim clicks the link their browser sends the one-time token to the attacker's server. The fix is to compare request.get_host() against Django's ALLOWED_HOSTS and always fall back to a hard-coded canonical domain.
| Bug Found | Unsafe reset endpoint — injected Host header echoed into the reset link without validation |
| Bug Found | X-Forwarded-Host bypass — unsafe endpoint also trusts the forwarded-host header set by proxies |
| True Positive | Safe reset endpoint — host validated against ALLOWED_HOSTS, canonical domain used regardless of header value |
| True Positive | Legitimate domain submitted — baseline behavior, link generated correctly |
The password reset endpoint calls request.get_host() and embeds the result in the reset link without validation. Submit any domain and see it appear in the generated URL.
Many applications filter the Host header but forget about X-Forwarded-Host, which load-balancers and reverse proxies set automatically. The unsafe endpoint trusts both — submitting via this path simulates the proxy-forwarded attack vector.
The protected endpoint checks the incoming host against the server's configured ALLOWED_HOSTS list. Any domain not in that list is rejected and the canonical domain is used instead. Try the same injected values — they are silently discarded.
A normal request using a known-good domain confirms baseline behavior. The reset link is generated correctly. This is the true negative — a valid host that should always work on both endpoints.
✗ Unsafe endpoint (baseline)evil.com and click Trigger Reset. The generated link contains evil.com — if emailed to the victim, their token is sent to the attacker (bug found).attacker.io to simulate a proxy-forwarded header. The unsafe endpoint treats X-Forwarded-Host identically to Host (bug found).evil.com again against the safe endpoint. The link always shows safronnynov.com — the injected domain is discarded (true positive).localhost or safronnynov.com against the safe endpoint. These are in ALLOWED_HOSTS so the safe endpoint accepts them (true positive).safronnynov.com confirms the unsafe endpoint works correctly when a legitimate host is used (true negative — no injection).