DedeCMS 5.7文件上传漏洞深度剖析:从黑名单绕过到防御体系构建

📅 2026/7/3 10:37:54 👁️ 阅读次数 📝 编程学习
DedeCMS 5.7文件上传漏洞深度剖析:从黑名单绕过到防御体系构建

1. 项目概述:为什么DedeCMS文件上传漏洞值得深挖?

在网站安全攻防的战场上,文件上传漏洞一直是个“常青树”级别的存在。它不像SQL注入那样需要复杂的逻辑绕过来绕过去,也不像XSS那样对输出环境有苛刻要求。很多时候,它就是一个简单的表单,一个上传按钮,一旦开发者疏忽,攻击者就能直接上传一个WebShell,拿到服务器的控制权,后果往往是灾难性的。而DedeCMS,作为国内曾经风靡一时的开源内容管理系统,其5.7版本的文件上传漏洞更是经典中的经典。这个漏洞之所以被反复提及和研究,不仅仅是因为它影响广泛,更因为它完美地展示了从“前端校验”到“后端逻辑”再到“代码层过滤”的完整攻防链条。

对于安全研究者、渗透测试工程师甚至是网站开发者来说,深入剖析这个漏洞,其价值远超“利用一个已知漏洞”。它能让你真正理解文件上传安全的核心矛盾:如何在保证功能可用性的前提下,构建一个滴水不漏的防御体系。你会发现,简单的“黑名单过滤”是多么脆弱,而“白名单+内容检测”的组合拳又该如何设计。通过复现和绕过DedeCMS 5.7的防护机制,你实际上是在演练一套完整的文件上传漏洞攻防方法论。无论是构建自己的靶场,还是审计其他系统,这套思路都能直接复用。接下来,我们就抛开那些泛泛而谈的理论,直接深入到代码层面,看看这个漏洞究竟是如何产生的,攻击者有哪些精妙的绕过技巧,而作为防御方,我们又该如何从根源上加固我们的系统。

2. 漏洞原理与代码层逻辑剖析

要理解一个漏洞,最好的方式就是阅读它的源代码。DedeCMS 5.7的文件上传功能主要涉及两个关键文件:/include/uploadsafe.inc.php和负责处理上传逻辑的upload.php或相关模块文件。漏洞的根源,就藏在这些文件的逻辑判断和过滤规则之中。

2.1 核心过滤机制:uploadsafe.inc.php的局限性

DedeCMS 5.7试图通过一个名为uploadsafe.inc.php的配置文件来统一管理上传安全。这个文件里定义了一个关键数组$cfg_not_allowall,它就是一个典型的黑名单。我们来看看它大概长什么样(基于公开代码分析):

$cfg_not_allowall = “php|pl|cgi|asp|aspx|jsp|php3|php4|php5|exe|sh”;

这个配置的意图很明显:禁止上传一系列常见的可执行脚本和后门文件扩展名,如.php,.asp,.jsp等。同时,系统在上传后,会通过$filetype = strtolower(trim($filetype));等方式获取并处理文件扩展名,然后与黑名单进行匹配。

这里的第一个致命问题出现了:黑名单的覆盖范围永远是不全的。攻击者可以轻易尝试黑名单之外的脚本扩展名,例如:

  • .phtml,.php3,.php4,.php5,.php7(不同PHP版本的处理模块)
  • .phps,.pht
  • 在特定服务器配置下,.jpg.php这样的双扩展名也可能被解析(取决于服务器如何解析最后一个点之后的内容)。

第二个问题:对文件内容的检查缺失。DedeCMS 5.7的默认过滤机制,主要集中在文件扩展名的校验上。它没有对文件内容进行有效的检测,例如检查文件头(Magic Bytes)、检查是否包含PHP标签<?php<%。这意味着,攻击者可以将一个PHP WebShell代码,直接写入一个.jpg文件,如果服务器配置错误(例如,将.jpg后缀解析为PHP),或者通过其他漏洞配合(如文件包含漏洞),这个“图片马”就能成功执行。

2.2 前台与后台上传路径的差异

DedeCMS的上传点并非一处,这增加了攻击面:

  1. 前台会员中心上传:通常权限控制更严格,但过滤逻辑可能与后台共用同一套不完善的机制。
  2. 后台管理员上传:这是最危险的地方。后台提供了诸如“上传新图片”、“压缩包解压”等功能。特别是“压缩包解压”功能,它引入了一个全新的攻击维度。

后台压缩包解压漏洞是DedeCMS 5.7文件上传漏洞的“王牌”绕过方式。其逻辑大致是:管理员可以上传一个ZIP压缩包,系统会将其解压到指定目录(如/uploads/allimg)。关键代码逻辑漏洞在于:系统在解压时,只检查了压缩包本身的文件名,但没有对解压出来的文件进行二次安全检查,或者检查逻辑可以被绕过。

