Juicy Shop

← All lessons

Cross-Site Scripting (XSS)

Cross-Site ScriptingA03:2021 – Injection (XSS)intermediate12 min

XSS lets an attacker run JavaScript in another user's browser. Learn the three XSS types, why output encoding is the core defence, and how modern frameworks help — and where they still let you shoot yourself in the foot.

What XSS lets an attacker do

If an attacker can execute script in a victim's session, they can read cookies and tokens, perform actions as the victim, log keystrokes, or rewrite the page to phish credentials. XSS is injection into the HTML/JS context: untrusted input is placed into a page where the browser interprets it as code rather than text.

Three flavours of XSS

Stored XSS: the payload is saved (e.g. in a review) and served to every viewer. Reflected XSS: the payload rides in the request (e.g. a URL parameter) and is echoed back in the response — delivered via a crafted link. DOM-based XSS: client-side JavaScript writes untrusted data into the DOM (e.g. innerHTML) without encoding, so the payload never even has to reach the server.

The core defence: contextual output encoding

The fix is to encode data for the context where it is rendered. In HTML body context, characters like < > & " become &lt; &gt; &amp; &quot; so the browser shows them as text. React, Vue, and Angular auto-encode by default when you render a value as a text node — which is why {userInput} in JSX is safe. The danger is opting out: dangerouslySetInnerHTML / v-html / innerHTML hand raw markup to the browser.

When you must render HTML

Sometimes you genuinely need to render user-authored HTML (a rich-text comment). Then sanitise with a vetted library such as DOMPurify, which strips scripts and dangerous attributes, rather than trusting a hand-rolled regex. Add a Content-Security-Policy that forbids inline scripts as defence in depth, so even a missed encoding cannot easily execute.

Insecure vs secure

✗ insecure
const params = useSearchParams();
const q = params.get("q") ?? "";
return <p dangerouslySetInnerHTML={{ __html: q }} />;

The raw query parameter is injected as HTML. A link with ?q=<img src=x onerror=alert(document.cookie)> runs script in the victim's session.

✓ secure
const params = useSearchParams();
const q = params.get("q") ?? "";
return <p>{q}</p>;

Rendered as a JSX text node, React HTML-encodes q automatically, so any markup is shown as inert text rather than executed.

Key takeaways

  • XSS is code injection into the browser — it executes in the victim's session.
  • Know the three types: stored, reflected, and DOM-based.
  • Contextual output encoding is the primary defence; frameworks do it by default.
  • Opting out (dangerouslySetInnerHTML/innerHTML) is where XSS sneaks back in.

Knowledge check

Practice

Apply this lesson hands-on in the matching find-it / fix-it challenge.

Open challenge →

Further reading