\n\nWhat the server sees — and this is the part people get wrong:\n\nWith ?default=…, the payload is sent to the server as a query parameter. But the server never reflects it, so the response is unchanged and a server-side output filter has nothing to inspect. The attack happens after the response arrives.\nThe attacker can go further and move the payload into the fragment: http://www.some.site/page.html#default=…. Browsers never transmit the part after #, so now the payload genuinely never reaches the server, never appears in access logs, and is invisible to a WAF.\n\nWhy that matters for defence: server-side controls — request filtering, output encoding in the template, logging — are the wrong layer here. The fix has to live in the client code: read from a safe source, and write with a text-only sink (textContent) instead of an HTML-parsing one (innerHTML, document.write), with a Content Security Policy as the backstop.\nGo deeper:\n\n PortSwigger — DOM-based XSS — sources and sinks; why the server never sees the payload.\n\n", "dateModified": "2026-07-31T10:46:10+00:00" } } }

LOGBOOK

HELP

Quiz Entry - updated: 2026.07.31

How does DOM-based XSS differ from server-side XSS?

The server never puts the payload into its response — client-side JavaScript reads it out of the URL and writes it into the page, so server-side filters have nothing to catch. With a # fragment the payload doesn't even leave the browser.

In server-side XSS the vulnerable line is on the server, which echoes untrusted data into the HTML it sends. In DOM-based XSS the vulnerable line is in the page's own JavaScript: a source reads attacker-controlled data, a sink writes it into the document.

A page that pre-selects a language from the query string:

document.write("<OPTION value=1>"+document.location.href.substring(
  document.location.href.indexOf("default=")+8)+"</OPTION>");

Here the source is document.location.href and the sink is document.write — which parses whatever it is handed as HTML. Nothing validates the slice in between.

Attack URL:

http://www.some.site/page.html?default=<script>alert(document.cookie)</script>

What the server sees — and this is the part people get wrong:

  • With ?default=…, the payload is sent to the server as a query parameter. But the server never reflects it, so the response is unchanged and a server-side output filter has nothing to inspect. The attack happens after the response arrives.
  • The attacker can go further and move the payload into the fragment: http://www.some.site/page.html#default=…. Browsers never transmit the part after #, so now the payload genuinely never reaches the server, never appears in access logs, and is invisible to a WAF.

Why that matters for defence: server-side controls — request filtering, output encoding in the template, logging — are the wrong layer here. The fix has to live in the client code: read from a safe source, and write with a text-only sink (textContent) instead of an HTML-parsing one (innerHTML, document.write), with a Content Security Policy as the backstop.

Go deeper:

From Quiz: SPRG / Input Validation & Output Encoding | Updated: Jul 31, 2026