
1. 从“能改密码”到“能改你密码”CSRF漏洞的核心与DVWA靶场价值CSRF跨站请求伪造漏洞是Web安全里一个看似简单、实则极易被低估的威胁。它不直接窃取你的密码而是利用你已经登录的“身份”在你不知情的情况下让浏览器代替你向网站发送一个恶意请求。比如攻击者可以伪造一个“修改密码”或“转账”的请求只要你登录了目标网站并访问了恶意页面这个请求就会被自动执行。很多开发者会疑惑“我的登录有Session有Cookie为什么还会中招” 这正是CSRF的狡猾之处——它利用的是浏览器会自动携带Cookie发起请求的机制绕过了对用户身份的“意图”验证。DVWADamn Vulnerable Web Application靶场是学习和复现这类Web漏洞的绝佳环境。它把漏洞“封装”在一个可控的、安全的本地环境里让你能亲手从攻击者视角构造攻击再从开发者视角实施修复形成完整的攻防认知闭环。对于CSRFDVWA提供了从低到高不同安全等级的关卡非常适合用来理解漏洞原理、掌握攻击手法、并实践最有效的防护措施。这篇文章我会带你手把手在DVWA里复现CSRF漏洞。我们不只讲“怎么攻击”更重点拆解“为什么能攻击”以及“如何从根上修复”。我会把整个流程拆解成环境准备、漏洞原理分析、低中高各级别攻击复现、以及对应的Token验证、Referer检查、SameSite属性等防护方案的代码级实现。即使你之前没搭过靶场跟着步骤也能跑通。2. 搭建你的“安全实验室”DVWA环境准备与配置在开始“攻防”之前你得先有一个安全的实验场。DVWA的搭建过程本身就是一次很好的Web应用部署练习。2.1 基础环境选择与安装DVWA是一个PHP/MySQL应用所以你需要一个支持PHP和MySQL的Web服务器环境。对于新手最省事的方法是使用集成环境包Windows/Mac用户推荐使用XAMPP或PHPStudy。它们一键安装Apache、PHP、MySQL管理界面友好。Linux用户可以通过包管理器如aptfor Ubuntu/Debian,yumfor CentOS分别安装apache2或nginx、php、mysql-server和php-mysql扩展。这里以XAMPP为例演示通用步骤下载并安装XAMPP从官网下载对应你操作系统的版本按向导安装。建议安装路径不要有中文和空格。启动服务打开XAMPP控制面板启动Apache和MySQL服务。看到端口旁亮起绿灯表示服务已运行。下载DVWA从DVWA的GitHub仓库搜索“DVWA GitHub”下载ZIP包解压后将整个dvwa文件夹复制到XAMPP的htdocs目录下例如C:\xampp\htdocs\。访问DVWA打开浏览器访问http://localhost/dvwa/。如果看到DVWA的安装引导页面说明Web服务已就绪。2.2 数据库配置与安全等级设置访问http://localhost/dvwa/setup.php这是DVWA的配置页面。你需要解决两个常见问题数据库连接错误页面可能会提示“数据库连接失败”。这是因为DVWA默认的数据库配置在config/config.inc.php里可能不对。你需要修改这个文件。找到$_DVWA[ db_server ]、$_DVWA[ db_user ]、$_DVWA[ db_password ]等配置项。对于XAMPP通常用户是root密码为空。修改后保存刷新setup.php页面。创建数据库在setup.php页面底部点击“Create / Reset Database”按钮。DVWA会自动创建所需的数据库和表。成功后页面会跳转到登录页http://localhost/dvwa/login.php。默认登录凭证是用户名admin密码password。登录后在左侧菜单找到“DVWA Security”。这里你可以设置漏洞的安全等级Low毫无防护用于理解漏洞最原始形态。Medium有基础但可绕过的防护适合学习绕过技巧。High有较强的防护通常需要结合其他漏洞或更精巧的利用。Impossible理论上已修复的版本用于学习最佳实践。我建议你从 Low 等级开始一步步提升对比攻击手法的变化和防护的增强。每次修改安全等级后部分挑战可能需要重新登录或重置数据库。3. 解剖CSRF在DVWA Low等级下发起一次“改密”攻击现在我们进入正题。在DVWA左侧菜单选择“CSRF”并将安全等级设置为Low。你会看到一个简单的修改密码表单需要输入新密码和确认密码。3.1 理解攻击原理请求是如何被伪造的我们先正常操作一次。输入新密码比如newpassword123点击“Change”。用浏览器的开发者工具F12查看Network网络标签页找到这个请求。你会发现这是一个GET请求URL类似于http://localhost/dvwa/vulnerabilities/csrf/?password_newnewpassword123password_confnewpassword123ChangeChange关键点来了请求类型是GET这意味着所有参数都暴露在URL里。没有身份验证令牌请求体或URL中没有类似tokenxxxxxx的随机值。依赖Session Cookie浏览器发起这个请求时会自动带上你登录DVWA的会话Cookie。攻击者的思路就是构造一个包含恶意参数的URL然后诱骗已登录的用户去访问这个URL。因为浏览器会带上Cookie服务器看到有效的Cookie就认为这是用户的合法操作从而执行修改密码。3.2 手把手构造攻击页面攻击者不需要黑进服务器他只需要让你访问一个他控制的网页。这个网页可以隐藏地发起那个修改密码的请求。创建一个简单的HTML文件命名为csrf_attack.html内容如下!DOCTYPE html html head title看起来人畜无害的页面/title /head body h1快来点击这个有趣的链接/h1 !-- 方法1: 图片标签自动加载 -- img srchttp://localhost/dvwa/vulnerabilities/csrf/?password_newhackedpassword_confhackedChangeChange width0 height0 border0 / p上面有一个看不见的图片其实已经发出了改密请求/p !-- 方法2: 诱导点击的链接 -- a hrefhttp://localhost/dvwa/vulnerabilities/csrf/?password_newhackedpassword_confhackedChangeChange 点击抽奖 /a !-- 方法3: 页面加载后自动提交表单 (更隐蔽) -- iframe namehiddenFrame styledisplay:none;/iframe form idmaliciousForm actionhttp://localhost/dvwa/vulnerabilities/csrf/ methodGET targethiddenFrame input typehidden namepassword_new valuehacked input typehidden namepassword_conf valuehacked input typehidden nameChange valueChange /form script document.getElementById(maliciousForm).submit(); /script /body /html解释一下这三种方式图片标签img src恶意URL浏览器加载图片时会自动发起GET请求。将宽高设为0用户无感知。诱导链接将恶意URL做成一个超链接诱骗用户点击。隐藏表单自动提交创建一个不可见的表单或放在隐藏的iframe里用JavaScript在页面加载后自动提交。这是最常用、最隐蔽的方式对POST请求同样有效。现在进行攻击复现确保你已登录DVWALow安全等级下的CSRF页面。在浏览器新标签页中打开你刚创建的csrf_attack.html文件可以直接拖入浏览器或用file://路径访问。页面加载后迅速回到DVWA的CSRF页面尝试再次修改密码。你很可能会看到“Password Changed.”的提示但用的却是攻击者设定的密码hacked。或者直接尝试用admin/hacked重新登录DVWA。成功了这就是一次最基础的CSRF攻击。它利用了GET请求的可预测性浏览器自动携带Cookie服务端缺乏二次验证。注意在真实环境中攻击者的页面会放在他的服务器上例如http://evil.com/attack.html然后通过钓鱼邮件、论坛发帖等方式传播链接。DVWA在本地所以我们用本地文件模拟。4. 中级防护与绕过当服务端开始检查Referer在DVWA中将安全等级调到Medium然后刷新CSRF页面。你会发现功能没变但后端代码已经增加了防护。查看源码DVWA通常提供“View Source”按钮你会发现类似这样的逻辑以PHP示例// 检查 HTTP Referer 头部是否来自本站点 if( stripos( $_SERVER[ HTTP_REFERER ] , $_SERVER[ SERVER_NAME ] ) false ) { // 请求可能来自外部拒绝执行 echo “preReferer check failed. Request denied./pre”; exit; } // ... 执行修改密码操作 ...4.1 Referer检查的原理与局限Referer或正确的拼写Referrer是HTTP请求头的一个字段它告诉服务器这个请求是从哪个页面链接过来的。Medium等级的策略是只处理来自同域名localhost下的请求。这确实能防御直接从evil.com发起的攻击。但是Referer检查有多个可被绕过的点Referer可能为空或被篡改一些浏览器插件、安全软件或用户配置会禁止发送Referer。此外从本地文件file://、HTTPS跳到HTTP等场景Referer也可能为空或不被发送。如果服务器在Referer为空时默认放行那就产生了漏洞。利用子域名或近似域名如果检查不严格例如只用stripos查找子字符串攻击者可以注册一个像localhost.attacker.com或attackerlocalhost.com的域名来绕过。利用服务端漏洞如果网站本身存在某些开放重定向或XSS漏洞攻击者可以构造一个“站内跳转”的链接使Referer看起来是合法的。4.2 在DVWA Medium等级下的绕过实践DVWA Medium等级的Referer检查实现得相对宽松。我们可以尝试以下方法绕过方法A利用空Referer如果允许有些实现会在Referer不存在时跳过检查。我们可以构造一个从data:URL 或javascript:URL 发起的请求这些来源通常没有Referer。例如将攻击页面中的表单提交动作改为通过一个没有Referer的窗口触发这需要更复杂的JS构造在本地文件环境下可能受限。方法B更常见的实践——寻找不检查Referer的“漏洞点”在真实渗透测试中我们不会死磕一个点。如果修改密码有Referer检查我们会去测试网站的其他功能点修改邮箱、发表评论、点赞、转账等。不是所有接口都做了同等强度的防护。很可能“修改密码”做了检查但“修改个人信息”的接口忘了做。攻击链就转移了。在DVWA的Medium等级下为了教学目的它可能故意留了一个“后门”比如对某些特定参数或请求方式检查不严。但更重要的是理解这种“防护不一致”的思维。在实际修复时必须对所有敏感操作状态变更进行防护而不是挑几个做。我个人的经验是单纯依赖Referer防护是不够的。它更像一道辅助防线而不是主防线。因为它的可控性不在服务端手中而在客户端和网络环境手中。5. 构建真正有效的防线Token验证与SameSite Cookie要真正防御CSRF必须在服务端引入一个攻击者无法预测、无法伪造的凭证。这就是Anti-CSRF Token反CSRF令牌的理念。同时结合客户端的SameSite Cookie属性可以构建纵深防御。5.1 Token验证机制的实现对应DVWA High/Impossible等级在DVWAHigh安全等级下查看CSRF页面的源码你会看到类似如下结构前端表单生成时PHP示例?php // 生成一个随机Token并存入用户Session $token bin2hex( random_bytes(32) ); $_SESSION[csrf_token] $token; ? form action# methodPOST input typehidden nameuser_token value?php echo $token; ? !-- 其他表单字段 -- input typepassword namepassword_new input typepassword namepassword_conf input typesubmit nameChange valueChange /form后端处理请求时PHP示例if( $_SERVER[ REQUEST_METHOD ] POST ) { // 检查Token是否存在且匹配 $user_token $_POST[user_token] ?? ; $session_token $_SESSION[csrf_token] ?? ; if( !hash_equals( $session_token, $user_token ) ) { // Token验证失败拒绝请求 echo “preCSRF token validation failed. Request denied./pre”; exit; } // Token验证通过执行敏感操作 // ... 修改密码 ... // 操作完成后使当前Token失效一次性使用 unset($_SESSION[csrf_token]); }这个机制为什么有效随机性与不可预测性Token是服务器为每个会话、甚至每个表单随机生成的长字符串攻击者无法提前获知。绑定会话Token存储在服务器的Session中与当前登录用户绑定。攻击者即使拿到另一个用户的Token也无法用于他自己的会话。同源策略保护由于浏览器的同源策略Same-origin policy攻击者页面evil.com的JavaScript无法读取目标网站localhost页面中的Token值。因此他无法在伪造的请求中携带正确的Token。一次性使用可选但推荐重要操作如支付、改密的Token应在验证后立即失效防止重放攻击。在DVWAImpossible等级除了使用Token还会要求用户输入当前密码进行二次验证。这是“已知秘密”验证进一步提升了安全性即使Token因某种原因泄露如XSS漏洞没有当前密码也无法完成操作。5.2 SameSite Cookie属性的作用这是从客户端浏览器层面加固的防线。你可以通过设置Cookie的SameSite属性来告诉浏览器“这个Cookie只能在同站请求中发送”。SameSiteStrict最严格。Cookie仅在同站请求即当前网站顶级域名相同中发送。这意味着从evil.com发往localhost的请求浏览器绝不会携带设置为Strict的Cookie。彻底杜绝CSRF但可能影响用户体验例如从邮件链接点回网站登录状态会丢失。SameSiteLax默认值宽松模式。在跨站请求中仅对安全HTTPS的顶级导航如点击链接发送Cookie而对子资源请求如图片、脚本、AJAX不发送。这能阻止大多数CSRF攻击因为CSRF通常通过表单POST或GET图片发起同时保持了主要导航场景的用户体验。SameSiteNone必须与Secure属性仅HTTPS一同使用。Cookie在所有上下文中都会发送。这是旧版浏览器的行为也是CSRF漏洞的根源之一。如何设置在服务端设置Cookie时例如PHP的setcookie函数或Web框架的中间件setcookie(‘session_id’, $sessionId, [ ‘expires’ time() 3600, ‘path’ ‘/’, ‘domain’ ‘localhost’, ‘secure’ true, // 仅HTTPS ‘httponly’ true, // 禁止JS访问 ‘samesite’ ‘Lax’ // 或 ‘Strict’ ]);将SameSite设为Lax或Strict是防御CSRF非常有效且低成本的一环可以作为Token验证机制的有力补充。6. 从攻到防修复方案总结与实战检查清单经过前面的攻击复现和原理分析我们现在从防御者角度总结一套完整的CSRF防护方案。6.1 分层防护策略最安全的做法是实施“纵深防御”不依赖单一措施首选且必需Anti-CSRF Token为每个用户会话生成唯一、高强度的随机Token。在渲染表单时将Token放入隐藏域input type“hidden”。在处理请求时校验提交的Token是否与Session中存储的一致。对重要操作使用一次性Token用后即废。确保Token与用户会话绑定避免全局通用Token。重要补充SameSite Cookie属性为会话Cookie设置SameSiteLax或Strict。这是现代浏览器的推荐做法能自动拦截大量跨站请求。如果网站需要跨站使用Cookie例如嵌入第三方则必须使用SameSiteNone; Secure并务必配合CSRF Token使用。辅助验证检查Referer/Origin头部作为第二道或第三道防线可以验证请求的Origin或Referer头部是否来自可信域名。Origin头部比Referer更可靠它不会包含完整路径且在某些场景下如302重定向仍会发送。注意需要处理这些头部为空的情况制定安全的默认策略如“空则拒绝”。业务层面二次确认对于极其敏感的操作如转账、修改核心账户信息要求用户进行二次验证例如输入当前密码。输入手机/邮箱验证码。使用生物识别指纹、面部。这不仅能防CSRF也能防账户被盗后的恶意操作。6.2 开发者自查清单在开发或代码审计时可以对照以下清单检查CSRF防护是否到位检查项是/否说明与操作1. 敏感操作识别是否梳理了所有会导致状态变更的端点(POST/PUT/DELETE/PATCH以及部分GET)2. Token生成与存储是否使用密码学安全的随机数生成器生成TokenToken是否与用户会话绑定3. Token传递与校验前端表单是否包含Token隐藏域后端是否在业务逻辑前校验Token校验失败是否立即终止并日志记录4. Token一次性使用对于支付、改密等核心操作Token是否在用后立即失效5. Cookie属性设置会话Cookie是否设置了HttpOnly、Secure(HTTPS下) 和SameSiteLax/Strict6. 验证请求方法是否遵循RESTful规范对状态变更操作使用POST等非GET方法这不能防CSRF但是好习惯7. 框架内置防护如果使用Spring Security、Django、Laravel等框架是否启用了其内置的CSRF防护中间件8. 第三方依赖检查引用的第三方库/组件是否可能引入CSRF风险如旧版jQuery插件9. 异常处理Token校验失败或Referer检查失败时返回的HTTP状态码是否是403 Forbidden等明确拒绝状态10. 安全测试是否在测试环节包含CSRF漏洞扫描或手工测试6.3 遇到“Token验证失败”的排查思路在实际开发或维护中如果配置了Token却出现“Token验证失败”的错误可以按以下顺序排查检查Session状态用户会话是否有效是否在生成Token和校验Token之间Session丢失或过期了例如服务器重启、Session存储配置问题。检查Token传递前端表单的隐藏域名称如name“user_token”和后端获取参数的名称如$_POST[‘user_token’]是否一致如果是AJAX请求Token是否被正确添加到请求头如X-CSRF-TOKEN或请求体中检查页面缓存如果表单页面被浏览器或CDN缓存可能导致多个用户收到同一个Token。确保生成Token的动态页面不被缓存。检查多标签/多窗口操作用户在A标签页打开表单在B标签页登录/注销再回到A标签页提交可能导致Session或Token不一致。可以考虑为每个表单生成唯一Token并管理一个Token池。检查框架配置如果使用框架检查CSRF中间件是否已正确启用排除路径如API接口设置是否正确。CSRF的防护核心思想是“验证请求的意图是否真正来自用户”。Token是服务器给用户的一个“一次性口令”而SameSite Cookie是浏览器帮用户把守的“关卡”。两者结合再辅以良好的开发习惯和安全意识就能有效筑起防线。通过DVWA从Low到Impossible等级的实战你应该能清晰地感受到安全是一个持续的过程从毫无防护到简单防护被绕过再到构建起稳固的防御体系。理解攻击是为了更好地防御。