假设过滤代码是这样的:

// 检查压缩包文件名 if(preg_match(“/\.(php|asp|jsp)/i”, $zipname)) { die(“禁止上传危险压缩包!”); } // 解压操作 // … 解压后,对解压出的文件 $filename 没有进行同样的黑名单检查

攻击者可以制作一个ZIP包,里面包含一个名为shell.php的文件。由于ZIP包本身叫pic.zip,通过了第一层检查。解压时,系统忠实地将shell.php释放到了Web目录下,漏洞就此触发。

2.3 代码层绕过的核心思路总结

基于以上分析,攻击者针对DedeCMS 5.7文件上传的代码层绕过,主要围绕以下几点:

  1. 黑名单扩展名绕过:寻找未被列入黑名单的、但服务器仍会解析的脚本扩展名。
  2. 大小写绕过:黑名单检查使用strtolower,但如果检查逻辑有瑕疵,.PhP.PHP可能被遗漏。
  3. 双写扩展名绕过:如shell.php.jpg。如果系统错误地只检查第一个点之前或之后的内容,就可能绕过。
  4. 空格/点号截断绕过:在文件名末尾添加空格或点号(如shell.php.shell.php),在某些系统处理逻辑下,可能会被截断,最终保存为shell.php。这依赖于PHP版本和系统配置(如magic_quotes_gpc和自动去除空格尾部的特性)。
  5. %00空字节截断:这是历史上一个非常经典的漏洞。在PHP旧版本(<5.3.4)中,如果上传路径由用户部分控制(如$savepath = $_POST[‘path’]),攻击者可以构造path=/uploads/shell.php%00,系统在拼接完整路径$savepath . $filename时,会在%00处截断,使得$filename的后缀检查失效。虽然此漏洞在现代PHP环境中已基本修复,但在分析历史漏洞时至关重要。
  6. 压缩包解压逻辑绕过:如上所述,利用解压过程的安全检查缺失。
  7. 文件内容伪装:制作图片马(GIF89a头+PHP代码),配合服务器错误配置或本地文件包含(LFI)漏洞使用。

3. 实战复现:构造与绕过

理解了原理,我们就在一个可控的环境(如本地搭建的DedeCMS 5.7靶场)中进行实战。这里我们不使用任何自动化工具,纯粹通过手动构造数据包来理解每一步。

3.1 环境准备与基础测试

首先,找到一处文件上传点,例如会员中心的头像上传,或者后台的“上传新图片”。打开浏览器开发者工具(F12)的“网络(Network)”选项卡,准备拦截和修改请求。

第一次尝试:直接上传PHP文件。我们编写一个最简单的WebShell文件test.php

<?php @eval($_POST[‘cmd’]); ?>

选择并上传。预期结果:大概率会被拦截,页面提示“文件类型不允许!”。

抓包分析:拦截到这个POST请求,你会看到Content-Disposition部分包含了文件名filename=”test.php”。这就是被服务端检查的关键字段。

3.2 实施代码层绕过

绕过1:扩展名黑名单绕过将文件改名为test.phtmltest.php5重新上传。观察结果。如果成功,说明黑名单列表不包含这些扩展名。

绕过2:大小写混合将文件名改为test.Phptest.PHP进行尝试。注意,由于DedeCMS通常使用了strtolower,此方法可能无效,但仍是测试环节之一。

绕过3:双写扩展名与空格截断修改上传数据包:

Content-Disposition: form-data; name=”upfile”; filename=”test.php.jpg”

或者

Content-Disposition: form-data; name=”upfile”; filename=”test.php. ”

(注意末尾有一个空格)。有些系统在保存文件时,会去除末尾的空格或最后一个点,导致文件实际被存储为test.php

绕过4:利用后台压缩包解压(高成功率)

  1. 创建一个shell.php文件,内容同上。
  2. 将其压缩成ZIP文件,注意,压缩软件里直接对shell.php右键压缩,生成shell.zip。此时ZIP内文件即为shell.php
  3. 登录DedeCMS后台,找到“核心” -> “频道模型” -> “内容模型管理” -> “普通文章” -> “字段管理”,或者直接寻找“上传新图片”中是否有“从ZIP压缩包中解压”的选项。更常见的入口是后台的“系统” -> “图片水印设置”等相关上传功能,或者某些模块的自定义上传。
  4. 上传shell.zip。如果系统只检查了压缩包文件名,解压时未检查内部文件,那么shell.php就会被释放到服务器上,例如在/uploads/allimg/xxxxxx/目录下。
  5. 访问这个路径,如果返回空白或执行了PHP代码,则漏洞利用成功。

实操心得:在测试压缩包绕过时,务必注意服务器的目录权限。解压出的文件可能需要执行权限。另外,多尝试不同的后台上传点,因为不同模块调用的上传函数可能略有差异,某些模块的过滤可能更弱。

