Back to Case Studies
EdTech / SaaS
Web Application VAPT
2026

The Reset Link That Pointed at Us: Host-Header Account Takeover in a Mid-Market Learning Platform

Redacted (E-Learning / LMS, ~120 employees)

1 week

account takeover
password reset poisoning
host header injection
authentication

High

Severity

Password Reset

Attack Vector

Account Takeover

Impact

8.1

CVSSv3 Score

On This Page

Key outcome

Password-reset poisoning via Host and X-Forwarded-Host headers identified and demonstrated against XHack-controlled test accounts only. Reset links pinned to a fixed canonical URL; host headers no longer trusted. No real user was targeted.

Table of Contents

14

Executive Summary

A mid-market e-learning platform, referred to throughout this document only as Redacted, engaged XHack for a focused web application VAPT. The platform hosts courses for corporate customers, with learner, instructor, and administrator roles, and handles both personal data and course-purchase records.

XHack identified a password-reset poisoning vulnerability. When a user requested a password reset, the application built the reset link in the email using the hostname taken from the incoming request's Host header — and, behind its reverse proxy, from the X-Forwarded-Host header. Because an attacker controls those headers on their own request, an attacker could trigger a reset for any registered user and cause the email that user received to contain a link pointing at an attacker-controlled server, while still carrying a valid, working reset token.

If the targeted user opened that link, the token was delivered to the attacker, who could then complete the reset on the real site and take over the account. XHack demonstrated the full chain against its own test accounts, including a test instructor and a test administrator account, and never targeted a real user. The client shipped a fix within the engagement week.

Client Background

The client operates a learning-management platform used by roughly 120 staff internally and by a much larger population of corporate learners. Administrator accounts can manage all courses, learners, and enrolment data across the tenant, which makes them a high-value takeover target. Instructor accounts can edit course content and view enrolled-learner information.

The client cannot be identified beyond this description for commercial reasons.

Scope

  • In scope: the production web application and its authentication flows, including registration, login, and password reset
  • Out of scope: the payment provider's hosted pages, the mobile apps, and the corporate marketing site
  • Approach: grey-box. XHack received a standard learner account and, for the reset testing specifically, provisioned additional test learner, instructor, and administrator accounts on XHack-controlled email addresses so that no real user's reset flow was ever touched.

Discovery

XHack began with the authentication surface, which is consistently among the highest-impact areas of any application. The password-reset flow was straightforward on the surface: submit an email address, receive a link, set a new password.

The first signal came from reading the reset email itself. XHack requested a reset for a test account through the normal interface and inspected the resulting link:

https://app.redacted.example/reset-password?token=6f1c...e2a9

To test where that hostname came from, XHack replayed the reset request with a modified Host header:

POST /api/auth/forgot-password HTTP/1.1
Host: attacker.xhack-test.example
Content-Type: application/json

{ "email": "victim-test@xhack-test.example" }

The email that arrived for the test victim now contained:

https://attacker.xhack-test.example/reset-password?token=9b83...74d1

The token was valid. The application had generated a genuine, working reset token for the target account and wrapped it in a URL whose hostname the attacker had chosen. The reset request and the reset email go to different places: the attacker sends the request, the victim receives the email. The attacker never needs to see the victim's inbox — they only need the victim to click a link that points back at the attacker's own server.

Confirming the Proxy Variant

The application ran behind a reverse proxy, so XHack also tested X-Forwarded-Host, the header proxies use to convey the original hostname:

POST /api/auth/forgot-password HTTP/1.1
Host: app.redacted.example
X-Forwarded-Host: attacker.xhack-test.example
Content-Type: application/json

{ "email": "victim-test@xhack-test.example" }

This also succeeded. The framework trusted X-Forwarded-Host when constructing absolute URLs, so even requests that reached the origin with the correct Host could be poisoned through the forwarded header.

Demonstrating Token Capture

XHack stood up a simple listener on the attacker hostname and completed the chain end to end against its own test accounts. When the test victim opened the poisoned link, the request — token included in the URL — arrived at the XHack-controlled server and was logged. XHack then submitted that token to the real application's reset endpoint and set a new password, taking over the test account. The exercise was repeated for the test instructor and test administrator accounts to confirm the impact was identical regardless of privilege level.

No real user account was ever targeted. Every victim in the demonstration was an XHack-controlled test account created for the purpose.

Vulnerability Properties

