☰
DVWA靶场CSRF漏洞通关实战:从Low到High的完整攻击与防御指南
2026/10/20 15:41:53 网站建设 项目流程

CSRF这名字听着玄乎,其实就是“跨站请求伪造”。很多新手学Web安全,第一个正经练手的东西就是DVWA靶场里的CSRF模块。这个靶场把漏洞难度分成了Low、Medium、High三档,一档比一档更贴近真实站点的情况。我当年在本地环境里从Low一路打到High,踩了不少坑,也搞明白了很多文档里根本不会写清楚的细节。这篇文章就按我自己通关的路径来写,会把每一步细化到“点哪个按钮、改哪个参数、为什么非要这么改”,同时把BurpSuite在CSRF测试里的用法也揉进实战过程里,保证你看完不只是会点个链接,而是能真正说清楚CSRF为什么会成功、不同难度卡点在哪儿、真实系统里又该怎么防。

1. 环境准备:先把DVWA靶场跑起来

1.1 本地搭建DVWA环境

DVWA是一个开源的漏洞演练平台,装好在本地跑起来就是一个自带各种漏洞的网站。学习阶段一定要在本地环境操作,千万别拿别人的线上站点练手,未经授权的测试是违法的。

安装方式我推荐最省事的一键环境包,比如XAMPP或phpStudy这类PHP+MySQL集成环境。以XAMPP为例,大致流程:

  1. 下载并安装XAMPP,启动Apache和MySQL两个服务。
  2. 打开htdocs目录(默认在安装目录下),把DVWA源码解压进去,目录名建议保持dvwa。
  3. 进入dvwa/config目录,找到config.inc.php.dist文件,复制一份并改名为config.inc.php。
  4. 编辑config.inc.php,把数据库账号密码改成你本地MySQL的账号密码,比如用户root、密码留空或者你自己设置的密码。
  5. 浏览器访问http://127.0.0.1/dvwa/setup.php,点击Create / Reset Database按钮。
  6. 跳转到登录页,默认账号是admin,密码是password。

这里有一个很容易踩的坑:老版本DVWA对PHP版本非常敏感,在PHP 8.0以上的环境里偶尔会有提示或页面白屏。如果遇到,优先换用官方较新的版本,或者把PHP版本降到7.x。另外,如果80端口被别的程序占用,Apache的监听端口会变成其他值,那访问地址里的端口也要对应改。

1.2 登录后先设置安全级别

DVWA登录进去后,左侧菜单能看到所有漏洞模块。在开始CSRF之前,先到DVWA Security页面,把Security Level设置成你要练习的难度。下拉框里有Low、Medium、High、Impossible四档,选完点Submit。

设置好难度后,整个靶场所有漏洞模块都会按这个难度运行。也就是说,你做CSRF模块的时候,XSS模块也会同步变成当前难度,这一点到了High难度通关时要利用的。

还要记住一个要点:DVWA的登录状态是靠一个名为PHPSESSID的Cookie维持的。后面所有CSRF攻击之所以能成功,都是建立在受害者已经登录DVWA、浏览器带着这个Cookie的前提下。你说它完全等同于真实站点里的会话机制,其实不算夸张,理解这个点,后面看原理就顺了。

2. CSRF漏洞核心原理:为什么浏览器会“替你”发请求

2.1 从一次改密码请求说起

在DVWA里,CSRF模块的功能是“修改当前登录用户的密码”。Low难度下,你会看到一个输入新密码、确认新密码的页面,提交后密码就变了。

我们先用浏览器正常走一遍改密码流程,打开开发者工具或者用BurpSuite抓包,能看到请求其实是这样的:

GET /dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change HTTP/1.1 Host: 127.0.0.1 Cookie: PHPSESSID=abcdef123456

关键点在于:这个修改密码的操作,完全没有经过任何确认机制,也没有任何随机token,只要请求到达服务器,服务器就认为是当前登录用户本人在操作。那服务器怎么知道“当前用户”是谁?全靠Cookie里的PHPSESSID。而浏览器在发起请求时,会自动带上属于该站点的Cookie,不需要用户手动操作。

2.2 漏洞核心:服务端分不清“主动”与“被动”

用一个生活化的类比来解释。你家门禁卡感应到有人刷卡就开门,它不关心刷卡的人是你本人,还是有人从你家窗户伸了一根杆子、拿着你塞在包里露出来的门禁卡去刷。CSRF就是这种情况——服务器只认“有效会话”,不认“这个请求是不是用户主动发起的”。