3.3 绕过后的利用与连接

假设我们通过test.phtml绕过成功,文件保存在/uploads/userup/202405/1/test.phtml

  1. 验证文件存在:直接浏览器访问http://your-target/uploads/userup/202405/1/test.phtml。如果返回空白页(因为我们的Shell代码没有输出),这通常是好迹象。
  2. 使用WebShell管理工具连接:这里我们使用中国菜刀(Caidao)或蚁剑(AntSword)的旧版本连接方式作为原理演示(实际中请使用更安全的授权测试工具)。
    • 在连接地址中填入上述URL。
    • 密码栏填入我们WebShell中定义的密码,即cmd(对应$_POST[‘cmd’])。
    • 连接类型选择PHP
  3. 执行命令:连接成功后,即可在工具中执行系统命令,如whoami,ipconfig /all(Windows) 或ifconfig(Linux),查看当前权限和服务器信息。

4. 从防御者视角构建多维防御方案

仅仅知道如何攻击是远远不够的,作为开发者或安全运维,我们必须构建一个纵深防御体系,让DedeCMS或其他类似系统变得坚不可摧。

4.1 代码层加固:修补漏洞根源

这是最根本的解决方案。如果你还在使用DedeCMS 5.7,必须手动修改源代码。

1. 强化扩展名校验:采用白名单机制修改/include/uploadsafe.inc.php及相关上传处理文件。将黑名单思维彻底转变为白名单。

// 定义允许上传的文件类型白名单 $cfg_allowall = array(‘jpg’, ‘jpeg’, ‘gif’, ‘png’, ‘bmp’, ‘webp’, ‘ico’, ‘pdf’, ‘doc’, ‘docx’, ‘xls’, ‘xlsx’); // 获取文件扩展名并转换为小写 $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); // 严格检查 if (!in_array($file_ext, $cfg_allowall)) { die(‘文件类型不允许!’); }

2. 加强文件内容检查在保存文件前,对文件内容进行检测。

  • 检查文件头(Magic Bytes):读取文件的前几个字节,判断是否与扩展名匹配。例如,一个.jpg文件的开头必须是FF D8 FF E0FF D8 FF E1
    $file_header = bin2hex(file_get_contents($tmp_name, 0, null, 0, 4)); $allowed_headers = array(‘ffd8ffe0’, ‘ffd8ffe1’, ‘89504e47’, ‘47494638’, ‘25504446’); // jpg, png, gif, pdf if (!in_array($file_header, $allowed_headers)) { die(‘文件内容非法!’); }
  • 检查是否包含危险内容:使用正则表达式扫描文件内容,检查是否存在<?php,<%,language=”php”等PHP标签。注意,对于图片等二进制文件,直接读取可能会出错,需要先进行文件头判断。

3. 修复压缩包解压漏洞在解压ZIP或RAR文件的函数中,必须对压缩包内的每一个文件进行独立的安全检查,检查逻辑应与直接上传文件时完全一致(白名单+内容检查)。

