Login
ChallengesLearn
Scoreboard
Teams
Profile

Preferences

Truesapiens

LearnCross Site ScriptingStored XSS
Cross Site Scripting·Lesson 3 of 12

Stored XSS

The attacker plants a payload in the database. Every visitor who loads the affected page executes it — comments, reviews, profiles are the classic delivery channels.

Beginner10 min
XSSStoredPersistent
Loading lesson…
PreviousReflected XSSNextDOM-based XSS

© 2026 Truesapiens.

Terms of ServicePrivacy PolicyCookie Policy

Stored XSS — also called persistent or Type I XSS — occurs when an attacker injects malicious script into a data store that the application later serves to other users. Unlike reflected XSS, the payload persists on the server and executes in every victim's browser without requiring a crafted link. Comment sections, user profiles, and message boards are the most common targets.

What you'll be able to do
  • Explain how stored XSS differs from reflected XSS.
  • Identify persistent storage surfaces: comments, profiles, uploads.
  • Describe the Samy worm and how it achieved self-propagation.
  • Apply output encoding at the template layer as the primary defence.
Key terms
Stored XSS
A persistent XSS variant where the payload is saved on the server (database, file system, cache) and served to every user who views the affected page.
Self-propagation
When an XSS payload automatically replicates itself — for example, posting the same payload to the victim's profile or feed — creating a worm.
Output encoding
The practice of encoding data at the moment it is rendered in HTML, applying the correct scheme for the target context (HTML body, attribute, JavaScript, CSS).
Trust boundary
A conceptual line that separates untrusted user input from trusted internal data. Crossing this boundary without sanitisation introduces vulnerability.
What is it?

One injection, many victims

The danger of stored XSS lies in its amplification. A single injected payload is written to a database or file and served to every subsequent visitor. No phishing is needed — the attacker simply submits the payload through a form or API, and the application becomes a delivery vehicle for the attack. The payload executes in the security context of the application, meaning it can read cookies, make authenticated requests, and modify the DOM on behalf of every victim.

Comment sections are the canonical vector. A comment form that stores user-submitted HTML and renders it on a public page without escaping allows an attacker to inject a <script> tag that executes for every reader. Profile fields — "About me" sections, display names, and bios — are equally dangerous because they are rendered on multiple pages across the site.

Stored XSS attack flow
Mini Map
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Try it

Comment board with stored XSS

Post a comment below. In vulnerable mode, the comment is rendered unescaped — any HTML you include executes for every viewer. Switch to safe mode to see how output encoding prevents script execution while preserving the displayed text.

proddiscuss.app/threads/xss-demo
discuss
Comments (1)
Demo09:15:02

Welcome to the comment board! Try posting a message.

discuss · post comment
Vulnerable mode — comments rendered unescaped. Any HTML in a comment executes for every visitor.
Real-world relevance

The Samy worm: stored XSS that broke the internet

In October 2005, Samy Kamkar discovered a stored XSS vulnerability in MySpace's profile page. MySpace had filtered <script>, <body>, andonclick but missedonmouseover combined with CSSbackground: url(javascript:…). Samy crafted a profile that, when viewed, added the viewer as a friend and copied the payload to the viewer's own profile. Within 20 hours, over one million MySpace users were infected. The worm was so aggressive that MySpace had to shut down its servers temporarily.

The Samy worm remains the definitive case study in stored XSS because it demonstrated self-propagation without any server-side exploit. The vulnerability was a single unescaped profile field, and the effect was a platform-wide outage. It also led to the first felony conviction for a social-networking worm under the Computer Fraud and Abuse Act.

javascriptvulnerable
// VULNERABLE — stores and renders raw HTML
app.post('/comment', (req, res) => {
  db.insert('comments', { body: req.body.comment });
});

app.get('/post/:id', (req, res) => {
  const comments = db.select('comments');
  let html = comments.map(c => '<div>' + c.body + '</div>');
  res.send(html); // raw HTML in response
});

// SAFE — output-encode at render time
app.get('/post/:id', (req, res) => {
  const comments = db.select('comments');
  let html = comments.map(c =>
    '<div>' + escapeHtml(c.body) + '</div>'
  );
  res.send(html);
});
Mitigation

Encode at the output boundary, not the input

The standard defence against stored XSS is to apply context-aware output encoding when rendering data, not when storing it. Storing the raw input preserves the original data and lets the presentation layer handle escaping correctly based on the output context — HTML body, attribute, JavaScript, or CSS. This approach avoids double-encoding and data loss that input filtering would cause.

Server-side template engines like React JSX, EJS, and Handlebars auto-escape HTML context by default, but developers must avoid APIs that bypass escaping, such as dangerouslySetInnerHTML in React or | safe in Jinja2. In cases where HTML is intentionally allowed (e.g. rich text editors), use a sanitisation library like DOMPurify to strip dangerous tags and attributes.

Further reading
  • OWASP — Stored XSS(OWASP)
  • Samy Kamkar — MySpace Worm Technical Write-up(Samy Kamkar)
  • DOMPurify — Sanitisation Library(cure53)
Key takeaways

What to remember

  • Stored XSS affects every visitor who views the infected page — no phishing link is needed.
  • Comment sections, profiles, and message boards are the highest-risk surfaces.
  • Encode at output time, not input time. Store the raw value; escape when rendering.
  • The Samy worm (MySpace 2005) proved that stored XSS can self-propagate and cause platform-wide outages.
  • If you must allow some HTML, use a sanitisation library — never build your own filter.

Knowledge check

0/3 answered · 0 correct
  1. 1.Why is stored XSS considered more dangerous than reflected XSS?

  2. 2.What is the best practice for defending against stored XSS?

  3. 3.How did the Samy worm propagate on MySpace?