鲁斯前端布鲁斯前端

文章中英模式

布鲁斯前端面试题目 - CSRF cookie跨域攻击

深入解析CSRF攻击原理、危害与防御机制。了解如何使用SameSite Cookie、CSRF Token等方法保护Web应用免受跨站请求伪造攻击。

影片縮圖

懒得看文章?那就来看视频吧

CSRF攻击是什么?

跨站请求伪造(CSRF)是一种攻击,让你在不知情的情况下执行非本意的操作。想象有人伪造你的签名去银行提款。

🔍 CSRF攻击原理

1. 你登录银行网站(bank.com)

浏览器获得认证Cookie

2. 访问恶意网站(evil.com)

包含自动发送请求(带上Cookie)的代码

3. 恶意请求带上Cookie

银行网站误认为是你操作

简单CSRF攻击示例:

<!-- GET方式的CSRF攻击 -->
<img src="https://bank.com/transfer?to=hacker&amount=1000" style="display:none">
<!-- 当你访问含有此代码的页面时,自动发送转账请求 -->

<!-- POST方式的CSRF攻击 -->
<form id="csrf-form" action="https://bank.com/api/transfer" method="POST" style="display:none">
  <input type="hidden" name="to" value="hacker">
  <input type="hidden" name="amount" value="1000">
</form>
<script>
  // 页面加载后自动提交表单
  document.addEventListener('DOMContentLoaded', function() {
    document.getElementById('csrf-form').submit();
  });
</script>

CSRF攻击的工作原理

CSRF攻击的成功依赖于三个关键条件:

  1. 1. 用户已经在目标网站通过身份验证,并且存在包含认证信息的Cookie
  2. 2. Cookie自动随请求发送,即使请求来自不同网站
  3. 3. 攻击者诱使受害者访问恶意页面或点击恶意链接

攻击流程示例

情境设置:

  • 1. 用户已登录银行网站(bank.com),浏览器储存了认证Cookie
  • 2. 银行网站有一个转账API: POST /api/transfer
  • 3. 攻击者拥有一个恶意网站(evil.com)

步骤1: 用户已登录银行网站

// 用戶登入銀行網站後收到的Cookie
Set-Cookie: session=abc123; Domain=bank.com; Path=/; HttpOnly; Secure

步骤2: 攻击者准备恶意网站

<!-- 惡意網站(evil.com)上的HTML代碼 -->
<html>
  <body>
    <h1>贏取免費獎品!</h1>
    
    <!-- 自動提交的隱藏表單 -->
    <form id="csrf-form" action="https://bank.com/api/transfer" method="POST" style="display:none;">
      <input type="hidden" name="to" value="attacker-account" />
      <input type="hidden" name="amount" value="1000" />
    </form>
    
    <script>
      // 頁面加載後自動提交表單
      window.onload = function() {
        document.getElementById("csrf-form").submit();
      }
    </script>
  </body>
</html>

步骤3: 受害者访问恶意网站

当用户访问evil.com时,恶意脚本自动提交表单到bank.com,浏览器自动附加用户的银行Cookie。

POST /api/transfer HTTP/1.1
Host: bank.com
Cookie: session=abc123
Content-Type: application/x-www-form-urlencoded

to=attacker-account&amount=1000

步骤4: 银行网站处理请求

银行网站看到有效的session Cookie,认为请求来自合法用户,执行转账操作。

CSRF攻击的常见形式

1. GET请求攻击

利用图片标签或其他资源请求来执行攻击:

<!-- Malicious image tag triggers GET request -->
<img src="https://bank.com/api/transfer?to=attacker&amount=1000" width="0" height="0" />

2. POST请求攻击

如先前示例,使用自动提交的表单:

<form id="csrf-form" action="https://bank.com/api/transfer" method="POST">
  <input type="hidden" name="to" value="attacker-account" />
  <input type="hidden" name="amount" value="1000" />
</form>
<script>document.getElementById("csrf-form").submit();</script>

3. AJAX请求攻击

使用JavaScript发起XMLHttpRequest或fetch请求:

// 注意:這種方式通常會受到同源政策和CORS的限制
fetch('https://bank.com/api/transfer', {
  method: 'POST',
  credentials: 'include', // 關鍵:包含Cookie
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'to=attacker-account&amount=1000'
})
.then(response => console.log('CSRF攻擊成功'))
.catch(error => console.error('CSRF攻擊失敗', error));

4. 点击劫持(UI Redressing)

结合透明iframe和社会工程学,诱使用户无意中点击恶意按钮:

<style>
  .game { position: relative; width: 500px; height: 500px; }
  .overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; }
  iframe { position: absolute; top: 0; left: 0; width: 500px; height: 500px; opacity: 0.0001; }
</style>

<div class="game">
  <button class="overlay">Click to claim prize</button>
  <!-- Semi-transparent iframe positioned above the bank site's 'Confirm Transfer' button -->
  <iframe src="https://bank.com/transfer?to=attacker&amount=1000"></iframe>
</div>

CSRF攻击防御策略

❌ 常见错误观念

「CORS 限制跨站请求,就不能 CSRF」

错! 浏览器允许跨站提交表单(form submit)或发送图片请求(如 <img src="...">),这些方式不受 CORS 限制。

保护网站免受CSRF攻击的方法有多种,以下是最有效的防御策略:

1. SameSite Cookie 属性