$zip = new ZipArchive; if ($zip->open($zipfile) === TRUE) { for ($i = 0; $i < $zip->numFiles; $i++) { $filename_in_zip = $zip->getNameIndex($i); $file_ext_in_zip = strtolower(pathinfo($filename_in_zip, PATHINFO_EXTENSION)); // 对压缩包内每个文件进行白名单校验! if (!in_array($file_ext_in_zip, $cfg_allowall)) { $zip->close(); die(“压缩包内包含危险文件类型:{$filename_in_zip}”); } // 可选:解压到临时目录,进行内容检查后再移动到正式目录 } $zip->extractTo($target_path); $zip->close(); }

4. 重命名上传文件不要使用用户上传的文件名。使用随机生成的文件名(如MD5(时间戳+随机数))并保留正确的白名单扩展名。

$new_filename = md5(time() . rand(1000, 9999)) . ‘.’ . $file_ext; move_uploaded_file($tmp_name, $upload_dir . ‘/’ . $new_filename);

5. 设置正确的目录权限

  • 上传目录(如/uploads/)应仅赋予755权限(所有者可读可写可执行,其他用户只读可执行)。
  • 最关键的一步:禁止上传目录执行脚本。通过配置Web服务器实现:
    • Apache:在上传目录下放置一个.htaccess文件,内容为:php_flag engine off
    • Nginx:在站点配置的location块中针对上传目录添加:location ~ ^/uploads/.*\.(php|php5|phtml)$ { deny all; }

4.2 服务器与环境层加固

代码修复是基础,服务器配置是保险。

  1. 升级PHP版本:确保使用PHP 5.3.4以上版本,从根本上杜绝%00空字节截断漏洞。
  2. 配置open_basedir:在php.ini中设置open_basedir,将PHP可访问的文件限制在网站目录内,防止跨目录攻击。
  3. 禁用危险函数:在php.ini中,将disable_functions设置为包含system, exec, shell_exec, passthru, proc_open, eval等函数。这样即使WebShell上传成功,也无法执行系统命令。
  4. 使用Web应用防火墙(WAF):部署WAF可以拦截大部分基于特征的文件上传攻击包,提供网络层的防护。

4.3 安全开发规范

对于新项目,应建立铁律:

  1. 白名单至上:任何文件上传功能,必须使用白名单验证扩展名。
  2. 内容检查不可少:至少进行文件头校验。
  3. 强制重命名:永远不使用用户提供的文件名。
  4. 隔离存储:将上传的文件存储在Web根目录之外,通过脚本(如readfile.php?id=xxx)来读取和交付。这样即使文件是PHP,也无法直接通过URL访问执行。
  5. 权限最小化:上传目录脚本执行权限必须关闭。
  6. 日志审计:记录所有上传操作,包括文件名、IP、时间、用户ID,便于事后追溯。

5. 常见问题排查与深度思考

在实际渗透测试或安全加固过程中,你会遇到各种各样的问题。这里记录一些典型的场景和解决思路。

Q1:我修改了uploadsafe.inc.php,换成了白名单,为什么上传还是失败了?A:首先检查修改的文件是否已生效(清除OPcache,重启PHP服务)。其次,DedeCMS的上传逻辑可能分散在多个文件中,例如dede/archives_do.phpmember/upload.php等。你需要全局搜索$cfg_not_allowall或检查文件上传相关的函数调用,确保所有入口都应用了新的安全规则。最稳妥的方法是,找到处理上传的核心函数(可能是Upload类),在那里进行统一加固。

Q2:使用了白名单,也检查了文件头,但攻击者上传了一个包含PHP代码的test.jpg文件,并通过文件包含漏洞执行了,怎么办?A:这说明你的防御体系存在短板。文件上传安全必须与其他安全措施联动。在这种情况下,你需要:

  1. 修补文件包含(LFI)漏洞:这是另一个独立但相关的严重漏洞。
  2. 加强内容检查:除了文件头,可以增加对文件内容中PHP标签的深度扫描。对于图片,可以使用getimagesize()函数验证其确实是有效的图片,但这并非绝对安全。
  3. 实施终极方案——文件存储隔离:将上传的文件存储在无法通过Web直接访问的目录,通过一个安全的下载脚本(该脚本会再次检查文件类型和权限)来提供访问。这样,即使文件被包含,也因为不在Web目录下而无法被直接定位。

Q3:在测试压缩包绕过时,系统提示“压缩包内包含非法文件”,但我明明已经修改了代码,为什么?A:检查你的修改是否应用到了正确的解压函数上。DedeCMS可能有多处解压功能(如在线解压ZIP主题、解压模块等)。你需要找到后台对应上传功能具体调用的PHP文件。另外,注意缓存,修改后务必刷新后台页面。还有一种可能是你的白名单列表过于严格,压缩包内包含了一些系统需要的但不在白名单内的文件(如__MACOSX文件夹下的文件或.DS_Store),需要在检查逻辑中加入对这些系统文件的忽略判断。

Q4:防御方案这么多,优先级如何排列?A:对于一个正在运行的DedeCMS 5.7站点,应急加固的优先级如下:

  1. 立即实施(5分钟):在服务器层面,为上传目录配置“禁止执行脚本”的规则(Nginx/Apache配置)。这能立刻阻断绝大多数已经上传的WebShell的执行。
  2. 紧急修复(1小时内):修改代码,将黑名单改为白名单(至少先加上.php系列扩展名),并修复压缩包解压漏洞。这是堵住漏洞源头。
  3. 全面加固(1天内):实施文件重命名、增加基础的文件头检查、审查所有上传点代码。
  4. 长期优化(持续):规划将上传文件移至非Web目录、部署WAF、建立完整的安全开发生命周期。

深度思考:为什么文件上传漏洞经久不衰?根本原因在于“功能便利性”与“安全性”之间的平衡难题。开发者总是希望用户能上传各种格式的文件来丰富内容,而过于严格的限制又会影响用户体验。此外,文件上传涉及前端JS校验、后端语言(PHP)校验、Web服务器配置、操作系统权限等多个层面,任何一层的疏忽都会导致整体防御失效。因此,防御必须是一个覆盖“前端校验(仅用于体验)、后端白名单校验、内容校验、重命名、安全存储、权限控制”的立体体系。DedeCMS 5.7的漏洞案例,正是这个复杂问题的一个经典剖面,它教会我们的不仅是一个漏洞的利用与修补,更是一套应对此类威胁的完整方法论。