EXERCISE 2 — Why server-side encoding fixes reflected/stored but not DOM XSS ============================================================================ HOW SERVER-SIDE OUTPUT ENCODING WORKS: - For reflected and stored XSS, the unsafe insertion happens when the SERVER builds the HTML response: it drops untrusted input (a query param, a stored comment) into the page. - If the server ENCODES that input on output (e.g. < -> <, > -> >) for the HTML context, the browser receives the payload as inert TEXT, not markup. The script never becomes a script element. The bug is closed at the point where the server emits the HTML. - This works for BOTH reflected and stored because in both cases the SERVER is the component that writes the untrusted data into the response. Fix the server's output step and both are covered. WHY IT DOES NOTHING FOR DOM-BASED XSS: - In DOM-based XSS the unsafe insertion happens ENTIRELY IN THE BROWSER: the page's own JavaScript reads attacker input (e.g. location.hash, document.URL, location.search) and writes it into a dangerous SINK (innerHTML, document.write, eval) -- AFTER the server's response has already been delivered. - The server may never even see the malicious data. The classic source, location.hash (the part after #), is NOT transmitted to the server by the browser at all. So there is no server "output" step in which to encode it -- the dangerous write happens client-side, downstream of anything the server did. - Even if the server perfectly encodes its own output, the client JS still takes the raw URL/hash at runtime and assigns it to innerHTML, executing the payload. Server encoding is simply not in the data path. WHAT MUST CHANGE TO FIX A DOM-BASED BUG: - The CLIENT-SIDE JAVASCRIPT itself. Specifically: * Use a SAFE SINK: assign user data with element.textContent (or setAttribute for plain attributes) instead of innerHTML, so it's treated as text, never parsed as HTML. * Avoid dangerous sinks entirely (innerHTML, outerHTML, document.write, eval, setTimeout(string), new Function). * If HTML really is needed, sanitize with DOMPurify before insertion (Chapter 7). * Optionally adopt Trusted Types (Chapter 9) to enforce safe sinks. WHY IT'S INVISIBLE IN SERVER LOGS: - Since the payload often travels only in the URL FRAGMENT (after #), which the browser doesn't send to the server, the server logs show a normal request with nothing suspicious. The attack happens purely in the victim's browser. This makes DOM XSS easy to miss in server-centric testing and monitoring -- you have to inspect the client-side data flow (sources -> sinks), not the access logs. ONE-LINE TAKEAWAY: Server-side encoding guards the SERVER's output; DOM XSS lives in the CLIENT's runtime. Different component, different fix -- safe DOM sinks, not server encoding.