Quick answer
The 429 (Too Many Requests) error page of a subdomain belonging to a Q&A social network reflected part of the URL inside the Google Analytics <script> block, without sanitizing it. Because the input was already in JavaScript context, there was no need to break out of HTML: it was enough to break the string literal with '-alert(document.domain)-'. A classic reflected XSS, but the vector — the GA snippet — is interesting.
1. Context — the 429 error page
When a user makes too many requests to a subdomain of the platform, the server returns a 429 error page. This page includes a Google Analytics snippet that takes part of the URL as input to build its tracking calls.
The problem is that this URL value is inserted directly inside the Google Analytics <script> block without any kind of sanitization, turning it into a JavaScript injection point.
2. The payload and the vulnerable URL
https://subdomain.target.tld/'-alert(document.domain)-'
The part of the URL after the slash ('-alert(document.domain)-') is reflected directly inside the Google Analytics script in the HTTP response.
3. The vulnerable code in the response
<script type="text/javascript">
...
ga('set', 'dimension1', 'board-'-alert(document.domain)-'');
ga('set', 'dimension2', 'False');
ga('set', 'dimension3', 'False');
});
});
</script>
The URL value is concatenated inside a single-quoted JavaScript string literal. The payload closes the opening quote with ', injects the alert(document.domain) call through string concatenation (-alert(document.domain)-), and reopens the quote so the rest of the code stays syntactically valid.
Payload breakdown
dimension1', 'board- ← original string up to where the input lands
'-alert(document.domain)-' ← attacker input
The resulting sequence in the script is:
ga('set', 'dimension1', 'board-' - alert(document.domain) - '');
JavaScript evaluates -alert(document.domain)- as an arithmetic operation, which forces alert(document.domain) to be evaluated as part of the expression. The arithmetic result doesn't matter — what matters is that alert(document.domain) executes.
4. Condition to trigger the XSS
The 429 error page is only shown when the server detects too many requests from the same client. For the victim to load the vulnerable page, they first need to be rate-limited. In practice this is achieved in several ways:
- The attacker sends many prior requests from the victim's IP (non-trivial).
- The victim is already naturally rate-limited by their own usage.
- The link is sent directly when they are already in a 429 state.
5. Technical classification
| Field | Value |
|---|---|
| Vulnerability type | Reflected XSS — input reflected in a <script> block |
| CWE | CWE-79 — Improper Neutralization of Input |
| Vector | URL segment reflected in the Google Analytics snippet |
| Persistence | Reflected — triggers only when loading the malicious URL |
| Precondition | Active 429 (rate limit) state on the client |
6. Technical notes
- The XSS lives inside an existing
<script>block, not in an HTML attribute. This makes it especially direct: there's no need to escape an HTML context, the input is already inside a JavaScript context where any code is directly executable. - The vector is the Google Analytics snippet, a third-party block of code that the platform included in its error pages. The attack surface is in how the app built the parameters passed to
ga()using URL data without escaping it. - The 429 condition slightly limits exploitability compared to a Reflected XSS with no preconditions, but it doesn't eliminate it — the malicious URL is still crafted by the attacker and shareable.
General lesson
When a client value ends up inside a <script> block, the vector is already half-cooked: you only need to break the context of the string literal that contains it. Don't only look at HTML when you audit error pages or tracking snippets — JavaScript context is where you'll usually find the least sanitization.
Related labs
Practice reflected XSS in script blocks and bypasses with arithmetic concatenation: XSS labs.
Practice this in a lab
Xss
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Stored XSS in template names — from the most boring field to domain takeover
A template title field, unsanitized, in a session with permissions over domains. A real €1,200 bounty. How to find XSS where nobody looks.
DOM XSS — gadgets, postMessage handlers and CVE-2025-59840
DOM XSS isn't just innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers without origin checks, broken hash-based routing.
postMessage — common vulnerabilities: origin bypass, XSS sink, cross-window IDOR
How to identify and exploit vulnerabilities in window.postMessage(): listeners without origin validation, insecure JSON payloads that reach DOM XSS, cross-origin IDOR.