June 9, 2026
Demystifying XSS: The Flaw That Lets Hackers Hijack Your Users
Ali Elkhouly
3 min read
Imagine building a secure fortress of a website. You've got firewalls, robust databases, and state-of-the-art encryption. But there's a tiny, unescaped text box in your comment section.
Through that single crack, an attacker slips in a line of JavaScript. Suddenly, your fortress isn't just breached — it's actively working against your users, stealing their session cookies, and defacing your brand.
Welcome to the world of Cross-Site Scripting (XSS).
Despite being one of the oldest vulnerabilities on the web, XSS consistently ranks high on the OWASP Top 10.
Why? Because as the modern web becomes more interactive and JavaScript-heavy, the attack surface only grows.
Let's break down exactly what XSS is, how it works, and how you can bulletproof your applications against it.
What is Cross-Site Scripting (XSS)?
At its core, XSS is an injection vulnerability. It occurs when a web application includes untrusted data in a web page without proper validation or escaping.
The web browser cannot tell that the malicious script is untrusted. It thinks the script came from a legitimate source (your server) and executes it in the context of the victim's session.
The Stakes: What Can an Attacker Do?
If an attacker can execute arbitrary JavaScript in a victim's browser, they can:
Steal Session Tokens: Access document.cookie to hijack user sessions.
Log Keystrokes: Capture sensitive data like passwords or credit card numbers as the user types.
Perform Actions on Behalf of the User: Force the user to make unauthorized purchases, change their password, or post content.
Deface the Site: Alter the DOM to display fraudulent forms or malicious links.
The Three Flavors of XSS
XSS isn't a one-trick pony. It generally manifests in three distinct ways: Stored, Reflected, and DOM-based.
- Stored XSS (Persistent)
- This is the most dangerous form of XSS. The malicious script is permanently stored on the target server (e.g., in a database, comment section, or visitor log). When a victim requests the stored information, the script is served to their browser and executed.
Example Scenario: > A hacker posts a comment on a blog:
Every single user who visits that blog post will now silently send their session cookies to the attacker.
- Reflected XSS (Non-Persistent)
In a reflected attack, the malicious script is part of the request sent to the server and is immediately "reflected" back in the response. It isn't stored anywhere on the server; it relies on social engineering to trick a user into clicking a malicious link.
Example Scenario:
A search page displays what the user searched for: Results for: [User Input].
An attacker crafts a link: http://example.com/search?q=
If the victim clicks this link, the script executes immediately in their browser.
- DOM-based XSS
Unlike Stored and Reflected XSS, where the vulnerability lies in server-side code, DOM-based XSS happens entirely within the client-side JavaScript. The server sends clean code, but the client-side script processes data from an unsafe source (like the URL hash) and writes it into the Document Object Model (DOM) insecurely.
Example Scenario:
A website has client-side code that reads a username from the URL:
const user = new URLSearchParams(window.location.search).get('name');
document.getElementById('welcome').innerHTML = user;
If the URL is site.com?name=, the browser executes the payload when updating the DOM.
Defending Your Code: How to Prevent XSS
Securing your app against XSS requires a defense-in-depth strategy. Relying on just one layer is a recipe for disaster.
- Context-Aware Output Encoding (The Gold Standard)
- Never trust user input. Before rendering any data back to the user, ensure it is encoded based on where it's being placed (HTML body, JavaScript variable, attribute, or URL).
- HTML Body: Convert < to <, > to >, and & to &.
- Modern Frameworks: If you are using React, Angular, or Vue, you are in luck. Frameworks like React automatically encode data rendered via {userInput}. However, beware of escape hatches like dangerouslySetInnerHTML.
// BAD (React Escape Hatch)
// GOOD (React safely escapes this by default)
- Sanitize HTML Input
Sometimes, you want users to input rich text (e.g., a blog editor allowing or tags). In this case, encoding everything breaks the feature. You must use an HTML Sanitization library like DOMPurify to strip out dangerous tags (