Skip to content

Stored XSS onclick angle brackets double quotes encoded single quotes backslash escaped

Field Value
Platform PortSwigger Web Security Academy
Difficulty Practitioner
Vulnerability Stored XSS — Injection via Website Field into onclick Handler
Injection Point Website URL field → onclick JavaScript string
Goal Execute alert(0) by breaking out of the JS string using '

Lab — Stored XSS: Injection via Website Field into onclick Handler

Solution Walkthrough

Posting a comment and inspecting the rendered author link:

<a id="author" href="https://teto.com"
   onclick="var tracker={track(){}};tracker.track('https://teto.com');">
    teto
</a>
Screenshot

The website URL appears in two places: the href attribute and inside the onclick JavaScript string. The onclick handler passes our URL as a string argument to tracker.track() — if we can break out of that string, we can inject JavaScript.

Step 1 — Identify that literal ' is filtered

The website field rejects URLs that don't match expected format and strips or escapes literal single quotes — the field has some validation. However, &apos; is the HTML entity for ' and may be decoded server-side before being inserted into the onclick handler.

Step 2 — Confirm &apos; decodes to ' in the output

https://teto.com&apos;

Resulting HTML:

onclick="var tracker={track(){}};tracker.track('https://teto.com'');"
Screenshot
Screenshot

&apos; was decoded to ' and inserted literally — breaking the JavaScript string. We can now inject code.

Step 3 — Concatenate an alert call

https://teto.com&apos;+alert(0)+&apos;

Resulting HTML:

<a id="author" href="https://teto.com'+alert(0)+'"
   onclick="var tracker={track(){}};tracker.track('https://teto.com'+alert(0)+' ');">
    Teto
</a>
Screenshot

Clicking the author name executes alert(0) and the lab is solved :P


How the Payload Works

The onclick handler builds a string from our URL:

tracker.track('https://teto.com'+alert(0)+' ');
//             ↑                 ↑          ↑
//             string opens      concat     empty string (our closing &apos; + app's ')
//             our &apos; closes it  call executes

When the link is clicked:

  1. 'https://teto.com' — first string (closed by our injected ')
  2. + — concatenation operator
  3. alert(0) — executed as a function call to compute the right operand
  4. +' ' — our closing &apos; opens a string that the application's own ' closes

The &apos; Entity Bypass — Why It Works

The application validates and sanitizes the website field looking for literal ' characters and blocks or escapes them. However, it does not decode HTML entities before checking — so &apos; passes validation as a harmless string.

When the value is stored and later rendered into the HTML page, the browser (or the server-side template) decodes &apos; back to ' before inserting it into the onclick attribute value. The sanitization ran on the encoded form; the decoded form — containing the dangerous ' — ends up in the JavaScript.

Input:      https://teto.com&apos;+alert(0)+&apos;
Validation: sees no ' — passes
Stored:     https://teto.com&apos;+alert(0)+&apos;
Rendered:   https://teto.com'+alert(0)+'        ← &apos; decoded to '
JS executes: string breaks out, alert fires