Cross-Site Scripting (XSS): The Complete Practical Guide

Stored XSS

How stored cross-site scripting works: payloads saved by the app and served to every viewer, finding storage and render sinks, and targeting admins.

Published

Stored XSS, also called persistent XSS, is the high-impact cousin of reflected. Instead of bouncing your payload back in one response, the application saves it and serves it to everyone who later views that content. You plant it once and it fires on its own, for every viewer, with no link to click.

The persisted-then-served flow

The pattern is two steps separated in time and often in place:

  1. You submit input that the app stores: a comment, a product review, a display name, a support ticket, a filename.
  2. The app later renders that stored value into a page for some viewer, without encoding it.

The gap between those steps is what makes stored XSS so reliable. The payload waits in the database until someone opens the page, then runs in their session.

You post a comment:  <img src=x onerror="/* your script */">
Later, every visitor to the thread runs it.

Why it is higher impact than reflected

Reflected XSS needs you to deliver a link and needs the victim to click it. Stored XSS needs neither. It fires for anyone who loads the page, which means:

  • You reach many victims from a single injection.
  • You can reach victims you could never phish, including staff.
  • It survives, firing again on every view until someone cleans the record.

Finding the storage sink and the render sink

The most common mistake is to look for your payload to fire on the page where you entered it. It often will not. The field you post into and the page that displays it are frequently different, sometimes in a completely different part of the app.

  • Submit a unique marker and then go looking for it everywhere: the public thread, your profile as others see it, the moderation queue, an email, an admin dashboard.
  • The render context is what dictates your payload. The same stored string might appear inside an HTML body on one page and inside an attribute on another. Identify the context on the page that actually matters.

Targeting a privileged viewer

The best stored XSS targets content that a high-value user is guaranteed to open. A support ticket gets read by an agent. A flagged comment gets opened by a moderator. A new seller profile gets reviewed by an admin. Plant your payload where their workflow forces them to render it, and your script runs in their session instead of yours. Turning that execution into a stolen session or an account change is covered in exploiting XSS.

Self-propagating payloads (XSS worms)

On a social app where users view each other's content, a stored payload can write itself into the viewer's own profile as it runs, so each victim becomes a new source. That is an XSS worm, and it is how a single injection has historically spread across an entire platform in hours. You will not need to build one to pass a lab, but it is worth understanding why stored XSS on user-to-user content is treated as critical.

The contact-form-to-staff-portal pattern

A recurring real-world shape: a public form (contact us, apply here, report a problem) has no obvious output, so it looks safe. But the submissions are read in an internal tool that renders them without encoding. You inject through the public form and your payload fires in the back office. When a field has no visible reflection, assume it might be rendered somewhere you cannot see, which overlaps with blind XSS.

Stored XSS is where XSS stops being a popup and starts being a breach. Plant one on a free live target and watch it fire against another session.

← Back to Cross-Site Scripting (XSS): The Complete Practical Guide