Property Detail
Vulnerability Class Password Reset Poisoning via Host-Header Injection
OWASP Top 10 A07:2021 Identification and Authentication Failures
CWE CWE-640 Weak Password Recovery Mechanism; CWE-644 Improper Neutralization of HTTP Headers
CVSSv3 Score 8.1 (High) — Confidentiality: High, Integrity: High, Availability: None, User Interaction: Required
Attack Vector Network
Authentication Required No (attacker needs only the victim's registered email address)
Privilege Required None
User Interaction Required (victim must open the reset link)
Impact Full account takeover of any user, including instructors and administrators

Why It Happened

Frameworks make it easy to build absolute URLs from the current request, and the reset feature used exactly that convenience: it asked the request for its base URL and appended the reset path and token. In development and in normal use the request's host is the real application host, so the link looks correct and nobody notices where the hostname came from. The value is attacker-controlled, and the reverse proxy's X-Forwarded-Host handling widened the surface further.

The token itself was well made — high entropy, single use, time limited. None of that mattered, because the vulnerability did not attack the token's strength. It delivered a perfectly valid token to the wrong recipient.

Impact Assessment

  • Account takeover of any user. The only prerequisite is knowing a target's registered email address and persuading them to open a link — one that, to the recipient, came from the real service because they genuinely requested (or expected) a password reset.
  • Administrator compromise. Because the flaw is identical for every role, a targeted attack on a known administrator's email address would yield tenant-wide control over courses, learners, and enrolment data.
  • Confidentiality and integrity across the tenant. A taken-over administrator account exposes learner personal data and allows modification of course content and enrolment records.
  • Regulatory exposure. Learner records are personal data; a real takeover leading to their exposure would likely trigger GDPR notification obligations.
  • Phishing quality. Reset poisoning produces unusually convincing phishing, because the email is a real one from the real system, sent because the attacker triggered it at a moment the attacker chose.

There was no evidence of prior exploitation in the application's logs.

Remediation

Immediate (Same Day)

  • Stop deriving the reset URL from the request. The reset link is now built from a single, fixed, server-side canonical base URL held in configuration. The request's Host and X-Forwarded-Host headers are no longer consulted when constructing the link.
  • Reject unexpected host headers at the edge. The reverse proxy and application now validate Host against an allow-list of the application's own domains and ignore X-Forwarded-Host from untrusted sources.

Short-Term (Within the Week)

  • Audit every other place the application builds an absolute URL — email verification, invitation links, and share links — for the same request-derived-host pattern. The email-verification link had the identical issue and was fixed in the same release.
  • Shorten the reset token lifetime and bind it more tightly, invalidating any outstanding token as soon as a new one is requested, so a captured-but-unused token has a smaller window.
  • Add alerting on password-reset anomalies, such as a burst of reset requests for high-value accounts.

Longer-Term

  • Treat all inbound headers as untrusted input in a documented standard, with host validation applied centrally rather than per feature.
  • Add automated tests that request a reset with a spoofed Host and X-Forwarded-Host and assert the generated link always uses the canonical domain, failing the build otherwise.
  • Periodic VAPT on the authentication flows specifically, since they change often and carry the highest blast radius.

Outcome

The client pinned the reset and verification links to the canonical base URL on the day of disclosure and added host-header validation at the proxy within the same week. A re-test by XHack confirmed that reset links now always point at the application's own domain regardless of any Host or X-Forwarded-Host value supplied, and that spoofed-header reset requests no longer produce a poisoned link.

No real user was targeted at any point, and the client's logs showed no prior exploitation. The engineering team acknowledged that the reset URL had been built from the request since the feature was first written, and that no previous review had questioned where the hostname came from.

Password-reset poisoning is not a new class of bug — it has been documented for over a decade — but it remains common precisely because the insecure version is the path of least resistance in almost every web framework. Building account-security links from a fixed, server-controlled base URL, and refusing to trust the Host header for anything that matters, is a small change that closes an entire category of account-takeover risk.

Engagement details

Client

Redacted (E-Learning / LMS, ~120 employees)

Industry

EdTech / SaaS

Service

Web Application VAPT

Duration

1 week

Year

2026

Tags
account takeover
password reset poisoning
host header injection
authentication
web application security
owasp top 10
vapt
Get a Web App VAPT

Start your engagement

Get a Web App VAPT

XHack delivers the same rigorous methodology behind every case study. Let us pressure-test your defences.

Get a Web App VAPT