这是目前最推荐的防御方法,通过设置Cookie的SameSite属性限制跨站请求发送Cookie:

Set-Cookie: session=abc123; SameSite=Strict; Path=/; HttpOnly; Secure

SameSite取值选项:

  • 1. Strict:仅在相同站点发出的请求才会发送Cookie(最安全,但用户体验较差)
  • 2. Lax:导航到目标网站的GET请求会发送Cookie,但其他跨站请求不会(平衡安全和用户体验,现代浏览器默认行为)
  • 3. None:允许跨站发送Cookie,但必须同时设置Secure属性(不安全,仅在必要时使用)

2. CSRF Token

使用随机生成的令牌验证请求合法性:

服务器端生成并存储CSRF Token:

// Node.js Express示例
const crypto = require('crypto');

app.get('/form', (req, res) => {
  // 生成隨機Token
  const csrfToken = crypto.randomBytes(16).toString('hex');
  
  // 存儲在用戶會話中
  req.session.csrfToken = csrfToken;
  
  // 在表單中嵌入Token
  res.send(
    '<form action="/api/transfer" method="POST">' +
    '<input type="hidden" name="_csrf" value="' + csrfToken + '" />' +
    '<!-- 其他表單字段 -->' +
    '<button type="submit">確認轉帳</button>' +
    '</form>'
  );
});

服务器端验证Token:

// 中間件驗證CSRF Token
app.post('/api/transfer', (req, res) => {
  // 從請求中獲取Token
  const csrfToken = req.body._csrf;
  
  // 驗證Token是否匹配
  if (csrfToken !== req.session.csrfToken) {
    return res.status(403).json({ error: 'CSRF token驗證失敗' });
  }
  
  // Token驗證成功,處理請求
  // ...
});

CSRF Token有效是因为恶意网站无法获取跨域的令牌值,因此无法构造包含正确令牌的请求。

3. Double Submit Cookie

不需要服务器存储,同时将令牌存在Cookie和请求参数中:

// 設置一個僅用於CSRF保護的Cookie
app.get('/form', (req, res) => {
  const csrfToken = crypto.randomBytes(16).toString('hex');
  
  // 設置Cookie (需要加上SameSite=Lax增強安全性)
  res.cookie('csrf', csrfToken, { 
    httpOnly: false, 
    sameSite: 'Lax',
    secure: true 
  });
  
  // 在表單中也包含相同的令牌
  res.send(
    '<form action="/api/transfer" method="POST">' +
    '<input type="hidden" name="_csrf" value="' + csrfToken + '" />' +
    '<!-- 其他表單字段 -->' +
    '</form>'
  );
});

// 驗證時比較Cookie中的令牌和表單提交的令牌是否一致
app.post('/api/transfer', (req, res) => {
  if (req.cookies.csrf !== req.body._csrf) {
    return res.status(403).json({ error: '驗證失敗' });
  }
  // 處理請求...
});

CSRF常见面试题

1. CSRF与XSS攻击的区别是什么?

答:两者攻击方式和目的不同:

CSRF

利用已登录状态发送伪造请求

无法读取数据,只能执行操作

防御:验证请求来源

XSS

注入并执行恶意脚本

可读取数据、执行任意JS

防御:过滤输入、编码输出

2. 为什么SameSite Cookie是防御CSRF的有效方法?

答:SameSite限制Cookie只在特定情况下发送:

Strict

仅同站点请求发送Cookie

Set-Cookie: session=123; SameSite=Strict;

Lax (现代浏览器默认)

允许顶级导航和GET请求带Cookie

Set-Cookie: session=123; SameSite=Lax;

CSRF攻击需要自动发送Cookie,SameSite直接阻止了这一机制。

3. CSRF Token的工作原理是什么?

答:通过不可预测的令牌验证请求来源:

1. 生成令牌

服务器生成随机Token

2. 嵌入页面

放入表单或JS请求中

3. 验证令牌

检查Token是否匹配

// 前端表單
<form>
  <input type="hidden" name="_csrf" value="隨機令牌">
  <!-- 其他表單字段 -->
</form>

// 後端驗證
if (req.body._csrf !== session.csrfToken) {
  return res.status(403).send("拒絕請求");
}

由于同源政策,恶意网站无法读取目标网站的Token,无法构造有效请求。

4. 设置SameSite Cookie还需要CSRF Token吗?

答:通常不需要同时使用两种防御机制,但在某些情况下可能需要双重保护:

SameSite=Lax(推荐设置)通常已足够:

  • 1. 阻止大多数CSRF攻击
  • 2. 允许从外部网站的链接正常导航
  • 3. 现代浏览器的默认行为

何时需要同时使用CSRF Token:

  • 1. 需要支持较旧的浏览器(不支持SameSite属性)
  • 2. 必须使用SameSite=None(例如第三方集成场景)
  • 3. 需要更高安全等级的应用(如金融服务)
  • 4. 防御深度策略(Defense in Depth)的实践
// 最佳實踐:結合使用
// 1. 設置SameSite Cookie
app.use(session({
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'lax' // 默認值,阻擋大多數CSRF
  }
}));

// 2. 關鍵操作仍使用CSRF Token作為額外保護
app.post('/api/payment', csrfProtection, (req, res) => {
  // 處理支付請求
});

安全性与兼容性的平衡:SameSite Cookie提供基础保护,而CSRF Token可在需要时提供额外安全层。