文件上传漏洞实战:从黑名单绕过到.user.ini利用的完整渗透链

📅 2026/7/29 2:11:16
文件上传漏洞实战:从黑名单绕过到.user.ini利用的完整渗透链
1. 项目概述一次从防御到攻击的完整视角转换最近在复盘一些老漏洞的利用手法时我重新审视了“ez_upload”这个经典的漏洞场景。它之所以经典不仅仅是因为它频繁出现在各种CTF比赛和渗透测试的靶场里更因为它像一本活教材串联起了文件上传漏洞从基础到高阶的多个核心知识点。很多朋友可能觉得文件上传嘛不就是传个木马然后访问执行就完了。但实战中尤其是面对一些有基础防护的站点你会发现从“能上传”到“能利用”之间隔着好几道需要绕过的墙。这次我想分享的就是围绕“ez_upload”这个虚构但极具代表性的漏洞点如何一步步从最基础的黑名单绕过进阶到利用服务器配置特性比如.user.ini来实现更深层次的利用。整个过程其实是一次思维模式的转变从攻击者视角去理解防御者的规则再从防御者视角的盲区中寻找攻击路径。这对于从事安全研究、渗透测试或者想深入理解Web安全的朋友来说是一次非常有益的思维训练。无论你是刚入门的新手还是想梳理知识体系的老手我相信这个从浅入深的实战解析都能给你带来一些新的启发。2. 漏洞环境与核心逻辑拆解2.1 “ez_upload”场景模拟与核心代码逻辑我们首先来构建一个典型的“ez_upload”漏洞场景。它通常是一个具备基础防护的文件上传功能代码逻辑清晰方便我们理解攻击与防御的博弈。假设后端是PHP语言前端是一个简单的表单。一个典型的、有缺陷的上传处理逻辑可能如下所示已做简化?php // upload.php $upload_dir uploads/; $allowed_ext array(jpg, png, gif); // 黑名单不这里是白名单思想的反例常被误读为黑名单 $max_size 1024 * 1024; // 1MB if ($_SERVER[REQUEST_METHOD] POST isset($_FILES[file])) { $file $_FILES[file]; $file_name $file[name]; $file_size $file[size]; $file_tmp $file[tmp_name]; $file_ext strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); // 获取后缀 // 检查1: 文件大小 if ($file_size $max_size) { die(文件太大。); } // 检查2: 文件类型黑名单绕过重点 $denied_ext array(php, php3, php4, php5, phtml, phps); // 明显的黑名单 if (in_array($file_ext, $denied_ext)) { die(危险的文件类型禁止上传。); } // 检查3: 文件内容简单的图片头检查 $check getimagesize($file_tmp); if($check false) { die(请上传真实的图片文件。); } // 重命名并移动文件 $new_file_name uniqid() . . . $file_ext; // 使用随机名增加直接访问难度 $upload_path $upload_dir . $new_file_name; if (move_uploaded_file($file_tmp, $upload_path)) { echo 文件上传成功路径: . $upload_path; } else { echo 文件上传失败。; } } ?核心逻辑分析这段代码体现了三种常见的、但存在缺陷的防御措施扩展名黑名单$denied_ext数组明确禁止了.php及其相关变种。这是最原始的黑名单策略也是我们“绕过”的起点。文件头验证getimagesize()函数用于验证文件是否为图片。这旨在阻止非图片文件的上传但可以通过在恶意代码前添加图片头如GIF89a来绕过。随机化文件名uniqid()生成随机文件名使得攻击者即使上传成功也无法直接知道文件的访问路径增加了利用难度。但这并非绝对安全需要结合其他漏洞如目录遍历、信息泄露或后续技巧如.user.ini来利用。这个场景的“漏洞”在于防御措施是割裂且不彻底的。黑名单总有遗漏文件头检查可以被伪造随机化文件名只是增加了利用门槛。我们的攻击路径就是逐层突破这些防御。2.2 攻击目标与利用链规划我们的最终目标是在目标服务器上执行任意代码。在“ez_upload”场景中直接的路径可能是上传一个Webshell如一句话木马但由于黑名单和随机化命名直接上传.php文件并访问行不通。因此我们需要规划一条利用链突破黑名单上传一个不被黑名单禁止但又能被服务器以某种方式解析执行的文件。绕过文件头检查让我们带有恶意代码的文件在getimagesize()看来是一张“合法”的图片。定位或影响上传文件解决随机文件名带来的“找不到文件”问题或者通过影响其他文件的解析方式来间接执行我们的代码。实现代码执行最终通过Web请求触发我们植入的代码。.user.ini文件的利用正是解决第3步和第4步的一种优雅且强大的方式。它不像直接上传Webshell那样“显眼”而是通过修改PHP运行环境来“润物细无声”地实现控制。3. 第一层突破经典黑名单绕过技巧实战黑名单是“我知道哪些是坏的我把它们禁掉”。但世界上的“坏”东西是列举不完的。3.1 扩展名大小写与多重后缀绕过这是最基础的绕过。Windows服务器IIS通常对文件名大小写不敏感而Linux/Unix系统是敏感的。但关键取决于应用代码如何处理后缀。大小写绕过如果黑名单只检查了小写php尝试上传.pHp.Php。多重后缀绕过利用代码中提取最后一个点之后作为后缀的逻辑缺陷。例如上传文件名为shell.php.jpg。如果代码使用pathinfo($name, PATHINFO_EXTENSION)它会得到jpg从而绕过黑名单。但某些Web服务器如Apache的配置可能允许它按照从右往左的特定顺序尝试解析。如果服务器配置了AddType application/x-httpd-php .php对于shell.php.jpg它可能仍然会尝试交给PHP解析器因为.php这个片段触发了规则。这更依赖于服务器环境。实操示例我们准备一个最简单的PHP Webshell文件内容为?php eval($_POST[cmd]);?将其保存。直接命名为shell.php上传会被黑名单拦截。重命名为shell.pHp上传观察是否成功。重命名为shell.php.jpg上传观察是否成功并尝试访问http://target/uploads/随机名.php.jpg看是否被解析。注意多重后缀绕过高度依赖于服务器解析规则。Apache的mod_mime模块行为、Nginx的fastcgi_split_path_info配置等都会影响结果。这属于“环境特性”而非“代码漏洞”但在渗透测试信息收集阶段非常重要。3.2 利用冷门或特制的解析后缀黑名单不可能穷尽所有。除了常见的php, php3, php4, php5, phtml还有哪些可能被解析PHP相关.php7,.phpt,.pht,.phps(源代码显示)在某些特定配置下可能被执行。其他语言后端如果目标服务器还支持其他脚本语言例如.asp,.aspx,.jsp对于对应的环境就是可执行的。但这超出了纯PHP漏洞场景。配置文件变体.user.ini本身不是脚本但它可以影响PHP行为这是我们后面的重点。.htaccess(Apache)如果服务器允许上传.htaccess文件且AllowOverride设置不当攻击者可以上传一个包含AddType application/x-httpd-php .xyz的.htaccess文件强制将所有.xyz文件解析为PHP然后再上传shell.xyz。这是非常致命的一招。实战思维在信息收集中要识别服务器类型Apache/Nginx/IIS、中间件版本、已安装的模块/处理器映射。这能为你提供潜在的可解析后缀列表。3.3 空格、点号与路径截断历史手法这些手法多见于旧版本PHP和特定环境现代环境中大多已修复但作为知识需要了解。空格与点号在文件名末尾添加空格或点号如shell.php.或shell.php。Windows系统在保存文件时会自动去除末尾的点和空格但代码检查后缀时可能因为trim()函数缺失而认为后缀是php.或php从而绕过对php的检查。上传后系统存储的文件名实际是shell.php。路径截断CVE-2015-2348等在POST请求中文件名通常存在于multipart/form-data数据包里。在老版本PHP5.3.4 5.4.0中如果对filename字段处理不当可以利用空字符%00进行截断。例如上传的文件名设置为shell.jpg%00.php代码检查后缀得到jpg但在保存时%00被解释为字符串结束符最终保存为shell.jpg但内容是我们构造的PHP代码。此方法在PHP高版本中已默认失效因为%00会被拒绝。操作心得对于现代Web应用不要过分依赖这些历史截断方法。它们更像是“漏洞博物馆”里的展品提醒我们输入验证必须彻底。你的主要精力应该放在解析特性、配置缺陷和逻辑漏洞上。4. 第二层伪装绕过内容类型与文件头检查很多上传功能不仅检查后缀还会检查文件内容比如用getimagesize()、exif_imagetype()判断是否为图片或者检查$_FILES[‘file’][‘type’]客户端MIME类型极易伪造。4.1 伪造合法图片文件头图片文件在文件开头有特定的魔术数字Magic BytesJPEG:FF D8 FF E0或FF D8 FF E1PNG:89 50 4E 47 0D 0A 1A 0AGIF:47 49 46 38 39 61(GIF89a)我们可以创建一个文件文件开头是合法的图片头后面拼接我们的PHP代码。这样getimagesize()会成功返回图片信息而文件后续的PHP代码则会被忽略对于图片查看器或执行如果被解析为PHP。制作图片马Image Shell的方法命令行合成Linux/Mac# 将一个正常的图片如normal.jpg和一个PHP webshellshell.php合并 cat normal.jpg shell.php shell.jpg这样生成的shell.jpg用图片浏览器打开显示正常但用文本编辑器查看末尾能看到PHP代码。十六进制编辑器用010 Editor、WinHex等工具打开一张正常图片直接在最末尾文件EOF之前追加你的PHP代码。注意不要破坏图片原有的结构否则getimagesize()可能失败。直接在代码中构造?php // 创建一个GIF图片马 $gif_header \x47\x49\x46\x38\x39\x61; // GIF89a $php_code ?php eval($_POST[pass]);?; file_put_contents(shell.gif, $gif_header . $php_code); ?4.2 利用文件包含漏洞实现代码执行这是关键的一步。即使我们上传了一个包含PHP代码的图片马shell.jpg服务器默认也不会把它当作PHP来解析。除非存在**本地文件包含LFI**漏洞。假设目标网站存在这样一个漏洞// include.php 存在文件包含漏洞 $page $_GET[page]; include(/some/path/ . $page . .php);这个漏洞允许我们控制include函数的部分路径。虽然它固定添加了.php后缀但如果我们能进行目录遍历或许可以包含其他文件。更经典的是无后缀限制的包含// vuln.php $file $_GET[file]; include($file); // 危险未过滤输入这时我们可以通过file参数直接包含我们上传的图片马http://target/vuln.php?fileuploads/随机名.gif。当这个文件被include()函数包含时文件中的PHP代码段?php ... ?就会被服务器解析执行即使它的后缀是.gif。利用链形成上传图片马shell.gif(内容GIF89a?php phpinfo();?)绕过黑名单和文件头检查。找到文件包含漏洞点vuln.php。访问http://target/vuln.php?fileuploads/shell.gif。phpinfo()页面成功执行证明代码执行成功。重要提示include包含非.php文件时文件中的PHP代码必须包含在?php ?标签内才会被解析。纯文本或图片二进制数据中的?php ?标签会被Zend引擎识别并执行。这也是为什么图片马能生效的原因。5. 第三层渗透.user.ini文件的巧妙利用当没有直接的文件包含漏洞时.user.ini提供了一种影响更广、更隐蔽的利用方式。它主要用于配置PHP运行环境。5.1 .user.ini 是什么它能做什么.user.ini是PHP在CGI/FastCGI模式下如NginxPHP-FPM Apachephp_module支持的用户级配置文件。当PHP在某个目录下发现.user.ini文件时会在处理该目录及其所有子目录中的PHP文件之前应用该文件中的配置指令。关键特性作用范围对该文件所在目录及其所有子目录生效。生效时机在目录中的PHP文件执行之前。常见指令auto_prepend_file 文件路径在该目录下每个PHP文件执行前自动包含指定的文件。auto_append_file 文件路径在该目录下每个PHP文件执行后自动包含指定的文件。open_basedir限制PHP可访问的目录。disable_functions禁用特定函数。我们的利用核心在于auto_prepend_file和auto_append_file。想象一下如果我们能在网站的图片上传目录例如/uploads/下上传一个.user.ini文件并设置auto_prepend_fileshell.jpg那么以后任何人在访问/uploads/目录下的任何一个PHP文件时哪怕这个PHP文件是网站本身正常的业务文件都会先自动包含并执行我们上传的shell.jpg中的代码。5.2 利用条件与实战步骤利用前提条件服务器环境PHP运行在CGI/FastCGI模式目前绝大多数生产环境都是并且user_ini.filename配置项为.user.ini默认就是user_ini.cache_ttl不为0默认300秒。文件上传能上传文件到Web可访问目录且对文件名和后缀限制不严允许上传.ini文件或能通过黑名单绕过例如.user.ini可能不在黑名单中。目录权限上传目录下存在或未来可能被放置PHP文件。如果上传目录本身从不执行PHP则.user.ini不会生效。但很多情况下上传目录可能会有图片处理脚本、展示脚本等。路径可控auto_prepend_file指令中指定的文件路径必须是服务器能访问到的。通常我们使用相对路径如shell.jpg表示与.user.ini同目录的文件。完整实战步骤假设我们已成功上传一个图片马到/uploads/目录访问地址为http://target/uploads/abcdefg.jpg其内容包含?php eval($_POST[cmd]);?。步骤一制作.user.ini文件创建一个文本文件内容如下auto_prepend_fileabcdefg.jpg将其保存文件名即为.user.ini。注意在Windows系统下直接创建以点开头的文件可能麻烦可以在命令行使用echo auto_prepend_fileabcdefg.jpg .user.ini或者先保存为user.ini上传时再改名为.user.ini。步骤二上传.user.ini文件尝试上传此文件。由于黑名单可能只关注脚本后缀.ini文件很可能被允许上传。如果被拦截可以尝试.user.ini.jpg利用解析特性或.user.ini空格等方式绕过。步骤三触发与验证上传成功后我们需要等待user_ini.cache_ttl默认300秒时间或者想办法触发PHP重新扫描目录如重启PHP-FPM服务但这在攻击中不现实。更实际的方法是寻找一个肯定会访问到的、位于/uploads/目录或其子目录下的PHP文件。直接访问已知PHP文件如果/uploads/目录下存在如index.php,showimage.php等文件直接访问它。我们的Webshell代码就会在该文件原有逻辑之前执行。利用文件包含触发如果网站其他位置存在文件包含漏洞即使只能包含.php文件我们可以尝试包含/uploads/目录下的任何一个合法的、已存在的PHP文件。这个被包含的文件执行时会触发.user.ini。等待与观察有时网站后台可能会有扫描上传目录、生成缩略图等功能的PHP脚本。当这些脚本运行时就是我们的Webshell被执行之时。访问http://target/uploads/index.php假设存在如果页面正常输出前出现了异常或者我们用POST方式向该URL发送cmdphpinfo();命令能成功执行则证明利用成功。5.3 .user.ini利用的优势与局限优势隐蔽性强不像直接上传shell.php那样显眼。.user.ini是一个配置文件shell.jpg是一个“图片”在文件管理器中看起来不那么可疑。影响范围广一旦生效会影响该目录下所有PHP文件的执行相当于获得了该目录的“持久化”后门。绕过某些防御一些WAF或安全扫描可能只检测可执行文件的内容而对.ini配置文件和图片文件放松警惕。局限与注意事项需要目录下有PHP文件这是最大的限制。如果上传目录纯静态只存放图片、文档没有任何PHP文件会被执行则.user.ini不会生效。生效有延迟受user_ini.cache_ttl影响配置更改不是立即生效的。可能被覆盖如果网站应用本身也在该目录下放置了.user.ini可能会被覆盖。或者服务器主配置禁用了.user.ini功能。路径问题auto_prepend_file如果使用相对路径是基于被访问的PHP文件所在目录的相对路径而不是基于.user.ini的路径这点需要理解清楚。通常将.user.ini和图片马放在同一目录下使用相对路径最可靠。6. 组合拳实战从信息收集到getshell让我们串联起所有技术点模拟一次完整的、有一定对抗性的渗透测试过程。阶段一信息收集与功能探测目标识别确定目标存在文件上传功能例如用户头像上传、文章附件上传、反馈图片上传等。环境探测通过报错信息、响应头、技术栈指纹识别如Wappalyzer等手段确定服务器是Apache/Nginx/IISPHP版本是否开启特定模块如mod_rewrite。策略试探上传一个正常图片观察返回结果。看返回路径是原文件名还是随机文件名是否提供了完整的访问URL尝试上传一个.txt文件看是否被允许这有助于判断过滤策略是黑名单还是白名单。阶段二黑名单绕过与文件上传初步尝试直接上传shell.php确认被拦截。后缀绕过尝试shell.pHp,shell.php.jpg,shell.php5,shell.phtml,shell.htaccess测试Apache环境shell.user.ini。制作图片马使用copy /b normal.jpg shell.php shell.jpgWindows或cat normal.jpg shell.php shell.jpgLinux制作合并文件。上传图片马将shell.jpg上传确认成功并记录返回的随机文件名如uploads/9a7e8f1b2c.jpg。阶段三寻找触发点直接访问尝试访问上传的图片马http://target/uploads/9a7e8f1b2c.jpg。正常情况下应显示图片或乱码因为包含PHP标签的图片可能损坏代码不会执行。搜索文件包含使用爬虫或目录扫描工具寻找可能存在包含漏洞的参数如?file,?page,?include,?path等。同时查看应用功能是否有“模板加载”、“语言包切换”、“本地文件预览”等功能。测试包含如果找到疑似包含点尝试包含/etc/passwdLinux或C:\windows\win.iniWindows进行验证。然后尝试包含我们上传的图片马路径uploads/9a7e8f1b2c.jpg。如果成功则直接getshell。阶段四.user.ini持久化利用当无文件包含时检查上传目录确认uploads/目录下是否存在任何.php文件。可以尝试访问uploads/index.php或通过目录扫描发现。创建.user.ini内容为auto_prepend_file9a7e8f1b2c.jpg。上传.user.ini尝试上传可能需要使用.user.ini.末尾加点或.user.ini.jpg绕过。触发与验证如果uploads/下有index.php直接访问它。如果网站有其他功能会调用上传目录的PHP脚本比如图片管理后台等待或触发该功能。可以尝试向http://target/uploads/index.php或任何该目录下的PHP文件发送POST请求参数为cmdsystem(whoami);查看响应中是否包含命令执行结果。阶段五权限提升与后渗透成功执行命令后就是标准的后渗透流程获取反向Shell、信息收集、提权、内网横向移动等这超出了本文范围但却是实战的最终目的。7. 防御视角如何构建更安全的文件上传功能作为开发者了解了攻击手法才能更好地防御。以下是一些关键原则使用白名单而非黑名单只允许明确安全的文件类型如[jpg, jpeg, png, gif]。任何不在列表中的后缀都拒绝。文件类型验证三重奏检查MIME类型使用$_FILES[file][type]不可靠客户端可控结合后端验证。检查文件扩展名基于白名单。检查文件内容魔术数字使用finfo_file(FILEINFO_MIME_TYPE)或getimagesize()仅图片进行二进制内容验证确保扩展名与内容匹配。重命名与隐藏路径使用随机字符串如UUID重命名上传的文件避免用户猜测路径。不要将上传文件保存在Web根目录下。应该保存在一个非Web可访问的目录然后通过一个专门的脚本如download.php?idxxx来读取和输出文件。这样即使上传了恶意文件攻击者也无法直接通过URL访问触发。限制文件权限确保上传目录的脚本执行权限被关闭。在Nginx配置中可以为上传目录添加location ~* ^/uploads/.*\.(php|php5)$ { deny all; }。但更根本的是第3点不存于Web目录。使用专用存储服务考虑使用云存储如AWS S3, 阿里云OSS等这些服务通常有完善的安全策略并且文件不直接与应用服务器交互隔离了风险。扫描与监控对上传的文件进行病毒/恶意代码扫描。监控服务器上异常的文件创建、修改行为特别是.user.ini,.htaccess等配置文件。定期更新与安全配置保持PHP、Web服务器等中间件版本更新使用安全配置。例如在php.ini中可以设置user_ini.filename为空字符串以禁用.user.ini或者设置open_basedir限制PHP的活动范围。文件上传漏洞的攻防是一场持续的动态博弈。攻击者在不断寻找解析规则、配置缺陷和逻辑漏洞中的缝隙而防御者则需要建立多层次、纵深化的防御体系。理解每一种攻击手法背后的原理是构建有效防御的第一步。希望这篇从“ez_upload”出发的深度解析能帮你不仅学会“破”更能思考如何“立”。