1. 项目概述为什么CSRF依然是Web安全的“隐形杀手”在Web安全领域我们常常把目光聚焦在SQL注入、XSS这类“显性”攻击上它们动静大危害直观。但从业十多年我处理过的安全事件里有一种漏洞因其“悄无声息”的特性常常被开发者和初级安全人员低估那就是跨站请求伪造也就是大家常说的CSRF。你可能觉得现在框架都内置防护了CSRF是不是过时了恰恰相反在复杂的现代Web应用架构、前后端分离、API滥用以及第三方组件集成等场景下CSRF以一种新的形态持续构成威胁。我见过太多因为一个“不起眼”的接口未做防护导致用户资金被转移、密码被修改的案例。今天我就从一个老兵的视角带你彻底拆解CSRF不光是原理更重要的是在不同技术栈下的实战攻防、绕过技巧和那些教科书里不会写的修复“深坑”。简单来说CSRF攻击的核心是“借刀杀人”。攻击者诱导受害者在已登录目标网站的状态下访问一个恶意构造的页面这个页面会“代替”受害者向目标网站发起一个非预期的请求。因为浏览器会自动携带用户的认证信息如Cookie、Session ID服务器无法区分这个请求是来自用户的真实意愿还是被伪造的。这就像你登录了网银没退出然后不小心点了一个“抽奖”链接这个链接背后其实是一个向他人转账的请求你的浏览器就默默地执行了。2. CSRF攻击原理深度拆解从“是什么”到“为什么能成”2.1 核心攻击模型与流程拆解要理解CSRF必须从它的攻击模型入手。一个典型的CSRF攻击包含三个不可或缺的实体和四个关键步骤。三个实体受害者Victim拥有目标网站合法账户且浏览器已保持登录状态的用户。目标网站Target Site存在CSRF漏洞的Web应用通常其业务操作如转账、改密、发帖仅依赖会话Cookie进行身份验证。攻击者Attacker构造恶意请求并诱导受害者触发的人。四个步骤登录与保持状态受害者正常登录目标网站例如bank.com服务器返回一个会话Cookie如SessionIDabc123并保存在受害者浏览器中。此后浏览器向bank.com发起的任何请求都会自动携带这个Cookie。构造恶意请求攻击者分析目标网站某个敏感操作的请求。例如银行转账可能是一个GET或POST请求GET /transfer?toattackeramount1000 HTTP/1.1。攻击者将这个请求完整地复制下来。诱导触发攻击者通过邮件、论坛、社交网站等渠道向受害者发送一个包含该恶意请求的链接或页面。这个页面可能伪装成抽奖、有趣图片、热门新闻等。请求伪造与执行受害者在已登录bank.com的状态下访问了恶意页面。页面中的代码如一个自动加载的img标签其src指向转账URL或一个自动提交的隐藏表单向bank.com发起请求。浏览器自动附带上受害者本地存储的bank.com的Cookie。服务器收到带有合法SessionID的请求便认为是受害者本人的操作从而执行转账。注意这里的关键在于攻击者无法直接获取受害者的Cookie。他只是在利用浏览器自动发送Cookie的这一默认机制。整个攻击过程中受害者除了可能看到页面跳转或加载几乎感知不到任何异常。2.2 技术原理浏览器的“诚实”与同源策略的局限CSRF能够成功根植于Web最基础的运作机制Cookie的自动发送机制这是CSRF的基石。根据HTTP标准浏览器在向某个域发起请求时会自动检查本地Cookie并将属于该域包括子域取决于Cookie的Domain和Path属性的Cookie附加到HTTP请求头的Cookie字段中。这个过程对用户和页面JavaScript都是透明的。同源策略SOP的“不管辖区”同源策略限制了来自一个源的文档或脚本如何与另一个源的资源交互。但是它通常不限制从不同源发送“跨域请求”本身。浏览器允许你从一个页面如evil.com向bank.com发送请求。SOP限制的是读取跨域请求的响应。对于简单的请求如使用GET/POST的表单提交、imgsrc加载请求可以发出响应也能返回只是响应的内容无法被evil.com的JavaScript读取。对于攻击者来说他根本不需要读取响应只要请求被服务器执行就达到了目的。无状态HTTP协议的副作用HTTP本身是无状态的Web应用依赖Cookie/Session来维持用户状态。服务器仅通过请求中携带的凭证来判断用户身份而不关心这个请求是从哪、以何种方式发起的。2.3 攻击载荷Payload的多种形态攻击者会根据目标请求的类型选择不同的载荷构造方式1. GET型CSRF最简单直接。敏感操作错误地使用了GET方法参数全部暴露在URL中。!-- 恶意页面中的代码 -- img srchttp://bank.com/transfer?toattacker_accountamount10000 width0 height0 /受害者加载此页面时浏览器会自动尝试加载图片从而发起一个GET请求。由于图片加载失败是常事用户几乎无感。2. POST型CSRF更常见。操作使用POST方法需要构造一个表单。body onloaddocument.forms[0].submit() form actionhttp://bank.com/change_email methodPOST input typehidden nameemail valueattackerevil.com / !-- 可能还有其他必需的隐藏参数如token、user_id等都需要攻击者事先探测并填入 -- /form /body页面加载后JavaScript会自动提交表单。攻击者需要精确知道所有必需的参数名和值。3. 其他方法的CSRFPUT, DELETE等在RESTful API中常见。可以通过XMLHttpRequest Level 2需CORS配合或构造一个发送后重定向的复杂表单某些浏览器支持来实现门槛稍高但原理相通。3. 实战演练手把手搭建靶场与漏洞复现光说不练假把式。要真正理解CSRF最好的方式就是亲手搭建环境、构造攻击。这里我以最经典的Pikachu漏洞靶场和DVWADamn Vulnerable Web Application为例带你走一遍完整的流程。我强烈建议你在本地或隔离的虚拟机中操作。3.1 环境准备与靶场部署首先你需要一个基础的Web运行环境。我推荐使用XAMPP或PHPStudy它们集成了Apache、MySQL、PHP一键安装省去配置烦恼。下载与安装前往官网下载XAMPP安装过程全部默认即可。安装完成后启动Apache和MySQL服务。部署靶场Pikachu从GitHub下载源码解压到XAMPP的htdocs目录下例如C:\xampp\htdocs\pikachu。DVWA同样下载源码解压到htdocs目录下例如C:\xampp\htdocs\dvwa。初始化配置访问http://localhost/pikachu根据页面提示初始化数据库。访问http://localhost/dvwa点击页面底部的Setup / Reset DB链接创建数据库。首次登录默认账号为admin密码为password。在DVWA安全设置页面DVWA Security将安全级别调至Low以便我们进行漏洞复现。3.2 Pikachu靶场CSRFGET漏洞复现Pikachu的CSRF模块非常直观。登录与查看功能以用户lucy/123456登录Pikachu。进入“CSRF”模块下的“CSRF(get)”。你会看到一个模拟的“修改个人信息”页面可以修改昵称和邮箱。注意观察浏览器地址栏当你提交时URL变成了类似http://localhost/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex...phonenum...add...email...submitsubmit的形式。所有参数都通过GET方法传递这就是漏洞所在。构造恶意页面在你的htdocs下新建一个文件attack_get.html写入以下内容!DOCTYPE html html headtitle抽奖活动/title/head body h1恭喜您中奖了/h1 p请查看您的奖品/p !-- 利用img标签的src属性发起GET请求 -- img srchttp://localhost/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sexattackphonenum1234567890addHackerHomeemailhackerevil.comsubmitsubmit width0 height0 / p页面加载中.../p /body /html这个页面伪装成一个中奖页面其中的img标签会向Pikachu的修改接口发起请求将lucy的邮箱修改为hackerevil.com。实施攻击确保你已经以lucy身份登录了Pikachu并且会话未过期。在同一个浏览器中新开一个标签页访问http://localhost/attack_get.html。你会发现页面快速闪了一下图片加载失败然后回到Pikachu的个人信息页面刷新一下你会发现邮箱等信息已经被修改了攻击成功。实操心得GET型CSRF的利用简单到令人发指。在实际渗透测试中如果发现一个敏感操作如删除文章、审核通过使用了GET方法几乎可以断定存在CSRF漏洞。修复的第一步就是强制将此类操作改为POST方法。3.3 DVWA靶场CSRFPOST漏洞复现DVWA的CSRF模块在低安全级别下是一个无任何防护的密码修改功能。登录与定位功能以admin登录DVWA将安全级别设为Low。侧边栏进入CSRF。分析请求在密码修改框输入新密码并提交用浏览器开发者工具F12的“网络”Network标签捕获这个请求。你会发现这是一个POST请求表单数据包含password_new,password_conf,Change三个参数。服务器仅验证两个密码是否一致不验证原密码。构造恶意页面新建attack_post.html写入以下内容!DOCTYPE html html headtitle安全检测/title/head body h1系统安全升级请立即修改密码/h1 form idhackForm actionhttp://localhost/dvwa/vulnerabilities/csrf/ methodPOST styledisplay:none; input typehidden namepassword_new valuehacked123 input typehidden namepassword_conf valuehacked123 input typehidden nameChange valueChange /form script // 页面加载后自动提交表单 document.getElementById(hackForm).submit(); /script p正在为您更新密码请稍候.../p /body /html实施攻击保持DVWA的登录状态。在同一浏览器访问http://localhost/attack_post.html。页面会快速跳转回DVWA的CSRF页面并显示“Password Changed.”。此时admin的密码已被修改为hacked123攻击者成功夺取了管理员账户。注意事项在实际攻击中攻击者需要精确知道所有必需的参数。对于更复杂的表单可能包含隐藏的token或动态参数。这就需要攻击者先用自己的账户操作一遍分析请求或者通过其他信息泄露漏洞如HTML源码注释、JS文件来获取表单结构。这也是为什么不应对客户端提交的数据结构抱有丝毫信任。4. CSRF漏洞的挖掘与自动化探测思路作为安全人员我们不仅要懂攻击更要会挖掘。CSRF漏洞的挖掘可以遵循一套系统化的流程。4.1 手动挖掘流程与关键观察点资产梳理与功能点枚举使用爬虫如Burp Suite的爬虫、gospider或人工浏览收集目标应用的所有功能链接特别是那些具有“状态改变”功能的点登录/注销、密码修改、邮箱绑定、资料编辑、资金操作、数据删除、权限变更等。请求分析对每个敏感功能点使用代理工具Burp Suite, OWASP ZAP拦截其请求。方法判断如果是GET请求风险极高直接标记。参数分析检查POST/PUT等请求的参数。有无Token寻找如csrf_token,authenticity_token,_token,X-CSRF-TOKEN在头部等字段。如果完全没有存在漏洞可能性大。Token的绑定关系即使有Token也要验证Token是否与用户会话绑定。尝试将A用户的Token用在B用户的请求上看是否被拒绝。如果通用则Token形同虚设。Referer/Origin检查检查服务器是否验证了Referer或Origin头部。可以尝试在Burp中删除或修改这些头部重放请求看是否成功。同源性检查检查请求是否依赖自定义头部如X-Requested-With: XMLHttpRequest。在传统表单提交中浏览器不会主动添加此类头部但通过AJAX发送的请求会。如果服务器仅凭此头部判断是否为“合法”请求而忽略了CSRF Token则存在绕过可能。Cookie依赖分析确认该操作是否仅依赖会话Cookie进行身份认证。可以尝试在另一个浏览器无登录状态中直接重放该请求看是否返回“未授权”错误。如果未登录也能成功那可能是更严重的未授权访问漏洞。4.2 自动化工具辅助与脚本编写手动测试效率低对于大型应用需要借助工具或自写脚本。Burp Suite Professional - CSRF PoC Generator这是最强大的辅助工具。在Burp中拦截到目标请求后右键 -Engagement tools-Generate CSRF PoC。Burp会自动根据请求方法、参数生成一个HTML攻击页面。你可以进一步调整这个页面并直接在Burp内置浏览器中测试该浏览器会继承你的会话Cookie非常方便。OWASP ZAP - CSRF Token ScannerZAP的主动扫描规则中包含了对CSRF Token的检测。它可以尝试找出表单中的token字段并测试其是否可预测或重复使用。自定义Python探测脚本对于需要批量测试的场景可以编写脚本。思路是使用requests库的Session对象维持登录态然后遍历功能链接列表对每个链接发送请求并分析响应。探测Token缺失检查响应HTML中表单是否包含token字段或检查API响应/请求规范。测试Token有效性获取一个token后尝试在另一个会话中使用或尝试使用空值、错误格式的值提交。# 一个简化的思路示例非完整代码 import requests session requests.Session() login_data {username:test, password:test} session.post(login_url, datalogin_data) # 登录 # 假设有一个需要测试的URL列表 urls_to_test [/change_email, /transfer, /delete_post/123] for url in urls_to_test: full_url base_url url # 首先获取页面分析表单 get_resp session.get(full_url) # 这里可以解析HTML查找表单和可能的token # 然后构造一个模拟跨域请求的测试 # 例如用另一个不携带Cookie的会话对象去请求但手动添加Cookie头模拟攻击者场景 test_session requests.Session() # 从已登录会话中窃取Cookie模拟攻击者获取Cookie的情况但CSRF通常不需要 # 更真实的测试是检查请求是否缺少Token、Referer验证等 # 可以尝试删除可能的Token参数后提交 post_data {email: attackerevil.com} # 缺少token test_resp session.post(full_url, datapost_data) if 修改成功 in test_resp.text: print(f[VULNERABLE] {url} 可能缺少CSRF防护)避坑技巧自动化扫描CSRF的误报率不低。很多应用会在全局中间件或模板中自动添加Token但扫描器可能因为JavaScript动态加载表单而漏检。因此自动化结果必须结合人工审核。重点关注意义重大的敏感操作如资金、账号核心信息修改等。5. 主流防御方案剖析从原理到落地踩坑理解了攻击防御就有了方向。CSRF防御的核心思想是增加一个攻击者无法预测、无法伪造的凭证让服务器能区分合法请求和伪造请求。5.1 同步令牌Synchronizer Token Pattern, STP这是最经典、最可靠的防御方案适用于有服务端会话状态的Web应用。原理用户访问包含表单的页面时服务器生成一个随机、不可预测的Token如UUID将其存储在用户的会话Session中同时将其嵌入到返回页面的表单里通常是一个隐藏域input typehidden namecsrf_token value随机值。用户提交表单时这个Token会随着其他表单数据一起提交到服务器。服务器收到请求后比对请求中的Token和会话中存储的Token是否一致。一致则认为是合法请求否则拒绝。实现要点与坑点Token的生成与存储必须使用密码学安全的随机数生成器。Token应与当前用户会话强绑定。Token的提交对于GET请求不应使用Token防御而应强制改用POST。Token应放在POST请求体或自定义HTTP头中如X-CSRF-TOKEN避免通过URL传递导致泄露如Referer、日志。每会话或每请求通常采用“每表单”或“每会话”一个Token。对于安全性要求极高的操作如转账可以考虑每次请求都刷新Token即“每请求Token”但这可能影响浏览器的“后退”操作或多标签操作。AJAX请求的处理对于通过JavaScript发起的AJAX请求Token需要被JavaScript读取并添加到请求头中。常见的做法是将Token写入页面的meta标签如meta namecsrf-token contenttoken-value然后由前端框架如Axios全局拦截请求并添加头部。// 以Axios为例的全局配置 const csrfToken document.querySelector(meta[namecsrf-token]).getAttribute(content); axios.defaults.headers.common[X-CSRF-TOKEN] csrfToken;踩坑实录我曾经审计过一个系统它确实使用了Token但犯了一个致命错误Token在服务器端没有与会话绑定验证。它只是检查提交的Token是否存在于一个全局的Token池中。这意味着攻击者可以先访问自己的账户页面获取一个有效的Token然后用这个Token去伪造其他用户的请求。正确的做法必须是session[‘csrf_token’] submitted_token。5.2 双重Cookie验证这种方案在前后端分离、API化的应用中更常见因为它不依赖服务端会话存储Token。原理用户登录后服务端在返回的响应中通过Set-Cookie设置一个随机Token例如csrf_tokenabc123并标记为HttpOnly防止XSS窃取和SameSiteLax/Strict。前端JavaScript从Cookie中读取这个Token由于是HttpOnlyJS无法读取所以需要服务端在登录API的响应体中也返回这个Token值或者单独提供一个获取Token的API。前端在发起敏感请求如POST时将这个Token值放到请求的自定义Header中例如X-CSRF-Token: abc123。服务端收到请求后比对请求头X-CSRF-Token中的值与请求中Cookiecsrf_token的值是否一致。一致则通过。为什么有效攻击者可以伪造请求也可以让受害者的浏览器携带Cookie但他无法同时做到1. 让浏览器发送自定义Header因为跨域请求默认不能添加自定义头2. 读取HttpOnly的Cookie值并放到Header里。这就是“双重”的含义。实现注意事项Cookie属性务必设置SameSite属性至少为Lax以提供额外的防御层。设置HttpOnly防止XSS直接窃取Token值。Token的获取前端需要一个安全的方式获取Token值。常见模式是登录成功后服务端在JSON响应体中返回csrfToken字段前端将其存储在内存或非HttpOnly的Cookie中用于后续请求设置Header。CORS配置如果API与前端不同源需要正确配置CORS允许前端源发送X-CSRF-Token这个自定义头。5.3 SameSite Cookie属性这是一个由浏览器实现的、从源头缓解CSRF的机制。它不能完全替代Token但是一道极其有效的防线。原理通过设置Cookie的SameSite属性告诉浏览器在什么情况下可以发送这个Cookie。SameSiteStrict最严格。Cookie仅在同站请求即当前页面URL的站点与请求目标站点一致时发送。这意味着从evil.com发往bank.com的请求绝不会携带Strict属性的Cookie。副作用如果用户在evil.com点击一个指向bank.com的链接他访问bank.com时也是未登录状态体验不友好。SameSiteLax默认值宽松模式。在跨站的顶级导航如点击链接且是安全方法GET时会发送Cookie。但对于跨站的POST请求、iframe加载、AJAX请求等则不发送Cookie。这很好地平衡了安全与用户体验能防御绝大多数CSRF攻击。SameSiteNoneCookie会在所有上下文中发送但必须同时设置Secure属性即仅限HTTPS。如何设置在服务端设置Cookie时指定。Set-Cookie: SessionIDabc123; Path/; HttpOnly; SameSiteLax实操心得SameSiteLax已经成为现代浏览器的默认行为。这意味着即使你的后端代码没有显式设置Token只要用户的浏览器不是特别旧的版本很多基于POST方法的CSRF攻击已经天然被缓解了。但是这绝不能成为你不实现Token的理由。因为1. 浏览器兼容性旧版浏览器不支持2.Lax模式对GET请求的CSRF防护有限而GET请求本就不应用于写操作3. 它不能防御来自同源子域的攻击如果Cookie的Domain设置过宽。因此SameSiteCSRF Token才是黄金组合。5.4 验证Referer/Origin头部这是一种辅助验证手段不应作为唯一防线。Referer表示请求来源页面的完整URL。服务器可以检查Referer头部是否来源于自己的域名。攻击者构造的恶意页面来自evil.com其Referer也是evil.com因此会被拒绝。Origin对于跨域请求如CORS浏览器会发送Origin头部表示请求发起的源协议域名端口。它比Referer更简洁且不会包含路径等敏感信息。缺陷与绕过隐私与缺失用户可能禁用浏览器发送Referer或者从本地文件file://、HTTPS跳转到HTTP时Referer可能为空或被剥离。过于严格的检查会误伤正常用户。可被篡改虽然浏览器行为是标准的但在某些中间人攻击或客户端代理环境下头部可能被篡改。不过在纯粹的CSRF场景下攻击者无法控制受害者浏览器发送的Referer或Origin头。绕过技巧如果验证逻辑不严谨可能存在绕过。例如只检查Referer中是否包含example.com那么evil.com?example.com就能绕过。正确的做法是检查Referer的头部是否以https://yourdomain.com/开头或者其主机部分是否精确等于yourdomain.com。建议可以将Referer/Origin验证作为深度防御的一环与Token结合使用。例如先检查Referer是否合法如果不合法再要求验证Token这样可以在大多数正常请求中省去一次Token校验的开销。6. 高级绕过技巧与组合漏洞利用在真实的攻防对抗中防御措施可能存在缺陷攻击者会寻找各种绕过方法。6.1 Token防御的绕过Token未绑定会话如前所述如果Token是全局的或可预测的攻击者可以为自己生成一个Token然后用于攻击他人。Token泄露如果网站同时存在XSS漏洞攻击者可以利用XSS窃取用户的Token。因为Token通常就放在HTML页面中。这就是为什么防御需要纵深修复XSS同样重要。校验逻辑缺陷服务器可能错误地检查了Token的存在性而非有效性。例如只检查请求中是否有csrf_token参数而不校验其值。攻击者可以提交一个空值或任意值。跨域Token窃取CORS配置错误如果目标网站的API配置了过于宽松的CORS策略如Access-Control-Allow-Origin: *并且Token可以通过某个API获取如GET /api/csrf_token那么攻击者网站上的JavaScript就可以跨域读取到这个Token从而构造出完美的CSRF请求。关键点敏感接口和Token获取接口的CORS策略必须严格。6.2 SameSite Cookie的局限性旧浏览器不支持IE等旧浏览器不支持SameSite属性在这些浏览器上Cookie会以默认的None行为发送防御失效。宽松模式Lax下的GET请求SameSiteLax允许在跨站顶级导航的GET请求中发送Cookie。因此如果应用错误地使用GET方法进行写操作如GET /delete?id1CSRF攻击依然可能成功。时间窗口攻击某些攻击如“登录CSRF”发生在用户登录之前。攻击者先诱导用户访问恶意站点该站点向目标网站发起登录请求使用攻击者控制的凭证。由于此时浏览器还没有目标网站的登录CookieSameSite限制不适用。用户“被登录”后后续操作可能就处于攻击者的控制之下。6.3 结合其他漏洞的降维打击CSRF很少单独造成毁灭性影响但与其他漏洞结合威力巨大。CSRF XSS 完美攻击链XSS可以绕过几乎所有CSRF防护Token、SameSite、Referer。通过XSS攻击者可以直接在目标网站上下文中执行JavaScript从而直接读取Token、发起任意请求。这种组合是审计中的高危发现。CSRF 逻辑漏洞例如一个修改邮箱的功能分为两步1. 请求验证码到原邮箱2. 提交验证码和新邮箱。如果第二步存在CSRF攻击者可以在用户收到验证码后但还未提交时伪造请求将邮箱改为自己的。虽然他不知道验证码但如果逻辑漏洞允许在提交新邮箱时同时指定接收验证码的新邮箱攻击就可能成功。JSON CSRF现代API常使用JSON格式传输数据。传统的HTML表单无法直接发送JSON但可以通过构造一个带有typetext/plain的textarea或使用fetch()API并设置Content-Type: text/plain来绕过浏览器的CORS预检请求在某些配置不当的服务器上实现CSRF。防御方法是对于非简单请求如Content-Type为application/json严格执行CORS预检并在服务器端校验Content-Type。7. 不同技术栈下的CSRF防护实战理论最终要落地到代码。不同框架和架构的防护实现各有特点。7.1 传统服务端渲染SSR应用以Spring Security (Java)和Laravel (PHP)为例。Spring Security默认提供了CSRF防护。它会为每个会话生成一个Token并期望在非安全方法POST, PUT, PATCH, DELETE的请求中通过_csrf参数或X-CSRF-TOKEN头部提交该Token。Thymeleaf模板中自动添加使用form标签时Thymeleaf会自动添加一个隐藏域。input typehidden th:name${_csrf.parameterName} th:value${_csrf.token} /手动处理AJAX需要将Token放到meta标签并在JS中读取。meta name_csrf th:content${_csrf.token}/ meta name_csrf_header th:content${_csrf.headerName}/var token $(meta[name_csrf]).attr(content); var header $(meta[name_csrf_header]).attr(content); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(header, token); });禁用对于纯API接口如果已采用JWT等无状态认证可以在配置中禁用CSRF防护.csrf().disable()。LaravelLaravel为每个活跃的用户会话自动生成CSRF Token并通过VerifyCsrfToken中间件进行验证。Blade模板使用csrf指令自动生成隐藏字段。form methodPOST action/profile csrf ... /formAJAX请求Laravel将Token存储在meta标签namecsrf-token中。你可以像之前Axios的例子一样全局配置。排除特定路由在VerifyCsrfToken中间件的$except数组中添加URI可以排除某些路由如第三方支付回调的CSRF检查。7.2 前后端分离SPA应用以Vue.js Node.js/Express为例。核心是采用“双重Cookie验证”或“Token放在自定义Header”的模式。后端Express示例用户登录成功后生成Token在响应Cookie和JSON体中返回。// 登录路由 app.post(/api/login, (req, res) { // ... 验证逻辑 const csrfToken generateRandomToken(); // 设置HttpOnly的Cookie res.cookie(csrf-token, csrfToken, { httpOnly: true, sameSite: lax }); // 在响应体中也返回供前端JS读取 res.json({ success: true, user: userInfo, csrfToken: csrfToken }); });创建一个全局中间件验证非GET/HEAD/OPTIONS请求。// csrfMiddleware.js function csrfProtection(req, res, next) { const safeMethods [GET, HEAD, OPTIONS]; if (safeMethods.includes(req.method)) { return next(); } const tokenFromHeader req.headers[x-csrf-token]; const tokenFromCookie req.cookies[csrf-token]; if (!tokenFromHeader || !tokenFromCookie || tokenFromHeader ! tokenFromCookie) { return res.status(403).json({ error: Invalid CSRF token }); } next(); } app.use(csrfProtection);前端Vue with Axios示例登录后将服务端返回的csrfToken存储在Vuex或全局变量中并配置Axios默认头。// api.js import axios from axios; let csrfToken ; export function setCsrfToken(token) { csrfToken token; axios.defaults.headers.common[X-CSRF-Token] token; } // 在登录成功的回调中调用 setCsrfToken(response.data.csrfToken)后续所有非简单请求都会自动带上X-CSRF-Token头。7.3 基于JWT的无状态API对于完全无状态的JWT认证CSRF的风险模型发生了变化。因为身份信息JWT通常由前端存储在localStorage或sessionStorage中并通过Authorization: Bearer token头部发送而浏览器不会自动在跨域请求中附加这个头部。这听起来好像天然免疫CSRF并非如此一个常见的错误做法是为了兼容性将JWT也放在Cookie里并标记为HttpOnly。这样浏览器就会自动发送它从而重新引入CSRF风险。如果采用了这种模式就必须同时实施CSRF防护如双重Cookie验证。最佳实践对于纯JWT API坚持将Token放在Authorization头部绝不存入会被浏览器自动发送的Cookie。这样CSRF攻击由于无法伪造这个自定义头攻击就会失败。此时防御重点应转向防止XSS窃取localStorage中的Token。8. 企业级防护体系建设与SDL实践对于企业而言CSRF防护不应是开发人员事后补的补丁而应融入软件开发生命周期SDL。安全开发规范制定在编码规范中明确要求所有会改变系统状态或用户数据的操作必须使用POST、PUT、PATCH或DELETE方法严禁使用GET。所有非幂等的请求POST, PUT, PATCH, DELETE必须包含有效的CSRF Token验证。明确前后端分离架构下的Token传递和验证方案如双重Cookie验证。规定Cookie必须设置SameSite属性默认为Lax敏感Cookie必须设置HttpOnly和Secure。框架与组件选型优先选择内置了成熟CSRF防护机制的开发框架如Spring Security, Laravel, Django。在引入第三方库或中间件时需评估其安全性确认其CSRF防护是否默认开启或易于集成。自动化安全测试SAST/DAST静态应用安全测试SAST在代码层面通过工具扫描源代码识别是否存在未受保护的表单提交、缺失Token校验的控制器方法等。动态应用安全测试DAST在测试环境使用OWASP ZAP、Burp Suite等工具进行主动扫描模拟CSRF攻击验证防护措施是否生效。代码审计与渗透测试定期进行人工代码审计和黑盒渗透测试。审计重点检查Token的生成、存储、验证逻辑是否正确是否存在逻辑绕过的可能。渗透测试则从攻击者视角尝试寻找防护体系的薄弱点。监控与响应在Web应用防火墙WAF或网关层面可以部署规则监控异常的请求模式例如大量缺失预期Token的POST请求这可能指示正在发生CSRF攻击探测。建立安全事件响应流程一旦发现漏洞能快速定位、修复和上线补丁。我个人在多年的企业安全建设中深刻体会到单一的技术防御永远不够。意识才是最重要的防线。让每一位开发者都理解CSRF的原理和危害在写第一行代码时就能条件反射般地想到防护这才是将安全真正“左移”从源头杜绝漏洞的根本之道。每次代码评审看到提交的表单没有csrf我都会不厌其烦地指出这不仅仅是一个漏洞点更是一个安全习惯的缺失。安全体系的建设正是由这一个个细节堆砌而成的。