Pikachu靶场CSRF实战:GET、POST、Token三种形态与防御 下午没什么事把Pikachu靶场里CSRF模块的三个关卡从头到尾刷了一遍——CSRF(get)、CSRF(post)、CSRF(token)。这套内容网上其实到处都有但大部分教程是讲完概念、放几张截图就收工真正把“攻击者为什么要这么构造请求、服务端用什么逻辑拦住、换成真实系统该怎么防御”拆开讲透的反而很少。我这次一边实操一边抓包把每一步都验证了一遍顺便记录了不少容易踩的坑。这篇文章适合两类人一是刚接触Web安全、对CSRF还停留在“好像是跨站伪造”这个层面的新手二是准备面试或者打CTF想快速把GET、POST、Token这三种形态对比明白的选手。照着文章里的步骤走一遍你对CSRF的理解会比单纯背概念扎实很多。1. CSRF到底是什么1.1 大白话解释攻击流程CSRF全称Cross-Site Request Forgery跨站请求伪造。名字很绕但用一句话概括就是攻击者利用你已经在目标网站上“登录成功”的状态骗你的浏览器替他去干一件坏事。具体流程大概是这样的。你登录了某个网站服务器给你发了一张“票据”通常就是Cookie或者Session ID存在浏览器里。之后你每一次访问这个网站浏览器都会自动把票据带上服务器一看票据有效就认为这个请求是你本人发起。问题就出在“认为”两个字上——服务器只验证票据根本不关心这个请求是你亲手点的还是某个恶意页面在背后偷偷帮你发的。我习惯拿现实场景类比你住进了酒店前台给了你一张房卡。只要是拿着房卡来开门的人酒店就默认是房客本人。攻击者没法复制你的房卡但他可以诱导你走到一扇“看起来是门、实际是衣柜”的位置让你自己把卡刷上去房门照样开。CSRF干的就是这件事。1.2 CSRF成功需要同时满足哪几个条件不是随便发一个跨站请求就能打CSRF要成功必须有这么几个前提。目标站点存在可利用的请求并且请求参数是“可预测”的。比如修改个人信息的URL是/update.php?emailxxxphonexxx攻击者不需要知道你填了什么只要把参数结构猜对就行。用户当前处于登录态。Cookie或Session没过期浏览器愿意自动携带服务端也认可。服务端缺少有效的来源校验。没有Token、没有Referer校验、没有二次确认请求发过去就直接生效。这三个条件缺一不可。理解了这一点你就能明白CSRF是一个“攻击成本很低”的漏洞——不需要解密、不需要突破同源策略、不需要前后端代码配合攻击者只需要把URL或者表单拼对剩下的全部交给浏览器的自动携带机制去完成。1.3 为什么说CSRF是个“业务放大镜”CSRF很少单独造成特别炸裂的伤害但它经常被组合利用。比如配合XSS读取Token绕过CSRF防护配合JSON接口处理常见的POST请求配合其他逻辑漏洞把一次点击放大成资金损失或者账号接管。在OWASP的Top 10里CSRF排在行业关注的前列不是没有道理的——它本质上暴露的是“服务器是否能分辨请求发起者的真实意图”这个问题。2. Pikachu靶场搭建与环境准备2.1 两种主流的搭建方式Pikachu是一个中文的PHP靶场里面集成了SQL注入、XSS、CSRF、RCE等常见漏洞场景特别适合安全学习者练手。搭建方式常用的有两种。方式一是PHPStudy一键部署。下载PHPStudy并启动Apache和MySQL把Pikachu源码解压到Apache的WWW目录也就是类似D:\phpstudy_pro\WWW\pikachu的位置然后通过浏览器访问http://127.0.0.1/pikachu跟着页面提示完成数据库初始化修改inc/config.inc.php里的数据库账号密码就能跑起来。这种方式在Windows下最省心遇到问题了也容易定位。方式二是Docker部署。拉取现成的Pikachu镜像直接启动容器好处是环境干净、不污染本机。但实际用下来如果你所在的网络环境拉取公共镜像不稳定经常会碰到类似unexpected EOF或者连接中断的报错反而多花时间做网络方面的排查。我个人建议本地学习用PHPStudyDocker适合后面做自动化测试的时候再用。2.2 CSRF模块三个关卡的总体对比进入靶场后左侧菜单可以看到CSRF分类点开后是三个子关卡CSRF(get)、CSRF(post)、CSRF(token)。三个页面长得几乎一样都是修改个人信息的表单——性别、电话号码、住址、邮箱唯一差异是请求形式和防护逻辑。关卡请求方式防护机制利用难度核心看点CSRF(get)GET无最简单URL拼一下就能触发CSRF(post)POST无中等需要构造跨站表单CSRF(token)GET随机token校验较难必须在请求中带合法token把三个关卡放在一起做对比你很快会得出一个结论GET和POST只是请求方法的差异真正的分水岭在服务端有没有做“不可预测性”的校验。后面所有内容都围绕这个判断展开。3. CSRFGET一段URL就能改掉你的资料3.1 先抓包把请求特征摸清楚进入CSRF(get)关卡先用正常流程提交一次表单同时打开Burp Suite的代理浏览器流量挂到127.0.0.1:8080。提交之后在Burp的HTTP history里找到刚才的请求大概长这样GET /pikachu/vul/csrf/csrfget/csrf_get_login.php?sexboyphonenum123addbeijingemailtest%40test.comsubmitsubmit HTTP/1.1 Host: 127.0.0.1 Cookie: PHPSESSIDxxxxxxxxxxxxx这里有两个关键信息第一所有要修改的数据全部暴露在URL的查询字符串里参数名和参数结构一目了然。攻击者不需要知道受害者原本的数据只要能猜到这个URL格式改掉值就行。第二请求头里带着Cookie: PHPSESSID...这是浏览器自动携带的会话凭证。服务端看到这个Cookie有效就会无条件执行数据修改完全不校验这个请求是从哪里发出来的。3.2 构造恶意链接用img标签触发GET请求既然请求是GET触发方式就太多了最简单的是用img标签。把下面这段代码保存成一个HTML文件放到你自己能访问的任意服务器或者本地直接用浏览器打开html body h1今天给大家看一张图片/h1 img srchttp://127.0.0.1/pikachu/vul/csrf/csrfget/csrf_get_login.php?sexgirlphonenum666addhackeremailhacker%40hacker.comsubmitsubmit width0 height0 /body /html把这个页面地址发给已经登录Pikachu的受害者。受害者打开页面的瞬间浏览器会去加载img的src资源而加载图片本质上就是一次GET请求。由于请求的URL指向Pikachu所在域名浏览器会自动带上Pikachu的Cookie服务端收到请求后立刻修改数据。这里有个很容易忽略的技术细节img返回的是一个HTML页面图片加载会失败页面上会显示一个小图标但这完全不影响攻击效果。请求已经发出去了服务端已经处理了CSRF攻击是否成功和“图片能不能显示”没有任何关系。想验证的话开着Burp看请求历史你会看到这个GET请求一闪而过紧接着服务端返回了修改成功的页面。3.3 为什么GET型CSRF在真实环境里防不胜防因为GET请求的触发载体太多了用户点一个普通超链接地址栏直接访问。聊天工具或者邮件里发一个URL缩短链接。各种HTML标签的src属性比如img、script、iframe。甚至某些平台的自动加载机制只要内容出现在页面里就请求。整个攻击过程没有任何交互感受害者可能只是在群里点了一篇文章后台数据就被改了。对攻击者来说批量生成这种URL几乎零成本所以GET型CSRF在真实系统里最容易被业务方忽略又最容易被人利用。4. CSRFPOST换了个姿势本质没变4.1 POST请求和GET的差异到底在哪切到CSRF(post)关卡同样先正常提交一次抓包看看。POST /pikachu/vul/csrf/csrfpost/csrf_post_login.php HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Cookie: PHPSESSIDxxxxxxxxxxxxx sexboyphonenum123addbeijingemailtest%40test.comsubmitsubmit数据全部跑到请求体里了URL干干净净。这意味着前面那套img标签触发的方式直接失效——img标签只能发出GET请求它发不了POST。攻击者必须换一种思路找到浏览器里“天然支持跨域发POST”的组件。答案就是HTML表单。表单的action可以指向任意域名method可以指定为POST这是HTML原生的能力不受同源策略的约束。浏览器只会限制你“读取”跨域响应但不会禁止你“发送”跨域请求而且发送的同时还会带上Cookie。这就是POST型CSRF能成立的根本原因。4.2 构造一个自动提交的恶意表单完整代码如下html body h1欢迎参与抽奖活动/h1 form idsteal actionhttp://127.0.0.1/pikachu/vul/csrf/csrfpost/csrf_post_login.php methodPOST input typehidden namesex valuegirl input typehidden namephonenum value110 input typehidden nameadd valuebeijing-hacker input typehidden nameemail valuehackerhacker.com input typehidden namesubmit valuesubmit /form script window.onload function() { document.getElementById(steal).submit(); }; /script /body /html页面加载完成后window.onload被触发JavaScript调用form.submit()浏览器立刻向Pikachu服务器发送POST请求。由于请求背后的受害者已经登录Cookie自动携带服务端收到后直接修改数据。整套流程里受害者几乎没有感知。他看到的可能是一个抽奖页面页面一闪而过甚至跳转到了别的地方但后台的个人信息已经被改掉了。4.3 构造POST表单时容易踩的几个坑第一个坑是value里的URL编码问题。我之前见过不少人把email写成hacker%40hacker.com觉得要手动编码但实际上表单提交时浏览器会自动把表单数据编码成application/x-www-form-urlencoded格式。你只需要在value里写原始内容hackerhacker.com浏览器提交时会自动转换成hacker%40hacker.com。手动写反而可能导致服务端收到两次转义的内容。第二个坑是参数一定要完整。从Burp抓到的请求体里看到几个字段表单里就要写几个字段少了一个服务端可能报“参数缺失”或者“数据不完整”。第三个坑是action路径必须和对端完全一致。我见过有人漏掉了/vul/csrf/csrfpost/这个目录前缀导致404。所以构造恶意页面之前先在Burp里把完整的URL复制出来。第四个坑是触发时机。脚本里用window.onload而不是直接写在script标签顶部。虽然直接写顶部大部分情况也能提交但如果表单控件还没渲染完偶尔会出现提交失败。用onload可以保证DOM就绪后再执行。4.4 GET和POST型CSRF的本质结论刷完这两关你应该能理解一个很关键的事实POST一点都不比GET安全它只是让攻击者多写了一个表单而已。很多系统把“不能用GET提交数据”当作安全措施这是典型的防备错方向。在CSRF面前GET和POST都是浏览器原生能力攻击者都有办法构造区别只是手法的轻重程度。真正的安全边界在服务端不在请求方式。5. CSRFtoken服务端的安全令牌是怎么拦住攻击的5.1 正常业务流程里的token是什么进入CSRF(token)关卡页面看起来和前面差不多但多了一个隐藏字段。用浏览器查看源代码或者抓包都能看到类似这么一段input typehidden nametoken valuef5a4a5f1c3b8e2d9a1服务端在渲染这个表单的时候生成了一段随机字符串叫做token同时把这份token存进当前用户的Session里。用户提交表单时token会和其它数据一起发到服务端服务端把提交上来的token和Session里存的token做比对一致就继续执行修改不一致就拒绝。这个机制专门打击CSRF的“可预测性”。之前说的GET型、POST型CSRF之所以能打是因为攻击者能完整预测请求内容把URL或表单拼出来就给用户触发。加了token后攻击者在不知道当前用户页面token的情况下根本没法拼出合法请求。5.2 直接尝试攻击失败才是正常的你可以把第3章的img攻击手法搬到token关卡URL里不带token或者随便写一个token123456发过去。服务端会返回类似“token错误”的提示数据不会发生任何变化。也可以再进一步把自己浏览器页面里的token单独复制出来放进攻击URL里再让受害者触发。结果还是失败。原因是token是和具体会话绑定的受害者Session里的token和你自己页面里的token根本不是同一个值服务端一比对就对不上。这就是token的核心价值让“一次性猜不出、二次拿不到”的随机值出现在每个关键请求里。5.3 绕过token的几种常规思路说是绕过其实都是攻击者想方设法“偷”到token或者抓住开发实现的漏洞。常见的思路有这么几种。配合XSS读取token。如果目标页面存在XSS攻击者可以用脚本读取当前页面的DOM把隐藏字段里的token值拿到再用Ajax或者表单自动提交构造一个带合法token的CSRF请求。这也是为什么XSS和CSRF同时出现时危害会成倍增加。token不更新或者全局固定。有些系统为了省事token只在首次登录时生成一次后续一直复用甚至所有用户共用同一个token。攻击者自己注册一个账号访问一次页面把token拿下来就能批量去构造其他受害者的攻击请求。判断方法很简单刷新几次页面看token变不变再切换账号看token是否相同。token没有绑定Session。有的系统把token放在Cookie里或者放在URL参数里没有严格和当前会话绑定。如果token在URL里还可能通过Referer头泄露给第三方攻击者从Referer日志里直接拿到token。校验逻辑不严谨。比如只判断token非空、不校验有效时间、不区分大小写、只对比前几位这些开发细节上的疏忽都有可能被利用。这一节要提醒一句靶场里严格按规范实现了token直接绕是绕不过去的这也是你好好的学习机会。真正的业务系统里token的可靠程度完全取决于实现细节一定要当成一个完整的设计去对待。5.4 防御CSRF的正确姿势是组合拳做完三个关卡防御思路已经很清晰了把“请求来源可控”和“请求内容不可预测”结合起来。具体来说有这几个方向。统一使用token并且确保token是强随机、绑定会话、关键操作每次都重新校验。这是最核心的防线。敏感操作尽量用POST不是为了防CSRF而是避免被img、script这类标签无脑触发降低暴露面。设置Cookie的SameSite属性为Lax或Strict从浏览器层面拦截跨站请求携带Cookie。对高价值操作增加二次校验比如输入原密码、短信验证码、人机验证。有条件时校验Origin和Referer但要考虑兼容性和空值场景别一刀切把正常请求也拦了。防御和攻击是一体两面的。真正有效的防线从来不是单点而是层层递进让攻击者在某个环节停下来。6. 实操中遇到的问题排查与技巧6.1 常见问题速查表现象可能原因排查方法img触发后数据没变受害者未登录或Cookie已过期用Burp看请求有没有发出、响应是否跳转登录页恶意页面打开后没有任何请求脚本被拦截、页面未加载完就submit用浏览器开发者工具Network面板看请求记录POST提交后返回404action路径写错对照Burp抓包的完整路径逐字检查POST提交后提示参数错误表单字段缺失或字段名不一致逐项对比抓包请求体里的参数token提交后报错token过期、token没从受害者页面获取重新打开页面刷新token再构造请求浏览器带了Cookie仍然失败SameSite策略拦截了跨站Cookie换浏览器验证真实环境里这就是防御生效6.2 一个值得养成的排查习惯做CSRF实验翻车大部分原因不是攻击手法不对而是“你根本不知道请求有没有发出去”。所以我强烈建议全程开着Burp或者浏览器的F12开发者工具。Burp看被动请求HTTP history里能看到所有经过代理的请求包括img触发的那一条。开发者工具看网络打开Network面板勾选Preserve log刷新恶意页面就能看到页面发出的所有请求。还有一个常见的坑是浏览器多标签页导致的Session覆盖。如果你同时开着好几个Pikachu标签页后一个页面可能覆盖了前面的Session导致请求带的Cookie不是当前预期的那份。遇到登录态混乱的情况先清Cookie再重新登录一遍。6.3 一套通用的验证三步法刷完Pikachu的CSRF模块我把自己的做题套路固定成了三步先用Burp把正常请求抓全搞清楚URL、请求方法、所有参数、Cookie的收发情况。构造恶意页面或链接分别用“已登录”和“未登录”两种状态访问对比请求行为差异。修改请求里的关键参数甚至删掉防护字段重放一次看服务端到底拦不拦。这套思路不只在CSRF里好用拿去做XSS、SSRF、越权漏洞也一样适用。靶场练的从来不是“点几下发包”而是“拿到任意一个接口能通过抓包和重放判断它有没有问题”的能力。最后分享一点我自己的体会。很多人觉得CSRF是个不起眼的小漏洞不如SQL注入有冲击力也不如XSS多花样。但实际做一遍这三个关卡你会意识到CSRF是那种在真实系统里最容易因为业务逻辑疏漏而出现的漏洞。它利用的是浏览器底层的凭证携带机制不是某个函数的溢出也不是某个过滤器的配置错误所以它没法靠单一修补解决必须在设计层面把“请求不可预测”和“来源可校验”双双做到位。如果后续你还想继续往深了走我建议再看看这几个方向SameSite Cookie在不同浏览器里的真实行为、配合XSS拿到token之后的完整利用链、JSON格式下的CSRF处理方式。这几个问题吃透了你对Web安全里“请求信任边界”的理解会再上一个台阶。