
1. 项目概述从一次“改分”实战看CSRF漏洞的本质最近在带新人做安全测试发现很多人对CSRF跨站请求伪造漏洞的理解还停留在“知道有这么个东西”的层面真到了靶场环境面对一个看似无害的链接或表单往往就不知道怎么下手了。正好手头这个“Grade靶场”的CSRF漏洞场景非常典型——目标就是利用漏洞在未经授权的情况下修改学生的成绩。这听起来有点“黑客电影”的味道但原理其实很朴素就是利用了Web应用对用户请求来源的信任机制。今天我就以这个“改成绩”的实战为例把CSRF漏洞从原理到利用再到防御掰开揉碎了讲清楚。无论你是刚入门的安全爱好者还是想巩固基础的开发人员这篇文章都能让你对CSRF有一个透彻、实操层面的理解而不仅仅是背下几个名词。简单来说CSRF攻击就是攻击者诱骗已经登录了目标网站比如教务系统的用户去点击一个恶意链接或访问一个恶意页面。这个恶意请求会“借用”用户在目标网站上的登录状态通常是浏览器自动携带的Cookie以该用户的身份执行非本意的操作比如转账、改密码或者像我们这个靶场里的——更改成绩。整个过程用户可能毫无察觉。这个靶场模拟的正是这样一个存在缺陷的“成绩管理系统”我们接下来的所有操作都将围绕如何发现并利用这个缺陷展开。2. 靶场环境搭建与核心漏洞原理剖析2.1 靶场部署与初步侦查首先我们需要一个可操作的靶场环境。网络上有很多优秀的开源靶场比如Pikachu、DVWA、Webug等它们都内置了CSRF漏洞场景。为了更贴近“Grade”这个主题我们可以选择Pikachu靶场它里面有一个非常清晰的“CSRF”模块模拟了类似“修改个人信息”的场景其原理与我们“改成绩”完全一致。部署方式很简单通常是一个PHP项目你只需要有一个支持PHP和MySQL的Web服务器环境如XAMPP、PHPStudy把源码丢到网站根目录按照提示初始化数据库即可。注意请务必在本地虚拟机或隔离的网络环境中搭建和测试靶场。未经授权对任何线上系统进行测试都是非法且不道德的。环境跑起来后我们访问靶场登录默认账号密码通常在源码或文档里找到CSRF测试模块。以Pikachu的“CSRF(get)”为例它会展示一个表单让用户修改昵称、邮箱等信息。我们的第一步不是急着攻击而是扮演一个正常的用户和开发者去理解这个请求的流程。正常操作观察我们先正常修改一次信息比如把昵称改成“test_user”。在这个过程中打开浏览器的开发者工具F12切换到“网络”(Network)标签页并勾选“保留日志”(Preserve log)。点击提交后我们会看到浏览器向服务器发送了一个HTTP请求。关键请求分析仔细查看这个请求。你会发现几个关键特征请求方法很可能是GET也可能是POST。Pikachu的“CSRF(get)”模块用的就是GET。这意味着修改信息的参数如nicknametest_user直接附加在URL后面形如http://target.com/csrf/get/edit.php?nicknametest_usersubmitsubmit。认证凭证在请求头中你会看到Cookie字段里面包含了你的会话标识如PHPSESSIDxxxxxx。这就是你处于登录状态的证明。服务器靠这个Cookie来识别你是“谁”。缺乏Token最关键的一点查看请求的URL参数或请求体如果是POST你会发现没有一个随机的、一次性的令牌参数比如csrf_tokenabcdef123456。这就是漏洞存在的根本标志。这个分析过程就是“侦查”。它告诉我们这个修改操作仅依赖于浏览器自动携带的Cookie来认证用户身份而请求本身无论来自哪里只要参数正确服务器就会执行。这就为CSRF攻击打开了大门。2.2 CSRF漏洞的深层原理与条件为什么没有Token就是漏洞这需要从Web的“无状态”和“信任”机制说起。HTTP协议本身是无状态的服务器无法记住上一次是谁来的。为了维持登录状态发明了Session在服务器端和Cookie在客户端通常是Session ID这套机制。服务器告诉浏览器“给你个通行证Cookie下次来带着我就知道是你了。”CSRF攻击正是滥用了这份“信任”。它基于一个关键假设浏览器在向某个域名发送请求时会自动携带该域名下的Cookie。攻击者构造一个请求这个请求指向目标网站例如靶场的修改成绩接口。当受害者在已经登录目标网站的情况下即浏览器存有有效Cookie访问了攻击者精心设计的页面这个页面会自动或诱导用户向目标接口发起请求。此时浏览器“老实巴交”地附上了受害者的Cookie服务器一看Cookie有效便认为是受害者在主动操作于是执行了修改动作。一次成功的CSRF攻击需要同时满足以下几个条件目标操作存在网站有一个明确的操作接口比如修改数据、转账、发帖。Cookie/Session认证这个操作依赖Cookie或Session来识别用户身份且没有其他强验证如二次密码、验证码。请求可预测攻击者能够完全预测该操作所需的所有HTTP请求参数如用户ID、要修改的成绩值等。这些参数通常可以通过分析正常请求轻易获得。缺乏不可伪造的令牌请求中没有使用随机的、与当前用户会话绑定的TokenCSRF Token。或者即使有Token但存在其他逻辑缺陷导致Token可以被攻击者获取或绕过。我们这个“Grade靶场”场景完美符合以上所有条件。修改成绩是一个操作用Cookie登录请求参数学号、课程、新成绩固定或可预测并且最关键的是——没有CSRF Token验证。3. 漏洞利用实战手工构造与自动化攻击理解了原理我们开始动手。攻击的核心是“伪造请求”。根据目标接口使用的是GET还是POST方法我们的利用方式略有不同。3.1 GET型CSRF利用——最简单的攻击如果修改成绩的接口使用GET方法就像Pikachu的get模块那么利用起来简单得令人吃惊。攻击者只需要构造一个URL里面包含所有必要的参数。假设我们分析出正常修改成绩的请求是http://grade.local/change_score.php?student_id1001coursemathnew_score95submit1那么恶意URL就是它本身攻击者会想方设法让受害者点击这个链接。方式包括在论坛、评论区发布带有该链接的图片或文字“ 看看这个有趣的图片 ”实际上点击后是提交改分请求。通过短链接服务隐藏真实URL。将其嵌入到邮件或即时消息中。手工验证你可以在已登录靶场的情况下直接在浏览器地址栏输入这个恶意URL并回车你会发现成绩真的被修改了而页面可能只是跳转了一下甚至没有任何明显提示。这就是GET型CSRF的威力——简单、直接、隐蔽。3.2 POST型CSRF利用——需要一点HTML技巧更多的情况下修改操作会使用POST方法以传输更多数据或遵循RESTful规范。POST请求的参数不在URL里而是在请求体body中。这时仅仅一个链接就不够了我们需要一个能自动提交POST表单的页面。攻击者会搭建一个恶意网站页面上包含一个隐藏的HTML表单和一段自动提交的JavaScript代码。!-- 恶意页面 exploit.html -- html body h1你中奖了/h1 p正在跳转到领奖页面.../p !-- 隐藏的表单指向目标靶场接口 -- form idcsrf_form actionhttp://grade.local/change_score.php methodPOST input typehidden namestudent_id value1001 / input typehidden namecourse valuemath / input typehidden namenew_score value0 / input typehidden namesubmit value1 / /form script // 页面加载后自动提交表单 document.getElementById(csrf_form).submit(); /script /body /html当受害者访问这个恶意页面时表单会自动提交。由于受害者浏览器里存有grade.local的登录Cookie这个POST请求会带着Cookie发往靶场服务器从而成功将学号1001的数学成绩改为0分。页面可能快速闪一下受害者看到的只是“你中奖了”和一段跳转文字完全不知道背后发生了什么。3.3 利用技巧与场景扩展在实际攻击中攻击者会运用更多技巧提高成功率结合XSS如果目标网站本身存在XSS漏洞攻击者可以注入脚本直接在目标网站域内发起CSRF请求完全绕开同源策略限制威力巨大。精准钓鱼通过社会工程学使恶意链接或页面看起来与目标网站高度相关例如伪造一个“教务系统安全升级请重新验证”的页面其中就嵌入了CSRF攻击代码。绕过Referer检查有些简单的防御会检查HTTP请求头中的Referer字段看请求来源是否是自己网站的页面。攻击者可以通过一些手段如利用HTTPS跳转HTTP、某些浏览器的隐私设置或漏洞来伪造或剥离Referer。实操心得在测试时务必使用两个不同的浏览器或一个浏览器的正常窗口和无痕窗口来模拟攻击者和受害者。用一个浏览器受害者登录靶场另一个浏览器攻击者访问你构造的恶意页面观察受害者浏览器的状态或靶场数据是否发生变化。这是最清晰的验证方式。4. 漏洞挖掘与自动化工具辅助4.1 手工挖掘漏洞的步骤作为一名安全测试人员如何在一个Web应用中寻找CSRF漏洞可以遵循以下步骤功能点枚举列出所有具有状态改变功能的操作点。如修改资料、修改密码、转账、发帖、删除、审核、评分我们靶场的改成绩、上传等。请求分析对每个功能点使用代理工具如Burp Suite拦截其正常请求。查看方法是GET还是POSTGET方法的风险通常更高。寻找Token检查请求参数或头部如自定义Header、表单隐藏域是否存在看似随机的、名称如csrf_token、authenticity_token、_token等的参数。评估其他验证除了Token是否有验证码、二次密码、请求头校验如X-Requested-With移除Token测试如果发现Token尝试在重放请求时将其删除或修改为一个错误的值观察服务器是否拒绝执行。如果服务器依然成功执行操作说明Token校验逻辑存在缺陷。构造POC对于疑似存在漏洞的点无Token或Token校验可绕过按照上面第3节的方法手工构造一个简单的HTML PoC概念验证页面。在自己的测试环境中验证其是否真的能触发非授权操作。验证依赖确认该操作是否完全依赖Session Cookie进行身份认证。可以尝试在未登录状态下直接发送请求不带Cookie看是否被拒绝。如果被拒而在有Cookie且无Token时成功则漏洞确认。4.2 使用Burp Suite辅助测试Burp Suite作为渗透测试神器提供了强大的CSRF测试支持拦截与重放用Proxy模块拦截修改成绩的请求右键发送到Repeater模块。生成POC在Repeater中右键请求选择Engagement tools-Generate CSRF PoC。Burp会自动根据当前请求生成一个HTML攻击页面代码并内置了自动提交脚本。你可以直接复制这段代码保存为.html文件用于测试非常方便。漏洞扫描使用Scanner模块进行主动扫描时它也会自动检测潜在的CSRF漏洞。但要注意自动扫描可能会有误报和漏报手工验证必不可少。注意事项Burp生成的POC有时会因为目标站点的同源策略CORS或复杂的JavaScript交互而失败。对于现代单页面应用SPACSRF攻击可能更复杂需要分析其API调用方式和认证机制如是否使用Bearer Token而非Cookie。此时手工分析Ajax请求和前端代码变得尤为重要。5. 从防御到修复彻底杜绝CSRF攻击知道了怎么攻击才能更好地防御。作为开发者修复CSRF漏洞是必须的。以下是几种主流且有效的防御方案按推荐程度排序。5.1 同步令牌模式Synchronizer Token Pattern这是最经典、最有效的防御手段也是OWASP首要推荐的方法。原理服务器在用户会话中生成一个随机、不可预测的令牌CSRF Token并在返回给用户的表单或页面中嵌入这个令牌通常作为隐藏字段。当用户提交表单时必须将这个令牌一并提交回服务器。服务器验证提交的令牌是否与会话中存储的令牌一致且是否未过期。攻击者无法构造合法的请求因为他无法得知这个与特定用户会话绑定的随机令牌。实现步骤用户访问包含表单的页面如修改成绩页面。服务器端生成一个强随机数如UUID将其存入当前用户的Session中同时将其输出到表单的一个隐藏输入框里。form actionchange_score.php methodPOST input typehidden namecsrf_token value?php echo $_SESSION[csrf_token]; ? !-- 其他表单字段 -- input typetext namenew_score /form用户提交表单。服务器端change_score.php在处理请求前首先比较$_POST[csrf_token]和$_SESSION[csrf_token]是否一致。不一致则立即拒绝请求返回错误。关键细节令牌需足够随机使用密码学安全的随机数生成器。令牌需与会话绑定每个用户、每个会话的令牌都应不同。令牌应一次性最好每次提交后都使旧令牌失效生成新令牌更安全但可能影响浏览器后退或多标签操作。至少保证令牌有较短的有效期。对GET请求也要保护如果某些GET请求会改变状态不推荐但历史代码可能存在也需要为其添加Token验证可以将Token放在URL查询参数中。5.2 双重Cookie验证这是一种在客户端实现的、相对简单的方案常见于前后端分离的架构。原理在用户登录后前端不仅从响应中获取用于身份认证的Cookie通常是HttpOnly的还会获取一个额外的CSRF Token通过响应头或JSON body。前端代码通常是JavaScript将这个Token写入另一个非HttpOnly的Cookie中或者存储在内存如Vuex/Redux中。之后每次发起可能改变状态的请求POST/PUT/DELETE等时前端需要将这个Token作为自定义请求头如X-CSRF-TOKEN发送给服务器。服务器验证请求头中的Token值与Cookie中的Token值是否一致。为什么能防御攻击者可以通过CSRF攻击让浏览器发送Cookie但他无法通过JavaScript读取目标站点的Cookie得益于同源策略因此他无法知道这个Token的值也就无法构造正确的X-CSRF-TOKEN请求头。实现示例登录成功后服务器在响应头设置Set-Cookie: csrf_tokenabc123; Path/前端JS保存此Token从Cookie读取或从响应体解析。前端发起修改成绩的请求fetch(/api/change_score, { method: POST, headers: { Content-Type: application/json, X-CSRF-TOKEN: getCSRFToken() // 从Cookie或内存读取 }, body: JSON.stringify({score: 95}), credentials: include // 确保发送Cookie });服务器端比较请求头X-CSRF-TOKEN的值和请求中携带的Cookie里csrf_token的值是否一致。5.3 检查Origin/Referer Header这是一种辅助性的防御手段不应作为唯一依赖。原理服务器检查HTTP请求头中的Origin或Referer字段。这两个字段通常包含了发起请求的页面来源。如果请求来自同源站点自己的网站则放行如果来自外域则拒绝。局限性Referer可能被篡改或缺失有些浏览器出于隐私考虑在某些情况下不会发送Referer如从HTTPS跳到HTTP。攻击者也可能通过某些浏览器漏洞或中间人攻击来篡改Referer。Origin只存在于CORS请求对于简单的表单提交浏览器不会发送Origin头。配置复杂需要精确维护一个合法的源Origin列表。因此这种方法通常作为其他主防御方案如Token的补充用于增加攻击门槛。5.4 框架内置防护与最佳实践现代Web开发框架通常内置了CSRF防护Django中间件django.middleware.csrf.CsrfViewMiddleware默认启用为表单自动添加{% csrf_token %}标签。Spring Security默认启用CSRF保护要求所有非GET请求携带_csrf参数。Laravel为每个活跃用户会话生成CSRF令牌并通过csrf指令在表单中嵌入。Express (Node.js)可以使用csurf中间件。开发最佳实践遵循RESTful规范使用合适的HTTP方法。GET请求只用于获取数据永不改变状态。修改数据用POST、PUT、PATCH删除用DELETE。这从习惯上减少了GET型CSRF的风险。关键操作增加二次确认对于修改密码、转账、删除重要数据等操作要求用户输入当前密码或进行短信/邮箱验证。这虽然不是专门的CSRF防御但能有效缓解CSRF造成的损害。设置Cookie属性为身份认证Cookie设置SameSite属性为Strict或Lax。SameSiteLax可以阻止大多数跨站的POST请求携带Cookie对防御CSRF有显著效果已成为现代浏览器的默认或推荐行为。定期安全审计与测试将CSRF漏洞检查纳入代码审查和自动化安全测试流程。6. 靶场实战进阶漏洞组合与复杂场景单一的CSRF漏洞利用已经很有威胁但当它与其他漏洞结合时会产生更大的破坏力。在“Grade靶场”或类似环境中我们可以尝试探索一些进阶场景。6.1 CSRF XSS链式攻击的威力假设靶场的成绩查询页面存在一个存储型XSS漏洞比如在显示学生姓名时未做过滤攻击者可以将姓名设置为一段恶意脚本script.../script。那么所有查看该学生信息的老师或管理员其浏览器都会执行这段脚本。攻击者可以在这段脚本中嵌入CSRF攻击代码。例如脚本自动构造一个修改管理员密码或提升攻击者权限的请求。由于脚本是在目标网站域内执行的它完全拥有该域下的Cookie访问权限除非Cookie是HttpOnly的可以轻松读取CSRF Token如果存在但可被读取然后发起一个完全合法的、带Token的请求。这种组合攻击几乎可以绕过所有单纯的CSRF防御措施。防御思路根治XSS对所有用户输入进行严格的输出编码同时为关键Cookie设置HttpOnly属性防止JavaScript读取从而切断XSS获取Token的途径。6.2 针对JSON API的CSRF攻击现代前端应用常通过JSON API与后端交互。如果后端API仅依赖Cookie进行身份认证并且没有实施CSRF防护那么它依然可能受到攻击。攻击者可以构造一个恶意页面使用JavaScript的fetch或XMLHttpRequest向目标API发送JSON格式的POST请求。关键在于需要设置credentials: include或withCredentials: true来让浏览器携带Cookie。script fetch(http://grade.local/api/update_score, { method: POST, headers: {Content-Type: application/json}, credentials: include, // 关键携带Cookie body: JSON.stringify({studentId: 1001, score: 0}) }); /script如果服务器端没有检查Content-Type头部是否为预期值如application/json或者没有验证请求来源Origin/Referer也没有使用Token那么这个攻击就会成功。防御思路对于API除了使用CSRF Token还可以采用以下方式使用自定义请求头如前文所述的双重Cookie验证要求前端在请求中设置一个自定义头如X-Requested-With: XMLHttpRequest后端验证该头是否存在。因为浏览器在发起跨域请求时默认不允许前端代码添加某些自定义头这属于CORS预检请求的范畴这能在一定程度上增加攻击难度但并非绝对安全。采用Token-Based认证如JWT替代Cookie将认证信息放在请求头如Authorization: Bearer token中而不是Cookie里。由于浏览器不会自动在跨站请求中携带Authorization头因此从根本上避免了CSRF。但需要注意保护Token不被XSS窃取。6.3 绕过有缺陷的Token验证有时应用虽然使用了Token但实现上有漏洞Token未绑定会话所有用户共享同一个静态Token或者Token生成算法可预测。Token验证逻辑可绕过服务器端验证Token时存在逻辑缺陷。例如先检查Token是否存在如果存在则验证如果不存在则跳过验证。攻击者只需在请求中不提供Token字段即可绕过。Token泄露如果网站同时存在XSS漏洞攻击者可以通过XSS窃取到有效的Token。在靶场测试中可以尝试以下方法使用两个不同的账户A和B。用A账户登录获取其表单中的CSRF Token。用B账户登录在修改B自己信息的请求中尝试使用A账户的Token。如果成功说明Token未与用户绑定。尝试删除请求中的Token参数或者将其值改为空、0、null等观察服务器反应。7. 总结与反思安全思维的建立通过这个“Grade靶场”的CSRF漏洞利用实战我们完成了一次完整的安全攻防推演。从环境搭建、原理分析、手工利用、工具辅助到深入挖掘、组合攻击最后回归防御与修复。CSRF作为一个“古老”但至今仍常见的漏洞其核心教训在于服务器不能无条件信任任何来自浏览器的、带有合法Cookie的请求。对于开发者而言修复CSRF漏洞在技术上并不复杂同步令牌模式几乎是标配。但更重要的是将安全思维融入开发流程的每一步在设计API时考虑HTTP方法的语义在实现表单时第一时间引入CSRF防护中间件在代码审查时检查关键操作是否有二次验证在部署上线前进行必要的安全扫描。对于安全测试人员和安全爱好者CSRF是一个绝佳的入门漏洞。它逻辑清晰利用简单但又能引申出同源策略、Cookie机制、会话管理、前后端交互等一系列Web安全基础知识。通过手动构造PoC你能更深刻地理解HTTP协议和浏览器行为。在掌握了基础之后再去挑战那些与XSS、逻辑漏洞结合的复杂场景你的Web安全实战能力会得到扎实的提升。最后记住所有安全研究的首要原则仅在合法授权和可控的环境中进行测试。靶场就是我们安全的练兵场在这里磨砺技术是为了在真实世界中更好地构建和保护我们的数字家园。