尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DVWA文件上传漏洞实战:从Low到High三级防御绕过与加固

DVWA文件上传漏洞实战:从Low到High三级防御绕过与加固 1. 为什么文件上传在DVWA里值得单独跑一遍1.1 这个模块被很多人低估了我在带新人做Web安全入门的时候发现一个很普遍的现象大家搭好DVWA靶场第一件事都是直奔SQL注入和XSS因为那种“输入一段payload立刻看到反馈”的交互方式特别有成就感。文件上传模块反而经常被冷落要么随便传一张图片就关掉页面要么扫一眼源码觉得“就这几行代码有什么好看的”。但实际上文件上传是Web应用里攻击面最广、一旦出问题后果最直接的一个入口。它不是“多存了一个文件”那么简单而是“多存了一个可能被服务器解析执行的文件”。DVWA把文件上传单独列成一个练习模块并且用Low、Medium、High三个等级把三种典型的防御思路完整地演了一遍——不设防、只校验一个点、多条件联合校验。把这三级跑通你对后端文件上传表单设计的理解会比看十篇漏洞文档都来得透彻。1.2 三个等级的核心差异先看这张表在动手之前我习惯先建立一个整体认知这样后面每一步操作都知道自己在验证什么。等级校验方式盲区在哪一句话总结Low无任何校验直接保存文件任意类型、任意内容都能上传裸奔状态完全没有门Medium只检查请求头中的Content-Type请求头是客户端可控的门锁装在门框上没装在门上High检查扩展名、文件大小、服务端MIME识别不检查文件内部二次解析风险门锁装好了但窗户还开着这张表里的“一句话总结”是我自己多年工作里反复验证出来的结论。尤其是Medium那句“门锁装在门框上”——它看上去有个拦截动作但锁的钥匙却在攻击者手里这就是典型的“伪校验”。1.3 搭环境时踩过的三个坑先替你们排掉我建议直接用Docker方式搭建DVWA这是目前最省心的路径docker pull vulnerables/web-dvwa docker run -d -p 8080:80 vulnerables/web-dvwa浏览器打开http://localhost:8080默认账号密码是admin/password。登录后先把右侧的Security Level切到Low再进入File Upload菜单开始实验。但这个流程里有三个坑我几乎每次帮人排查都遇到第一个坑是数据库初始化失败。打开页面会停在Setup页面提示连接数据库失败。原因是容器里的配置文件没生成需要进容器把/var/www/html/config/config.inc.php.dist复制成config.inc.php把数据库账密填进去再重启容器。第二个坑是上传目录权限不足。上传时报错“Your image was not uploaded”但代码逻辑没问题那就是hackable/uploads目录没有写权限进容器执行chmod 777 /var/www/html/hackable/uploads就能解决。第三个坑更隐蔽我见过有人图方便直接把DVWA跑在云服务器上还映射到了公网端口。低等级下任何人都能往你的上传目录扔脚本那就不叫学习靶场了那叫给别人送肉鸡。DVWA只适合放在本机或内网隔离环境这个边界必须守死。工具方面浏览器本身够用但Medium级别的实验需要改请求头最方便的做法是准备Burp Suite用它的Proxy抓包改包。如果不想装浏览器的开发者工具也能手动构造请求但效率低很多我后面会讲。2. 低等级连校验都没有真正的问题藏在哪2.1 源码只看三行有效代码把等级切成Low点开View Source看PHP代码核心逻辑短得惊人?php if( isset( $_POST[ Upload ] ) ) { $target_path DVWA_WEB_PAGE_TO_ROOT . hackable/uploads/; $target_path . basename( $_FILES[ uploaded ][ name ] ); if( !move_uploaded_file( $_FILES[ uploaded ][ tmp_name ], $target_path ) ) { echo preYour image was not uploaded./pre; } else { echo pre{$target_path} succesfully uploaded!/pre; } } ?整个代码里没有任何过滤、没有类型判断、没有大小限制、没有重命名。所谓的“处理”就是把HTTP上传的临时文件移动到hackable/uploads/目录下。move_uploaded_file唯一的职责是确认这个文件确实来自HTTP POST请求至于内容是什么、扩展名是什么它一概不管。2.2 实测一个PHP脚本被直接保存并执行低等级的验证目标不是“拿shell”而是搞清楚两件事第一服务器会不会接受并保存一个非图片文件第二保存后的文件能不能被当作PHP直接访问执行。我先创建了一个用于验证的最小PHP文件?php echo file-upload-test; ?文件名取为test.php在DVWA页面上传页面返回了一行路径提示../../hackable/uploads/test.php succesfully uploaded!。接着我新开标签页访问http://localhost:8080/hackable/uploads/test.php浏览器直接输出了file-upload-test。说明Apache容器已经把uploads目录下的PHP文件当作PHP脚本交给解释器执行了。很多入门者做到这一步就兴奋得不行急着传一句话木马。我建议克制一下。低等级实验最有价值的产出是总结出三条关键观察服务端没有对文件内容做任何判断、没有修改文件名、没有限制目录的执行权限。这三条缺一不可共同构成完整的漏洞链路。只要其中任何一条是安全的危害都会大幅降低。2.3 低等级在真实业务里的影子有人可能会觉得这都什么年代了还会有后端代码对上传文件完全不做检查吗还真有。我前几年做代码审计时在一个内部系统的附件上传接口里就见过几乎一模一样的实现唯一区别是换成了Java语言。当时的开发理由是“这个接口只供内部人员使用”但内部系统被人拿到一个上传点基本等于内网沦陷。历史漏洞库里大量“任意文件上传”事件根因就是这么朴素——服务端无条件信任了用户提交的文件。所以低等级存在的意义不是教你一个攻击手法而是让你亲眼看见当程序对“外部输入”不设任何防线时攻击成本可以低到一次普通点击那么低。2.4 如果只修一个点先修什么低等级的加固非常明确白名单后缀校验。只允许.jpg、.jpeg、.png、.gif这类明确的白名单扩展名其它一律拒绝。注意这里说的是一律拒绝而不是“把不认识的扩展名改掉”或者“记录警告”。黑名单永远有绕过空间因为攻击者总能找到一个你没想到的扩展名白名单才是真正可闭环的防御。更进一步的加固是把上传目录的脚本执行权限彻底关掉在Apache里配置php_admin_flag engine off或者在Nginx里让该目录的PHP请求全部返回404。这样即使有人在文件内容里塞了恶意脚本服务器也没有执行它的入口。低等级教训就一句话不要信任任何客户端送过来的内容校验必须发生在本不该发生的时候。数据到达服务端的那一刻就要假设它是恶意的。3. 中等级用Content-Type做了一道门可门却没有锁3.1 看源码它到底加了哪几个条件把DVWA等级切成Medium再回来看源码?php if( isset( $_POST[ Upload ] ) ) { $target_path DVWA_WEB_PAGE_TO_ROOT . hackable/uploads/; $target_path . basename( $_FILES[ uploaded ][ name ] ); $uploaded_name $_FILES[ uploaded ][ name ]; $uploaded_type $_FILES[ uploaded ][ type ]; $uploaded_size $_FILES[ uploaded ][ size ]; if( ( $uploaded_type image/jpeg ) ( $uploaded_size 100000 ) ) { if( !move_uploaded_file( $_FILES[ uploaded ][ tmp_name ], $target_path ) ) { echo preYour image was not uploaded./pre; } else { echo pre{$target_path} succesfully uploaded!/pre; } } else { echo preYour image was not uploaded./pre; } } ?中等级比低等级多了两个条件上传类型必须是image/jpeg文件大小必须小于100000字节。但这里有一个致命的细节——它检查的$uploaded_type来自$_FILES[uploaded][type]而这个值是PHP从浏览器提交的HTTP头部里直接取出来的。换句话说服务端确实做了检查但检查的数据来源是客户端说了算的请求头。这不是真正的服务端校验而是“让客户端自己报告自己的身份”。3.2 直接上传会被拦但绕过只需要改动一个字段我先按正常路径上传一份内容为?php echo medium; ?的test.php页面提示“Your image was not uploaded”说明筛选条件确实生效了。但绕过这一步非常简单。用Burp Suite开启代理重新上传抓到POST请求后找到这一行Content-Type: application/x-php改成Content-Type: image/jpeg放行。页面立刻提示上传成功访问/hackable/uploads/test.phpPHP代码正常执行。整个过程不需要改文件内容、不需要改扩展名、不需要混淆编码只动了HTTP头部里的一个字符串。这就叫“钥匙就放在锁旁边”。3.3 不带Burp怎么办给一条备用路径我在帮朋友远程排查时经常遇到一端没有Burp环境的情况。其实浏览器开发者工具可以应急方法不复杂先把请求在Network面板里复制为cURL命令然后用curl手动指定Content-Type重放。比如curl -X POST http://localhost:8080/dvwa/vulnerabilities/upload/ \ -b PHPSESSID你的会话ID; securitymedium \ -F uploadedtest.php;typeimage/jpeg \ -F UploadUpload注意-F参数里可以显式指定typeimage/jpegcurl构造multipart包时会把Content-Type按你指定的值提交。这种方式不用额外装任何工具。但我仍然建议把Burp Suite装上。抓包改包不是只在这一关用后面遇文件包含、命令注入、越权排查几乎全靠它。趁早把工具链磨熟比死记几个payload有价值得多。3.4 中等级最值得反思的一点中等级给我的感受是“增加了拦截面但没有增加安全护栏”。它确实能拦住“老老实实用浏览器上传PHP文件”的普通用户但对任何懂一点HTTP协议的人来说这个校验形同虚设。我后来在真实代码审计里看到一个接口只检查Content-Type和文件大小然后直接保存并通过内网请求访问迅速意识到这里有同样的问题。但凡你在一段文件上传代码里看到$_FILES[xxx][type]就应该立刻提高警觉——这个值是直接取请求头的完全可控。3.5 中等级的正确修法如果让我来修补中等这个状态我会做三件事按顺序执行后端用finfo_file读取临时文件的真实类型不再信任任何客户端声明扩展名白名单只允许.jpg和.jpeg并且全部转成小写比较真实MIME与扩展名必须匹配比如image/jpeg只能对应.jpg或.jpeg。这三条同时满足才算把“服务端校验”这四个字落到实处。否则哪怕多写几百行代码只要最终依据的是请求头数据都等于没有。4. 高等级确实认真校验了但拦截逻辑仍有边角4.1 高等级源码到底加了哪些判断切到High源码逻辑明显丰富起来?php if( isset( $_POST[ Upload ] ) ) { $target_path DVWA_WEB_PAGE_TO_ROOT . hackable/uploads/; $target_path . basename( $_FILES[ uploaded ][ name ] ); $uploaded_name $_FILES[ uploaded ][ name ]; $uploaded_ext substr( $uploaded_name, strrpos( $uploaded_name, . ) 1); $uploaded_size $_FILES[ uploaded ][ size ]; $uploaded_tmp $_FILES[ uploaded ][ tmp_name ]; if( ( strtolower( $uploaded_ext ) jpg ) || ( strtolower( $uploaded_ext ) jpeg ) ) { if( $uploaded_size 100000 ) { $finfo finfo_open( FILEINFO_MIME_TYPE ); $type finfo_file( $finfo, $uploaded_tmp ); finfo_close( $finfo ); if( $type image/jpeg ) { if( move_uploaded_file( $_FILES[ uploaded ][ tmp_name ], $target_path ) ) { ... } } } } } ?高等级的变化可以概括为四点扩展名白名单只接受jpg和jpeg并且用strtolower统一成小写堵住.JPG之类的棱角文件大小限制保持在100KB以下用finfo_open(FILEINFO_MIME_TYPE)读取文件的真实MIME类型不再依赖客户端请求头真实MIME必须是image/jpeg。直接改扩展名、改请求头在高等级面前都行不通了因为服务端会读文件二进制内容去判断到底是不是JPEG。4.2 绕过高等级的一条完整路径高等级依然有绕过的逻辑空间因为image/jpeg只说明文件头部符合JPEG格式并不等于文件每个字节都必须被图像解码器消费完。JPEG文件结构里允许存在附属数据段很多图像解析器遇到不认识的段会跳过但PHP解释器遇到?php ... ?却不一定会跳过。这就引出经典的“图片马”手法。先在本地准备一张正常JPEG图片shell.jpg再把PHP验证代码追加到图片末尾。Windows下的做法copy /b shell.jpg payload.txt payload.jpgLinux下更常见cat logo.jpg payload.php payload.jpg上传payload.jpg扩展名是jpgfinfo_file识别出来的还是image/jpeg服务端正常放行。但这里有一个非常关键的技术细节也是我最初反复踩坑的地方直接访问/hackable/uploads/payload.jpg浏览器只会把它当图片输出并不会执行里面的PHP。因为Apache解析脚本的依据是扩展名.jpg不是可执行脚本类型。真正的绕过链条通常是“文件上传”配合“文件包含”——当站点里存在一个动态包含点比如?pagexxx攻击者把包含路径指向上传目录里的这个jpgPHP解释器在include读取时才会把文件内容当作PHP代码解析并执行。也就是说高等级把“上传”这一步的漏洞几乎堵平了但“上传”和“执行”是两个不同的环节。如果服务器上还有其他入口能导致文件内容被二次解析攻击链还是能被接上。4.3 我实测高等级时的切身体会我印象最深的一次是自己在本地验证整条链路上传完payload.jpg后直接访问uploads目录看到的是乱码和图片残影一点PHP输出的迹象都没有。当时第一反应是合并命令用错了反复试了几次都一样。后来沉下心去查了Apache的PHP解析规则才意识到问题出在自己对“执行条件”的假设错了。这种“看似失败、实则需要换一个触发入口”的经历比直接看到命令执行成功更有价值。它提醒我漏洞利用从来不是单点事件而是多条路径、多个组件叠加的结果。安全测试最忌讳的就是一条路走不通就慌要习惯画链路图把“上传”“存储”“访问”“解析”拆成独立节点逐个排查。4.4 高等级在真实业务里是什么强度平心而论高等级的水平已经超过很多真实业务系统了。现实中大量应用至今仍停留在“只做扩展名校验”甚至“什么都不做”的状态这一点做代码审计时屡见不鲜。但高等级距离真正的安全仍有距离它没有做到下面两件事第一它没有对上传内容进行重新编码。真正稳妥的图片上传处理会调用图像处理库把图片重新解码、缩放、剥离元数据再重新保存。这个过程的本质是让存储下来的字节流不再等于用户上传的原始字节流用户夹带的恶意载荷在重新编码过程中基本被洗掉了。第二它没有改变存储与执行的关系。高等级保存后文件名不变、目录不换、执行权限不关闭直接把文件放在Web可访问且可能被解析的目录里。如果把上传目录放到Web根目录之外或者关掉该目录的脚本执行权限整个攻击链的最后一环就被切断了。所以高等级给我的启发不是“再想一个更刁钻的绕过姿势”而是意识到安全检测只有和数据处理、架构设计绑定在一起才具备真正的防护强度。5. 三关跑完之后真正值得带走的一套加固清单5.1 从源码层面复盘三套方案跑完三个等级再回头看最清晰的是把三套方案并排放在一起比较。维度LowMediumHigh扩展名校验无无白名单只允许jpg/jpeg大小写转换MIME数据来源不校验来自客户端请求头服务端finfo读取真实内容文件大小限制无100KB100KB上传后文件名不修改不修改不修改典型绕过方式直接上传PHP抓包改Content-Type合并图片马再配合文件包含触发防御水平不存在形式大于实质有一定强度仍有间接路径这张表用来记“差异”就够了。但落到实际工作中更重要的是建立一条认知安全等级不是由校验条件的数量决定而是由数据可信度决定。低等级是不管可信度中等级是误把客户端声明当作可信数据高等级是开始从文件本质读取特征但还没有做到让数据彻底脱离用户控制。5.2 一套可以直接落地的四层防御如果你问我在实际项目里文件上传接口到底怎么设计我建议直接按这四层来不必东拼西凑第一层白名单与真实MIME双校验。文件的扩展名必须落在白名单内同时服务端必须用finfo_file读取文件的真实MIME类型两者匹配才允许通过。任何直接信任请求头的行为都应该被视为漏洞。第二层压缩重建与元数据剥离。图片上传后立刻用ImageMagick、GD等图形库重新解码、缩放到目标尺寸输出一份新图片。这一步能把藏在文件尾部或元数据区里的载荷清掉大部分而且不影响正常业务。非图片文件更简单直接走独立通道处理。第三层目录隔离与随机重命名。上传文件落到Web根目录之外或者独立域名目录关闭脚本执行权限。文件名用UUID或随机字符串重命名让攻击者无法预料文件存储路径也堵死“按名字猜地址”的路径探测。第四层存储环境治理。上传文件建议走对象存储或CDN不与企业业务域共享Cookie、Session。在一些重要场景比如允许上传文档和压缩包的接口接入反病毒扫描或沙箱检测在写入前把WebShell、宏病毒这类恶意脚本过滤掉。这四层每一层单独看都有效但真正的纵深防御要求它们组合使用。你在网上看各大厂的文件上传方案翻来覆去本质上就这三板斧要么不让它进Web可执行目录要么进去了也重新编码一遍要么执行路径直接断开。5.3 我的一点额外提醒文件上传这个知识点很多人学完DVWA就觉得自己会了。但真实业务远比武靶场复杂多文件上传、分片上传、云存储直传、签名URL过期、图片渐进加载……每一处新花样都会带来新的风险点。DVWA的价值是帮你把最核心的攻击与防御原理刻进脑子里而不是给你一套可以照搬所有场景的万能公式。最后分享一个我自己工作里的习惯凡是看到后端出现文件上传接口我不会先去判断它“能不能绕过”而是先问三个问题——用户上传的字节流是否会被原样保存存储位置是否能被Web容器解析执行文件内容是否可能在另一个入口被二次读取解析这三个问题只要有一个回答“是”这个接口就该列入重点审查名单。带着这个框架去重看DVWA的三个等级你会有完全不一样的收获。
返回列表