Testing, Pitfalls & Checklist

Chapter 10
Testing, Pitfalls & Checklist
Finding XSS, the broken-filter catalogue, and a deployable hardening checklist

The finale turns the course into practice: how to test for XSS, the broken-defence patterns to recognize in review, and a copy-pasteable hardening checklist that pulls all ten chapters together.

How to Test for XSS

  1. Map every input → output flow — URL params, form fields, headers, stored data, and where each is reflected. Don't forget indirect reflections (a name set on one page, shown on another).
  2. Inject a unique benign marker — e.g. xss7421 — and find where it lands in the response/DOM. This tells you the context (Chapter 3) before you craft a payload.
  3. Try context-appropriate payloads — a tag in HTML body, a quote-breakout in an attribute, a scheme in a URL — or one polyglot probe (Chapter 4).
  4. Check DOM XSS separately — trace client-side sources (location, hash) to sinks (innerHTML, eval); these won't show in server testing (Chapter 2).
  5. Confirm execution safely — use console.log or a request to a collector you control, not noisy alert() (Chapter 4).
Tools — but understand what they can and can't find
Burp Suite and OWASP ZAP automate reflected/stored probing; Dalfox is a fast XSS-specific scanner; browser DevTools trace DOM flows. Automated scanners are excellent at the mechanical reflected cases but routinely miss DOM XSS (requires data-flow analysis), stored XSS with delayed/second-order reflection, and context-specific breakouts. Manual review of the input→output map — especially the framework escape hatches from Chapter 9 — catches what scanners don't. Use both.

The Broken-Defence Catalogue

Broken defenceWhy it failsChapter
Blocklist filtering ("strip <script>")infinite bypasses — case, entities, no-script vectors4
HTML-encoding everywherewrong for JS/URL/unquoted-attribute contexts3,6
Encoding at input, storing encodeddouble-encoding; can't know output context6
Hand-rolled HTML sanitizerbypassed via parsing quirks / mXSS7
"We validate input"free-text legitimately has < > " '7
CSP with 'unsafe-inline're-allows injected inline scripts8
Unsanitized dangerouslySetInnerHTML/v-htmlbypasses framework auto-escaping9
HttpOnly treated as an XSS fixblocks cookie read only; script still acts1,5

The Hardening Checklist

Context-aware output encoding on every untrusted value — via framework auto-escaping or a library; encode on output, for the context
Store raw, encode late — never store pre-encoded data
Sanitize with DOMPurify (narrow allowlist, kept updated) wherever user HTML must render; prefer Markdown over raw HTML
Audit every escape hatchdangerouslySetInnerHTML, v-html, bypassSecurityTrust…, raw innerHTML/eval
Validate URL schemes — allowlist http/https/mailto; reject javascript:/data:
Strict CSP — nonce/hash-based, no 'unsafe-inline'; roll out via report-only; consider Trusted Types
HttpOnly + Secure + SameSite on session cookies — mitigation, not a fix
Allowlist input validation on strict-format fields — data quality & defence in depth
Test it — input→output map, context probes, DOM data-flow, scanners + manual review
The one principle that survives every chapter
If you remember a single thing from this course: keep untrusted input as data; never let it become code. Output encoding makes it display as data; sanitization strips the code-ish parts of HTML; CSP and Trusted Types stop the browser executing it if it slips through; framework auto-escaping applies the discipline by default. Every defence is one expression of that single rule, and every XSS bug is one violation of it. Layer the defences (defence in depth), but never weaken the foundation — encoding — to make a feature work; that's the recurring mistake that reopens XSS.

How the Course Fits Together

The arc: understand the bug (Ch. 1 — data vs code; same-origin power), the variants (Ch. 2 types, Ch. 3 contexts), the attack (Ch. 4 payloads, Ch. 5 impact), then the defences bottom-up — output encoding as the foundation (Ch. 6), sanitization for real HTML (Ch. 7), CSP as the backstop (Ch. 8), framework auto-escaping & Trusted Types (Ch. 9), and operational testing/hardening (Ch. 10). It also closes the loop with the CSRF course: XSS is the bug that defeats CSRF defences, which is why it's foundational and worth fixing first.

Hands-On Exercises

Exercise 1

Write a step-by-step XSS test plan for a single input field, from mapping the reflection to confirming execution. Include how you'd determine the injection context first, and how you'd separately check for DOM-based XSS that a server-side scanner would miss.

📄 View solution
Exercise 2

Audit this app against the broken-defence catalogue: it strips <script> from input, HTML-encodes all output, sets a CSP with 'unsafe-inline', and renders comments with v-html. List every flaw and the correct fix for each.

📄 View solution
Exercise 3

Produce a prioritized XSS hardening plan for a React app with a cookie session and a rich-text comment feature. Order the defences, justify the ordering (what's foundational vs backstop), and explain why fixing XSS also protects the app's CSRF defences.

📄 View solution

Chapter 10 Quick Reference

  • Test: map input→output flows · inject a unique marker to find the context · context-appropriate payloads · check DOM sources→sinks separately · confirm with a benign marker
  • Scanners (Burp/ZAP/Dalfox) catch reflected/stored well but miss DOM XSS & second-order — pair with manual review of the escape hatches
  • Broken defences: blocklists · HTML-encode-everywhere · encode-at-input · hand-rolled sanitizer · "we validate" · 'unsafe-inline' CSP · unsanitized v-html/dangerouslySetInnerHTML · HttpOnly-as-fix
  • Checklist: context-aware encoding · store raw/encode late · DOMPurify for HTML · audit escape hatches · URL-scheme allowlist · strict nonce CSP + Trusted Types · HttpOnly/Secure/SameSite · validate strict fields · test
  • The one principle: keep untrusted input as data, never code — every defence is one expression of it
  • Never weaken encoding to make a feature work — that's the recurring reopening mistake
  • XSS closes the loop with CSRF: it defeats CSRF defences, so it's foundational — fix it first

★ XSS — Cross-Site Scripting Complete — 10 / 10 chapters

From "untrusted input becomes code" and the same-origin power of an injected script, through the three types, injection contexts, payload anatomy and real-world impact, then the full defence stack — output encoding, sanitization, CSP, framework auto-escaping, and Trusted Types — to testing and a deployable checklist. Paired with the CSRF course, you now have both halves of the client-side-injection picture, and the throughline that ties them: keep untrusted input as data, and fix XSS first.