Offensive Security

XSS Explained: Stored, Reflected and DOM-Based

Cross-site scripting remains one of the most common web vulnerabilities because it appears anywhere untrusted input reaches a page without correct encoding. The consequence is frequently understated: successful XSS runs attacker code with the full privileges of your application, in your user's session, inside your origin.

XSS Explained: Stored, Reflected and DOM-Based

The three types

  • Stored (persistent) — the payload is saved server-side, in a comment, profile field or support ticket, and executes for every user who views it. The most damaging variant, because it needs no interaction from the victim and can affect administrators reviewing content.
  • Reflected — the payload travels in the request, typically a query parameter, and is echoed straight back in the response. Requires the victim to follow a crafted link, which makes it a natural pairing with phishing.
  • DOM-based — the vulnerability lives entirely in client-side JavaScript, which reads attacker-controllable input and writes it into a dangerous sink. The server may never see the payload at all, so server-side logs and filters provide no protection.

What an attacker actually gains

XSS is often dismissed as "just a popup". In reality the attacker executes arbitrary JavaScript with your application's privileges in the victim's browser.

  • Perform any action the user can perform, using their live session — no stolen password required.
  • Read any data rendered on the page, including data the user has legitimate access to.
  • Capture keystrokes and form input, including credentials typed into your own login form.
  • Rewrite the page to present convincing fake prompts within a genuine, correctly certificated origin.
  • Steal session tokens where they are reachable from JavaScript, which is why HttpOnly cookies matter.
  • Chain into account takeover by changing the email address or adding a new authentication factor.
DOM-based XSS never touches the server
DOM-based XSS never touches the server

Why filtering input is the wrong model

Attempting to strip dangerous characters on input is a losing strategy: the same data may be rendered later into HTML, an attribute, JavaScript, CSS or a URL, and each of those contexts requires different escaping.

The correct model is to store input faithfully and encode on output, according to the context it is being written into. Encoding for HTML text is not sufficient when the value lands inside a script block or an event handler attribute.

Context-aware encoding plus CSP is the durable defence
Context-aware encoding plus CSP is the durable defence

Defending properly

  1. Use a template engine that performs context-aware auto-escaping, and treat any use of its "raw" escape hatch as a code-review trigger.
  2. Avoid dangerous DOM sinks. Prefer assigning text content over writing markup, and never pass untrusted input to functions that evaluate strings as code.
  3. Deploy a strict Content Security Policy, ideally nonce-based, so that even a successful injection cannot execute. Treat CSP as a strong second layer, not a substitute for encoding.
  4. Set cookies HttpOnly, Secure and SameSite so session tokens are not readable from script.
  5. Sanitise rich text with a well-maintained, allow-list-based library rather than a homemade filter.
  6. Consider Trusted Types in supporting browsers to make unsafe DOM sinks fail closed.

Related from TechBiz Security

Sources & further reading

Frequently asked questions

What is the difference between stored and reflected XSS?
Stored XSS is saved on the server and executes for every user who views the affected content. Reflected XSS travels in the request and is echoed back in the response, so it requires the victim to follow a crafted link.
What makes DOM-based XSS harder to detect?
The flaw exists entirely in client-side JavaScript. The payload may never reach the server — for example when it sits in the URL fragment — so server logs, server-side filters and traffic inspection cannot see it.
Does a Content Security Policy stop XSS?
A strict, well-configured CSP can prevent injected script from executing and is a valuable second layer. It does not remove the underlying flaw, and weak policies are often bypassable, so correct output encoding remains the primary defence.
Are HttpOnly cookies enough to make XSS harmless?
No. HttpOnly prevents JavaScript from reading the session cookie, which blocks simple token theft, but the attacker can still perform actions as the user directly from the victim's browser using their authenticated session.

Related reading

0 comments

Leave a comment

Comments are moderated before appearing.