XSS & HTML Injection
xss & html injection xss and html injection exploit insufficient encoding in html parsing contexts — xss and html
Introduction
XSS and HTML injection occur when attacker-controlled strings enter HTML parsing contexts — attribute, text, script, URL — and execute as JavaScript in victim browsers. OWASP classifies reflected, stored, and DOM-based XSS; staff engineers map every sink (innerHTML, document.write, inline handlers) in HTML templates and SPA code.
Business problem
Stored XSS in profile, comment, or CMS field compromises every viewer's session — wormable on social platforms. Reflected XSS in search URL params targets phishing campaigns. DOM XSS in client routers bypasses server encoding entirely.
- Impact: Session hijack, keylogging, defacement, crypto mining, BEC pivot.
- Detection: Bug bounty and DAST often find XSS missed by unit tests.
Why this feature exists
HTML merges data and code — browsers don't distinguish attacker text from author markup without encoding. JavaScript execution contexts (script tags, onerror, javascript: href) exist for legitimate apps — attackers abuse insufficient encoding.
- Reflected: Server echoes input in HTML response.
- Stored: Input persisted and rendered to others.
- DOM: Client JS writes untrusted location.hash to innerHTML.
Browser internals
Sinks and sources: OWASP DOM XSS wiki lists sources (location, postMessage) and sinks (innerHTML, eval). HTML parser creates script elements from script tags in injected strings unless sanitized.
- mutation XSS: Legacy browsers re-parse innerHTML assignments — sanitizer must handle mXSS vectors.
- SVG/MathML: Alternative namespaces in innerHTML enable script in older patterns.
Rendering workflow
Safe rendering: Prefer textContent over innerHTML. If HTML needed, sanitize with maintained allowlist (DOMPurify). Template engines auto-escape by default — never disable unless trusted static content.
- URL context: Encode for href/src; block javascript: and data: except allowlisted MIME.
- Event handlers: Ban onclick/onerror in HTML — use addEventListener on trusted code only.
Feature deep dive
OWASP XSS prevention contexts mapped to HTML:
- HTML body: Encode < > & " '
- Attribute: Encode all non-alphanumeric — especially unquoted attrs.
- JavaScript: JSON.stringify in script blocks — never string concat.
- URL: encodeURIComponent; validate scheme allowlist http/https/mailto.
<!-- ❌ Reflected XSS --><p>You searched for: {{ searchQuery }}</p><!-- if searchQuery = <script>alert(1)</script> — game over without encode --><!-- ✅ Encoded --><p>You searched for: {{ encodeHtml(searchQuery) }}</p><!-- ❌ DOM XSS --><script>document.getElementById("out").innerHTML = location.hash.slice(1);</script><!-- ✅ Safe --><script>document.getElementById("out").textContent = decodeURIComponent(location.hash.slice(1));</script>
Accessibility analysis
XSS via aria-label or title attributes — encoded as attribute context. Screen reader users equally affected by malicious script that manipulates DOM or announces phishing via live regions.
SEO impact
Injected links in compromised HTML create spammy outbound links — Google manual action for pure spam. SEO monitoring should alert on unknown external link patterns in crawl.
Security considerations
OWASP Top 10 A03 Injection — XSS primary web frontend manifestation. Test with polyglot payloads, event handlers, SVG, template literals in framework bypasses.
- CSP report-only: Detect injection attempts via violation reports.
Performance impact
DOMPurify on every render adds CPU — batch sanitize at save time in CMS not per view. Large innerHTML replacements cause layout thrash — separate perf and security concerns.
Real production example
React safe patterns — default JSX escape; danger zone explicit:
- Lint: eslint-plugin-no-unsanitized for innerHTML assignments.
// ✅ JSX auto-escapes<p>{userBio}</p>// ⚠️ Only with sanitizeimport DOMPurify from "dompurify";<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(cmsHtml, { USE_PROFILES: { html: true } })}} />
Enterprise usage
Bug bounty programs pay critical for stored XSS — internal red team scans quarterly with OWASP ZAP on all form endpoints rendering HTML.
Common production failures
Markdown renderer allowed raw HTML passthrough — stored XSS in docs wiki. Angular bypassSecurityTrustHtml misuse on user content. JSON-LD injection breaking out of script string with sequence.
Architecture review questions
- Every user/CMS field mapped to encoding context?
- innerHTML usage inventory — all sanitized or eliminated?
- Inline event handlers banned in HTML templates?
- javascript: URLs blocked in link href validation?
- DOM XSS sources (location, postMessage) audited in SPA?
Hands-on project
Project: Fix reflected XSS demo — add encodeHtml, CSP, and ZAP scan proof of fix on search echo page.
Interview questions
Difference between reflected, stored, DOM XSS?(Advanced)
Reflected: server reflects input once in HTML response — often needs victim to click link. Stored: persisted server-side, affects all viewers — highest severity. DOM: entirely client-side sink/source — server encoding doesn't help; fix JS.
Follow-up: Example DOM XSS in React router?
Why is innerHTML dangerous but textContent safe?(Intermediate)
innerHTML invokes HTML parser — attacker string creates active elements and event handlers. textContent treats input as plain text node — no parser execution of markup semantics.
Follow-up: When is DOMPurify required?
Escape </script> in JSON-LD script block?(Advanced)
Breakout via </script> in string closes script block early — escape as \u003c/script or split string concatenation in JS. Better: serialize JSON safely server-side; validate no raw user input in JSON-LD strings.
Follow-up: Template literal XSS in modern frameworks?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
XSS and HTML injection exploit insufficient encoding in HTML parsing contexts — OWASP staff mitigations include context-aware escaping, CSP, sanitizer allowlists, and elimination of inline handlers and dangerous sinks.