攻击者要做的事情很简单:构造一个指向DVWA改密码接口的请求,然后想办法让登录了DVWA的用户触发它。最原始的办法就是发一个链接,诱导用户去点击。用户在已登录状态下点开这个链接,浏览器发起GET请求,自动带上PHPSESSID Cookie,服务器看到有效会话,直接执行改密,攻击者就拿到了密码。

2.3 Cookie与SameSite:为什么GET型CSRF尤其危险

现代浏览器给Cookie加了一个SameSite属性,用来限制跨站请求是否携带Cookie。Chrome从某一版本开始默认把没有设置SameSite的Cookie当作Lax处理。Lax模式下,跨站请求只有在“顶层导航”且使用GET方法时才会携带Cookie;像img标签加载、ajax请求这类跨站子资源请求,默认不会再带Cookie了。

这也就解释了一个现实问题:DVWA Low级别的改密码接口是GET请求,攻击者用“新窗口打开链接”或者“页面跳转”这种顶层导航方式触发,浏览器在Lax模式下依然会带上会话Cookie,所以CSRF照样能成功。但如果目标接口改成POST请求,用普通的表单或Ajax去跨站提交,Lax模式下的Cookie就不会带上,攻击就失败了。这也是为什么很多安全书籍强调“敏感操作不用GET、加上随机token”的根本原因。

3. Low难度通关:一个URL就够了

3.1 分析Low级源码逻辑

DVWA Low级别的CSRF源码非常直白,大致是这样的:

