Upload-Labs靶场实战:21种文件上传漏洞绕过技巧全解析
1. 文件上传漏洞:从入门到实战
大家好,我是老张,在网络安全这个行当里摸爬滚打了十几年,见过太多因为文件上传功能没做好而“翻车”的案例。简单来说,文件上传漏洞就是攻击者利用网站的上传功能,把恶意文件(比如能执行系统命令的Webshell)传到服务器上,从而控制整个网站甚至服务器。这听起来很可怕,对吧?但好消息是,只要我们理解了攻击者的思路,就能有效地进行防御。
今天,我们就拿Upload-Labs这个经典的靶场来练手。它是一个用PHP写的、专门收集各种上传漏洞的靶场,一共21关,每一关都代表了一种常见的防御手段和对应的绕过技巧。对于刚入门安全的朋友,或者想巩固Web安全知识的老手,这都是一个绝佳的实战平台。我会带你一关一关地打过去,不仅告诉你怎么绕过,更会深入分析每一关代码的逻辑缺陷在哪里,让你真正明白“为什么能绕过”,以及“如何修复”。咱们不搞那些虚头巴脑的理论,直接上手操作,用我踩过的坑,帮你铺平学习的路。
在开始之前,你需要准备好两样东西:一是Upload-Labs靶场环境,你可以从GitHub上搜索c0ny1/upload-labs下载;二是一个顺手的抓包工具,比如Burp Suite,后续很多关卡都需要它来拦截和修改数据包。另外,我建议你使用PHP 5.2.x版本的环境来搭建靶场,因为有些经典的截断漏洞在新版本中已经修复了,用老版本才能原汁原味地复现。好了,工具备齐,咱们这就开始闯关!
2. 前端校验:你的第一道“纸防线”
2.1 绕过JavaScript校验
第一关通常是最简单的,它给了我们一个“下马威”。页面上传一个.php文件,浏览器立刻弹窗提示“只能上传.jpg/.png/.gif”。你注意看,这个提示出现得飞快,甚至网络抓包工具里都还没看到任何数据包发送出去。这明显就是前端JavaScript在做校验。
前端校验就像门卫只检查你的证件照片,而不管证件本身真假。攻击者至少有三种方法轻松绕过:
- 直接禁用浏览器JavaScript:以Chrome为例,按F12打开开发者工具,在设置(Settings)里找到“Debugger”,勾选“Disable JavaScript”。刷新页面再上传,世界瞬间清净了。
- 拦截并修改上传请求:这是更通用的方法。我们先准备一个Webshell文件,比如
shell.php,内容是一句话木马<?php @eval($_POST[‘cmd’]);?>。上传时,先用Burp Suite抓包,你会看到数据包里的文件名是shell.php。别急,我们把它改成shell.jpg放过去,等前端校验通过后,在Burp里再把文件名改回shell.php,然后发送到服务器。因为服务器后端没有校验,所以文件就成功上传了。 - 动态修改前端代码:在浏览器开发者工具的“Sources”或“调试器”标签页里,找到负责校验的JavaScript函数(通常是
checkFile()),在里面下断点。当代码执行到判断文件后缀的地方时,直接修改内存中允许的后缀列表,或者让校验函数直接返回true。这种方法稍微需要点调试技巧,但能让你更深入理解前端逻辑。
核心要点:前端的一切校验对用户都是透明的、可被操控的。它只能作为提升用户体验的第一道过滤,绝对不能作为安全依赖。安全的校验必须放在服务端进行。
2.2 实战抓包与改包细节
很多新手在用Burp Suite抓上传包时会遇到问题,这里我多啰嗦几句实操细节。首先确保你的浏览器代理设置正确,指向Burp。在上传文件时,Burp的“Proxy” -> “Intercept”标签页如果是“Intercept is on”状态,请求就会被暂停。
你看到的数据包大概长这样:
POST /upload-labs/Pass-01/index.php HTTP/1.1
...
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: application/octet-stream
<?php phpinfo();?>
关键就在filename="shell.php"这一行。你把它改成filename="shell.jpg",先绕过前端。点击“Forward”放行,如果页面提示上传成功(但其实是假的jpg),说明前端校验过了。接着,立刻再次上传同一个文件,这次Burp抓到包后,直接把filename改回shell.php,然后放行。这时,服务器收到的就是完整的PHP文件了。去上传目录看看,是不是多了一个shell.php?访问它,如果显示了phpinfo()信息,恭喜你,第一关拿下。这个“先传假后缀,再改回真后缀”的技巧,在后面很多关卡都会用到,务必熟练掌握。
3. 服务端校验:MIME类型与黑名单的博弈
3.1 绕过MIME类型检查
来到第二关,禁用JS或者改前端文件名都不管用了。上传PHP文件,服务器会返回“文件类型不正确”。查看这关的源码,你会发现它检查的是$_FILES[‘upload_file’][‘type’],也就是MIME类型。当你上传一个PHP文件时,这个值通常是application/octet-stream,而服务器只允许image/jpeg、image/png、image/gif。
绕过方法简单粗暴:继续用Burp抓包。找到数据包中的Content-Type: application/octet-stream这一行,把它改成Content-Type: image/png,然后放行。服务器就被骗过去了,因为它只相信请求头里声明的类型,而不会去深度检测文件的实际内容。这里暴露的问题是:开发者误以为HTTP请求头里的Content-Type不可伪造。实际上,它和文件名一样,完全由客户端控制,必须通过检查文件内容的真实类型来验证。
3.2 黑名单绕过:寻找“漏网之鱼”
第三关开始,进入了经典的黑名单防御模式。你上传.php文件,它会明确告诉你:“不允许上传.asp,.aspx,.php,.jsp后缀文件!” 黑名单的思路是“禁止已知的危险项”,但总会有遗漏。
绕过思路一:非常规后缀名
Apache等Web服务器可以通过配置,将多种后缀解析为PHP。除了常见的.php,还有.php3, .php4, .php5, .phtml, .phps等。这取决于服务器httpd.conf或.htaccess中的AddType指令。你可以直接尝试上传一个shell.php5的文件,很可能就成功了。我建议你准备一个后缀名字典,用Burp的Intruder模块进行Fuzz测试,系统地找出所有被服务器解析的可执行后缀。
绕过思路二:系统特性与大小写 第四关到第十关,可以看作是黑名单防御的“攻防升级”。开发者不断补全黑名单,但攻击者利用系统特性总能找到新缺口。
- 第四关:
.htaccess文件攻击。这一关的黑名单极其全面,但唯独漏了.htaccess文件。这是Apache服务器的配置文件,可以覆盖当前目录的解析规则。我们分两步走:先上传一个.htaccess文件,内容为AddType application/x-httpd-php .jpg。这行代码的意思是“把所有.jpg文件都当作PHP来解析”。然后,再上传一个包含Webshell代码的shell.jpg文件。当你访问这个shell.jpg时,它就会被当作PHP执行。这种攻击威力巨大,因为它污染了整个目录的解析规则。 - 第六关:大小写绕过。这一关的源码去掉了将后缀名转为小写的
strtolower()函数。在Windows服务器上,文件名是不区分大小写的。因此,上传shell.Php或shell.PHP,系统实际存储和解析时都会视为shell.php,从而绕过黑名单(黑名单里只有小写的.php)。但在Linux服务器上,这是行不通的。 - 第七关:空格绕过。源码中去掉了去除首尾空格的
trim()函数。我们在Burp中把文件名改为shell.php(后面加一个空格)。当文件保存到Windows系统时,系统会自动忽略文件名末尾的空格,于是文件实际被存储为shell.php。同样,这招对Linux无效。 - 第八关:点号绕过。源码中删除了删除末尾点号的
deldot()函数。我们在Burp中将文件名改为shell.php.(末尾加一个点)。在Windows系统保存时,最后的点会被自动去除,文件又变回了shell.php。 - 第九关:
::$DATA流绕过。这是Windows NTFS文件系统的一个特性(备用数据流)。在Burp中将文件名改为shell.php::$DATA。当文件被保存时,::$DATA之后的部分会被忽略,最终生成的文件就是shell.php。
核心要点:黑名单永远有遗漏。无论是冷门后缀、系统特性(尤其是Windows)、还是配置问题,都可能成为突破口。最安全的做法是采用白名单机制,只允许明确安全的少数几种文件类型。
4. 白名单与截断:精准防御的裂痕
4.1 白名单校验与%00截断
从第十二关开始,靶场引入了更安全的白名单机制,通常只允许jpg, png, gif三种图片后缀。直接上传PHP文件会被明确拒绝。这时,我们需要利用一个古老但经典的漏洞:%00截断。这个漏洞存在于PHP 5.3.4之前的版本,且需要magic_quotes_gpc配置为Off。
截断原理:C语言中,\0(十六进制0x00)被用作字符串的结束符。PHP底层由C实现,在某些函数处理字符串时,遇到\0会认为字符串到此结束。第十二关的源码中,上传路径由$_GET[‘save_path’]参数控制,并与文件名拼接:$img_path = $_GET[‘save_path’].”/”.rand(10, 99).date(“YmdHis”).”.”.$file_ext;。如果我们控制save_path为../upload/shell.php%00,那么拼接后的路径是../upload/shell.php%00/xxxx.jpg。当PHP底层处理这个路径时,%00被解码为空字符\0,move_uploaded_file函数在遇到\0时就停止了读取,最终写入的文件路径实际上是../upload/shell.php,后面的/xxxx.jpg被截断丢弃了。
操作步骤:上传一个正常的shell.jpg(可以是图片马),在Burp抓到的GET请求中,找到save_path参数,将其值修改为../upload/shell.php%00。注意,%00必须在URL中进行编码。发送请求后,检查上传目录,应该会生成一个shell.php文件。
第十三关原理完全相同,只是save_path参数通过POST传递。在Burp的POST数据体中,我们不能直接写%00,因为POST不会自动URL解码。我们需要在Burp中选中%00,右键选择“Convert selection” -> “URL” -> “URL-decode”,将其转换为原始的十六进制00字符(在Burp的Hex视图中会显示为00)。
4.2 双写与条件竞争:逻辑漏洞的艺术
第十一关:双写绕过。这一关的防御思路变了,它不是拒绝上传,而是将黑名单中的后缀替换为空字符串:$file_name = str_ireplace($deny_ext,””, $file_name);。如果上传shell.php,替换后文件名就变成了shell.,无法解析。绕过方法是双写:上传文件名为shell.pphphp。当代码执行替换时,它会找到中间的php并将其删除,剩下的部分正好组合成shell.php。这种漏洞源于开发者只做了一次简单的字符串替换,而没有递归处理或使用更严谨的正则匹配。
第十七、十八关:条件竞争。这是非常经典且在实际漏洞挖掘中高价值的逻辑漏洞。代码的逻辑通常是:1. 先将文件上传到临时目录;2. 检查文件是否合法(后缀、内容等);3. 如果合法就重命名,如果不合法就删除(unlink)。问题出在“上传”和“删除”之间存在一个微小的时间窗口。
攻击者利用这个时间差进行“竞争”。我们上传一个特殊的Webshell,其内容不是直接执行命令,而是写入一个新的Webshell文件,例如:<?php fputs(fopen(‘real_shell.php’,’w’),’<?php @eval($_POST[‘c’]);?>’); ?>。然后,用Burp的Intruder模块,以多线程、无间隔的方式,疯狂重复发送这个上传请求。同时,用另一个工具(比如Burp的Repeater或自己写的Python脚本)疯狂访问这个临时上传的Webshell文件。只要在它被删除之前成功访问一次,real_shell.php文件就会被生成到服务器上,并且不会被删除,从而实现持久化控制。这种攻击的成功率取决于网络速度和服务器处理速度,通过高并发可以极大提高成功率。
5. 内容校验与二次渲染:深入文件骨髓的对抗
5.1 文件头与图片马
从第十四关起,防御升级到检查文件内容。第十四关使用getimagesize(),第十五关使用exif_imagetype(),它们都是通过读取文件的**头部字节(魔术字)**来判断是否为真实图片。例如,GIF文件头是GIF89a,PNG文件头是\x89PNG\r\n\x1a\n,JPEG文件头是\xff\xd8\xff。
绕过方法是制作图片马。将Webshell代码附加到一张正常图片的末尾。在Windows命令行下可以使用:copy /b normal.jpg + shell.php webshell.jpg。这样生成的webshell.jpg,用图片查看器打开是正常的,但用文本编辑器拉到末尾就能看到PHP代码。直接上传这个图片马,文件头校验就能通过。
但是,仅仅上传成功还不够,服务器默认不会把.jpg文件当作PHP来执行。这时就需要配合文件包含漏洞。假设网站存在诸如include($_GET[‘file’])这样的漏洞,我们就可以通过?file=./upload/webshell.jpg来包含这个图片马,其中的PHP代码就会被执行。在实际渗透测试中,文件上传点和文件包含点常常不在同一处,需要综合信息收集才能利用。
5.2 二次渲染的终极挑战
第十七关是Upload-Labs中最难、也最贴近实际生产环境的一关。它模拟了诸如头像上传等场景:用户上传图片后,服务器为了统一规格或防止木马,会对图片进行二次渲染(使用imagecreatefromgif/png/jpeg等函数重新生成一张图片)。这样,简单地追加在文件末尾的Webshell代码会在渲染过程中被彻底破坏。
绕过二次渲染需要更精细的操作,并且不同图片格式方法不同:
- GIF格式:成功率相对较高。原理是找到GIF图片在渲染前后保持不变的数据块区域,将Webshell代码插入这些区域。你需要用工具(如010 Editor)分别对比原始GIF和经过服务器渲染后下载回来的GIF,标记出所有未变化的十六进制部分。然后,将一小段Webshell代码(如
<?=$_GET[0]($_POST[1]);?>)写入这些区域。由于代码很短,容易找到容身之处。 - PNG格式:更为复杂。通常需要将Payload写入PNG的IDAT数据块或PLTE数据块。这需要你理解PNG的文件结构,并借助专门的脚本。网上有公开的脚本(例如用PHP的
gd库生成特定像素的PNG),这些脚本生成的PNG图片本身就携带了经过编码的Payload,在二次渲染后Payload依然能保留。 - JPG格式:难度最大,非常玄学。JPG的压缩算法复杂,渲染后变化极大。通常也是使用现成的脚本,在JPG的某些特定数据段内插入Payload,但成功率不稳定,需要多次尝试不同尺寸、不同内容的原图。
实战建议:对于二次渲染,除非有特别需求,否则不建议手工去抠字节。更好的方法是收集网上公开的、已经测试成功的GIF/PNG图片马样本,直接使用。在防御层面,二次渲染是当前对抗图片马非常有效的手段,但也要注意,如果渲染函数使用不当(如渲染失败却依然保存了上传的原始文件),则可能被绕过。
6. 逻辑与解析:非常规的奇技淫巧
6.1 解析漏洞与路径操控
第二十关和第二十一关,考察的是对PHP函数特性及代码逻辑的深度理解。
- 第二十关:
move_uploaded_file的路径特性。这一关的$img_path由用户通过POST参数save_name完全控制。研究发现,move_uploaded_file函数在保存文件时,如果目标路径末尾存在/.,它会自动忽略掉。因此,我们可以这样利用:上传一个shell.php,然后在save_name参数中填写shell.php/.。代码检查save_name的后缀(pathinfo取后缀)得到的是空(因为最后一个点是/.),可能绕过某些检查。最终move_uploaded_file保存时,会忽略/.,从而在服务器上生成shell.php文件。 - 第二十一关:数组绕过。这是终极的代码审计题。它先检查MIME类型,然后处理文件名。关键逻辑如下:
$file = empty($_POST[‘save_name’]) ? $_FILES[‘upload_file’][‘name’] : $_POST[‘save_name’];
if (!is_array($file)) { $file = explode(‘.’, strtolower($file)); }
$ext = end($file); // 取最后一个元素作为后缀
$allow_suffix = array(‘jpg’,’png’,’gif’);
if (!in_array($ext, $allow_suffix)) { die(‘禁止上传该后缀文件!’); }
$file_name = reset($file) . ‘.’ . $file[count($file) - 1]; // 拼接文件名
我们的绕过Payload是,将save_name设置为一个数组:save_name[0]=shell.php&save_name[2]=jpg。这样,$file直接就是数组,不会进入explode逻辑。$ext = end($file)得到的是jpg,通过了白名单检查。接着,reset($file)得到shell.php,count($file)为3,$file[count($file)-1]即$file[2]为空字符串。最终$file_name = ‘shell.php’ . ‘.’ . ‘’ = ‘shell.php.’。结合move_uploaded_file会忽略末尾/.或/. 的特性(在某些环境下),或者系统自动去点,最终可能得到一个可执行的shell.php文件。这关需要仔细构造,并理解数组操作和函数特性。
6.2 防御方案总结与最佳实践
打完21关,我们来系统梳理一下如何构建一个健壮的文件上传功能。防御是层层递进的,单一措施都有被绕过的风险。
- 前端校验:可以做,用于快速反馈、提升用户体验,但心里要明白这毫无安全性可言。
- 服务端白名单:这是核心! 只允许业务必需的后缀,如
[‘jpg’, ‘jpeg’, ‘png’, ‘gif’]。使用pathinfo($filename, PATHINFO_EXTENSION)或确保从最后一个点之后安全地提取后缀,并转换为小写。 - 重命名文件:上传的文件不要使用用户提供的文件名。应使用随机生成的文件名(如MD5(时间戳+随机数))并保留白名单内的后缀。这可以防止覆盖、混淆以及某些解析漏洞。
- 内容校验:使用
getimagesize()、exif_imagetype()或读取文件头魔术字,确保是真实的图片格式。这能有效过滤伪装的图片马。 - 二次渲染:对图片进行缩略图生成或水印添加等处理。这是目前对抗图片马最有效的方法之一,因为真正的图片数据会在渲染过程中被重构,嵌入的恶意代码会被清除。
- 隔离存储:上传的文件不要存储在Web目录下,或者至少确保存储目录没有执行脚本的权限。可以通过Nginx/Apache配置,禁止上传目录的PHP执行。将文件路径存储在数据库中,通过一个专门的、无执行权限的下载脚本来访问文件。
- 权限最小化:运行Web服务器的进程用户(如www-data)权限应尽可能低,避免其有权限写入或执行系统关键文件。
- WAF与安全监控:部署Web应用防火墙,对上传请求进行规则过滤。同时,监控服务器上是否被上传了可疑的可执行文件。
文件上传漏洞的攻防是一场持续的战斗。Upload-Labs靶场像一本教科书,涵盖了历史上的经典漏洞。但在真实环境中,漏洞可能以更隐蔽、更复杂的方式组合出现。我的经验是,吃透这些基础原理,在代码审计和渗透测试时保持“攻击者思维”,多问一句“这里如果这样构造,会发生什么?”,你就能发现更多潜在的风险。安全没有银弹,唯有深入理解,方能有效防御。
更多推荐
所有评论(0)