if (isset($_GET['Change'])) { $pass_new = $_GET['password_new']; $pass_conf = $_GET['password_conf']; if ($pass_new == $pass_conf) { // 直接执行更新密码SQL } }

没有校验来源,没有校验token,甚至不限制请求方法,只要收到参数就把密码改了。这个难度对应的就是早期很多网站真实存在过的写法,也正是因为过于简单,特别适合入门理解CSRF的攻击链。

3.2 攻击者视角:构造恶意链接

既然服务器只认参数,我们直接在浏览器地址栏拼一个完整请求就行:

http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=admin888&password_conf=admin888&Change=Change

这个URL里面包含了新密码和Change提交参数。只要登录用户点击这个链接,密码就会变成admin888。

实际操作里,攻击者通常是把这个链接伪装成正常的网页内容,比如做成“点击领取优惠券”的文字超链接,或者直接放到HTML页面的script跳转里。在本地模拟的时候,你可以在另一个浏览器窗口里打开这个URL,也可以做一个简单的HTML页面:

<a href="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=admin888&password_conf=admin888&Change=Change">点击查看优惠信息</a>

3.3 受害者视角:模拟一次真实攻击

我自己练习时的操作方式是这样的:

  1. 用A浏览器登录DVWA,正常访问CSRF模块,确认当前密码是初始的password。
  2. 在B浏览器(或者隐身窗口)里打开攻击者页面,这个页面里放着伪装链接。
  3. 模拟受害者在A浏览器里点击链接,地址栏跳转到DVWA的CSRF页面,页面显示“密码已修改”。
  4. 回到DVWA登录页,用旧密码password登录会失败,用admin888登录则成功。

这里需要注意一个细节:一定要用同一个浏览器登录过DVWA,再在同一个浏览器里点击恶意链接。因为浏览器对站点Cookie是隔离的,B浏览器里没有A的登录会话,就算点击链接,服务器也只会提示未登录或直接跳转登录页。很多人测试没反应,九成都是忘了这一条。

3.4 自动化PoC:用BurpSuite生成CSRF攻击页面

BurpSuite在抓包分析里有个很方便的功能——生成CSRF PoC。如果用专业版,在Proxy的HTTP History里找到刚才的改密码请求,右键选择Engagement tools下的Generate CSRF PoC,BurpSuite会自动生成一个HTML页面,里面是自动提交的表单。

生成的HTML长这样:

<html> <body> <form method="GET" action="http://127.0.0.1/dvwa/vulnerabilities/csrf/"> <input type="hidden" name="password_new" value="admin888"> <input type="hidden" name="password_conf" value="admin888"> <input type="hidden" name="Change" value="Change"> </form> <script>document.forms[0].submit();</script> </body> </html>

把这个HTML保存成本地文件丢给“受害者”打开,页面加载时表单会自动提交,请求直接打到DVWA上,密码秒改。注意,这个功能在社区版里没有,社区版用户直接手写上面这个HTML即可,效果完全一样。

4. Medium难度通关:Referer校验遇上路径伪造

4.1 看源码:服务端校验了什么

Medium难度比Low多了一层校验,源码里大致是这样:

if (stripos($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME']) !== false) { // 更新密码 } else { // 提示请求不是来自本站 }

服务器拿到请求后,会检查HTTP头里的Referer字段,看它里面是否包含目标站点的域名(SERVER_NAME)。想到的防御思路是“只要请求是从本站页面发出来的,Referer里就一定带着本站域名”。

这个思路本身不算错,但它忽略了一件事:Referer是完全由浏览器生成的,而且攻击者可以在一定程度上控制它。

4.2 为什么Referer校验不可靠

Referer的含义是“当前请求是从哪个URL跳过来的”。服务器认为,如果Referer里包含127.0.0.1,说明用户是从DVWA页面里点出来的。但攻击者的恶意页面,如果恰好URL里也包含了127.0.0.1这个字符串,是不是就能骗过校验?

对,这就是路径伪造。攻击者不需要真的控制目标站点,只需要让自己的攻击页面URL里包含目标域名即可。比如目标站点域名是127.0.0.1,那攻击页面地址可以是:

http://attacker.com/127.0.0.1/csrf.html

当浏览器从这个页面跳转到DVWA时,Referer就是http://attacker.com/127.0.0.1/csrf.html,虽然它来自攻击者服务器,但字符串里确实包含了127.0.0.1,Medium级别的校验就直接放行了。

4.3 实操:让攻击页面“带上”目标域名

具体做起来,其实就是在你自己的Web服务器上,创建一个以目标域名命名的目录,比如127.0.0.1,然后把攻击HTML放进去:

/var/www/html/127.0.0.1/csrf.html

访问地址是http://attacker.com/127.0.0.1/csrf.html。页面里放一个自动提交的GET表单,指向DVWA的改密码接口。受害者登录DVWA后访问这个页面,浏览器带着完整的Referer发出跨站请求,服务端发现Referer包含127.0.0.1,成功改密。

如果你是通过localhost访问DVWA的,那伪造目录名就应该是localhost。总之,目录名要和你在浏览器地址栏里访问DVWA时用的主机名完全一致,否则校验过不了。

4.4 新浏览器Referrer Policy带来的坑

这里有一个我反复踩过的大坑:现代浏览器默认会截断跨站请求的Referer。Firefox、Chrome在跨站请求时,默认只发送origin部分,也就是只发http://attacker.com,不带后面的/127.0.0.1/csrf.html。结果就是路径伪造明明做了,服务器那边却只看到http://attacker.com,校验失败。

解决办法是在攻击页面的HTML头里强制指定Referrer Policy:

<meta name="referrer" content="no-referrer-when-downgrade">

加上这行之后,从当前页面发出的跨站请求(HTTP到HTTP也属于同等级别,不算降级),浏览器会发送完整的Referer,不再截断成origin。这样路径伪造就能在最新浏览器里稳定生效。我练习时习惯用Firefox,并且在攻击页面统一加上这个meta标签,Medium难度基本一次过。

5. High难度通关:Token校验下的组合拳

5.1 看源码:user_token是如何工作的

到了High难度,改密码页面本身会多出一个隐藏字段user_token,源码逻辑大致是:

$token = $_SESSION['csrf_token']; if ($_GET['user_token'] == $token) { // 更新密码 }

每次访问CSRF页面时,服务器都会在页面上输出一个随机的token值,同时把这个值存进服务器的Session里。提交改密请求时,必须带上与Session中一模一样的token,否则直接报错。

这带来的改变是:攻击者无法再简单地拼一个URL让受害者点击了。因为请求里缺少那个动态token,服务器会拒掉。这就是真实站点里最常见的CSRF防御——同步令牌模式。

5.2 为什么纯CSRF很难完成

为什么说High难度里“纯CSRF”很难完成?因为攻击者拿不到受害者会话里的token。token是随机的、每次访问页面都会变化,攻击者既不能事先预测,也不能跨域读取。

要想通关,思路就必须升级:不再追求“只用一个链接打穿”,而是想办法在受害者的浏览器里执行脚本,让他自己从DVWA页面里把token取出来,再带着token发出请求。

5.3 通关思路:CSRF + 存储型XSS组合

DVWA并不是单独只给你一个CSRF模块。靶场里有个存储型XSS模块(xss_s),High难度下,如果能把一段JavaScript成功注入到XSS模块的留言里,那么受害者访问那个留言页面时,脚本就会在DVWA的同源环境下执行。这里的“同源”非常关键,因为脚本和DVWA处于同源,fetch请求能自动携带PHPSESSID Cookie,也能读取CSRF页面返回内容里的token。

完整攻击链是这样的:

  1. 攻击者登录DVWA,进入存储型XSS模块,在message留言框里提交一段JavaScript脚本。
  2. 脚本被存到数据库里,之后任何人访问这个留言页面,脚本都会执行。
  3. 受害者登录DVWA后访问XSS模块页面。
  4. 脚本自动请求CSRF页面,从响应HTML里用正则提取user_token。
  5. 脚本带着这个token、以及改密参数,向CSRF接口发起请求。
  6. 服务器校验token通过,密码被修改。

这里尤其要注意:整个组合里最重要的前提是“脚本在DVWA域内执行”。如果不先通过XSS留一个立足点,攻击者连token都拿不到,再牛的组合拳也打不出去。这也是High难度设计的真实含义——某些漏洞单看威胁有限,但一旦和同源可控点组合,破坏力就上去了。

5.4 组合脚本细节

我当时往XSS存储模块里塞的脚本类似这样:

<script> fetch('/dvwa/vulnerabilities/csrf/') .then(r => r.text()) .then(html => { let match = html.match(/user_token' value='([^']+)'/); if (!match) return; let token = match[1]; fetch('/dvwa/vulnerabilities/csrf/?password_new=hacked123&password_conf=hacked123&Change=Change&user_token=' + token); }); </script>

写脚本之前,建议先抓一次CSRF页面的响应,看清楚隐藏字段的HTML格式。不同版本的DVWA在token字段的引号、属性顺序上会有一点差异,正则匹配也要跟着调整。比如有的版本输出是name='user_token' value='xxx',有的则是value='xxx' name='user_token',正则写错就提取不到token。

如果在High难度下script标签被过滤了,可以换img标签的onerror事件:

<img src="x" onerror="/* 这里放同样的JS逻辑 */" />

DVWA不同版本在高难度下过滤逻辑略有差别,但只要存在任意一个能执行脚本的注入点,思路都一样。

5.5 High难度最现实的防御意义

High难度给我们的提示其实很直接:单纯给请求加token,确实能挡住大部分自动化的CSRF攻击。但如果站点同时存在同源XSS漏洞,攻击者可以在页面内部“借用”受害者的身份,token也就形同虚设。现在开发里最常用的防御组合是CSRF Token配合严格的CSP策略,同时把XSS漏洞收敛好,因为token能不能守住,很大程度上取决于XSS能不能被彻底关进笼子里。

6. BurpSuite进阶用法:抓包、改包、PoC一把梭

6.1 代理配置与DVWA抓包

BurpSuite是Web安全测试里绕不开的抓包工具。使用前先把浏览器代理指向127.0.0.1:8080,BurpSuite默认监听这个地址。以Firefox为例,在网络设置里配置手动代理,HTTP代理填127.0.0.1,端口填8080,然后访问DVWA,切回BurpSuite的Proxy标签页,就能看到请求。

一个实际经验:如果BurpSuite里刷不出本地DVWA的包,先检查浏览器是不是开了“忽略本地流量”之类的选项。有些浏览器对localhost访问不会走代理,解决办法是改用127.0.0.1访问,或者在代理工具里放行本地地址。

6.2 用Repeater修改并重放请求

抓包之后,我们在HTTP History里找到一条改密码请求,右键发送到Repeater。Repeater的作用是手动修改请求再重放,非常方便观察服务端的响应变化。

比如在Low难度下,把password_new和password_conf改成任意值,点击Send,服务端返回修改成功页面。在Medium难度下,删掉HTTP头里的Referer字段再发送,会发现服务端返回“来源不正确”的提示,这就能直观体会到校验逻辑的存在。在High难度下,把user_token改错再发包,服务端会返回Token Mismatch。三个难度各点一遍,你能明显感受到防御强度是逐级递增的。

6.3 生成CSRF PoC的完整流程

除了手动构造HTML,BurpSuite专业版在Proxy History里右键目标请求,选择Engagement tools下的Generate CSRF PoC,会弹出一个小窗口展示生成好的HTML。点Copy HTML,存成文件,再用浏览器打开,就能复现完整攻击。核心步骤:

  1. 选中请求,右键Generate CSRF PoC。
  2. 在弹窗里调整参数,确认action地址、方法、参数名都正确。
  3. 复制HTML到本地文件。
  4. 用已登录目标站的浏览器打开这个HTML文件。

社区版用户没有这个菜单,直接手写一个带隐藏字段的表单再自动submit,效果是一样的。

6.4 BApp扩展与自动化组合

BurpSuite的BApp商店里也有很多与CSRF相关的扩展,共同套路是扫描页面里的状态变更请求、判断是否存在token或SameSite防御,有的还能自动生成验证页面。用不用插件全看习惯,我的体会是:理解了手动流程之后,插件只是帮你提速。新手阶段最好先手动抓包、手动改HTML、手动测,把整个攻击链路亲手走过一遍,再去尝试各种自动化工具,不然很容易停留在“点按钮”层面。

7. 常见问题与排查技巧实录

7.1 常见问题速查表

现象可能原因排查方法
DVWA页面白屏PHP版本太高、缺少扩展换PHP 7.x环境,开启pdo_mysql等扩展
登录后访问CSRF页面跳回登录页会话失效、Cookie被拦截重新登录,检查浏览器是否清除Cookie
Low难度点击链接没反应URL路径不对、参数写错对照代理里的请求URL逐一检查
Medium提示“请求不是来自本站”Referer被截断、伪造目录名不匹配加meta referrer标签,确认目录名与访问地址主机名一致
High提示Token Mismatch没带token、token过期、正则提取错误重新访问CSRF页抓最新token,校准正则
BurpSuite抓不到DVWA请求代理没生效、本地流量被跳过用127.0.0.1访问,关闭忽略本地流量选项
密码改乱了不知道怎么恢复数据库重置即可访问/dvwa/setup.php,点Create / Reset Database

7.2 高频坑位逐一说透

CSRF测试里最容易让人怀疑人生的坑,第一是会话失效。DVWA的PHPSESSID在浏览器重启或长时间不操作后可能过期,攻击却一直失败。我养成的习惯是每换一个难度之前,都先去DVWA页面上刷新一次,确认自己还处于登录状态。

第二是Referer坑。Medium难度在旧教程里很流行“空Referer绕过”,但在新版DVWA和浏览器默认策略下已经不稳定,远不如“伪造路径+meta referrer”靠谱。如果你跟着网上老博客做Medium一直失败,多半就是浏览器策略变了。

第三是token正则的坑。High难度里,token字段在HTML中出现的格式和顺序可能和教程不完全一致。正确做法是用BurpSuite看一次实际响应,再对着真实HTML调整正则,而不是死抄别人的表达式。

8. 从通关到防御:CSRF的几个硬核防御姿势

通关不是为了炫技,最终要把攻防视角拉到防御端。CSRF在真实系统里的防御方案,我总结下来最核心的就这几点。

8.1 Token是首选

给每个状态变更请求都加上随机token,token绑定会话,服务端每次校验。要注意token必须足够随机,不能用时间戳、用户ID这类可预测值。生成方式最好用语言自带的加密安全随机函数,比如PHP的random_bytes。同时设置token的时效性,过期必须重新获取,不能一个token用一辈子。

8.2 SameSite和Cookie策略不能少

给会话Cookie明确设置SameSite属性。如果是纯API站点,可以考虑严格模式;日常Web应用一般用Lax。但要清醒认识到,SameSite只是浏览器侧的缓解措施,GET型顶层导航在Lax下依然可能携带Cookie,所以敏感操作不要用GET。

8.3 校验来源只能当辅助

Origin和Referer校验可以作为辅助手段,但绝不能当唯一防线,因为Referer可以被路径伪造、浏览器策略影响,Origin在某些场景下也可能缺失。安全防御讲究纵深,Token为主,源校验为辅。

8.4 关键操作二次确认

修改密码、绑定手机、转账这类高风险操作,要求用户重新输入密码或输入短信验证码。这本质上等于额外加了一层动态令牌,即使CSRF请求真的打过来了,没有用户输入的动态信息也白搭。代价是用户体验稍微受影响,换来的安全收益非常大。

8.5 最后说点个人经验

我在本地把这些难度全部亲手打了一遍之后,最大的感受是:漏洞本身不可怕,可怕的是只停留在“会点链接”的层面。你把Low到High打通的这个过程,真正学到的是理解浏览器、服务器、Cookie三者之间的信任边界——浏览器默认帮你带Cookie,服务器默认信任这个会话,这两个“默认”一旦碰上开发偷懒,漏洞就出来了。后面再做任何Web开发或者安全测试,看到改密码、改邮箱这类接口,脑子里就会自动复盘一遍:有没有token?请求方式是什么?Cookie有没有SameSite?这套反射式的判断,才是通关DVWA最有价值的收获。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询