前言
前段时间看到有篇文章是关于DedeCMS后台文件上传(CNVD-2022-33420),是绕过了对上传文件内容的黑名单过滤,碰巧前段时间学习过关于文件上传的知识,所以有了这篇文章,对DedeCMS的两个文件上传漏洞(CVE-2018-20129、CVE-2019-8362)做一个分析。
简介
DedeCMS简介
DedeCMS由上海卓卓网络科技有限公司研发的国产PHP网站内容管理系统;具有高效率标签缓存机制;允许对类同的标签进行缓存,在生成 HTML的时候,有利于提高系统反应速度,降低系统消耗的资源。众多的应用支持;为用户提供了各类网站建设的一体化解决方案,在本版本中,增加了分类、书库、黄页、圈子、问答等模块,补充一些用户的特殊要求 。
DedeCMS V5.7 SP2前台文件上传(CVE-2018-20129)
漏洞复现
复现环境:phpstudy、DedeCMS V5.7 SP2、php5.6.9
前提条件:会员模块开启、以管理员权限登录。
会员模块默认情况下是不开启的,需要管理员在后台手动。
登录到前台以后找到内容中心,发表一篇文章,点击下面编辑器中找到上传图片按钮,其实这里原本想实现的功能就是一个简单图片上传的功能。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/b952aff7-3141-41bb-93de-6a5d0034ddcf_1653994082.png)
然后使用BurpSuite抓包,把文件名称从1.png改成1.png.p*hp,然后放包上传。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/f4d4c528-bc35-4ec9-9292-92439e55c753_1653994086.png)
在响应信息中得到上传文件的保存地址,并且文件的后缀也是PHP。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/3e698659-5c0a-4590-a7b9-5f2021a7af6a_1653994089.png)
但是当我们尝试去访问这个文件时会发现有的文件是不解析的,这跟我们上传的文件有关系,这个问题我们后面再解释。
上传的脚本文件可以正常利用。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/3d9e29a6-53a9-49e3-9d35-ae8685b54d6a_1653994093.png)
漏洞分析
从抓取的数据包可以看到提交路径是/dedecmsgbk/include/dialog/select_images_post.php,跟进这个文件看一下进行了怎样的处理。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/5af05dc2-2493-48ce-aed4-2e6c513f0e02_1653994096.png)
在select_images_post.php文件中的第36行,对文件名称进行了正则替换,正则会匹配回车符、换行符、制表符、*、 % 、/ 、?、、 |、 “、 :、
并至少匹配1次,把匹配到的内容替换成‘ ’(空),因为我们通过抓包把文件名称改成了1.png.p*hp,所以经过替换会变成1.png.php。
![](https://files.mdnice.com/user/31365/1605666d-c570-4521-80ab-bf74c4834365.png)
紧接着在第38行对文件名称再次验证,文件名中只需要存在jpg、gif、png中任意一个,如果不存在程序就会提示错误信息,但这里有一个非常大的缺陷,就是程序只是验证文件名称中存在jpg、gif、png三个中的任意一个,并不是在验证文件的后缀。所以我们上传的文件名称1.png.php是可以绕过这个限制的。既然这个限制这么轻松就可以绕过,那我们可不可以直接把文件名称改成1.png.php,而不是1.png.p*hp呢?这个问题最后会进行解答。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/447d959b-0bb2-4a79-a043-31f48912b6d6_1653994099.png)
程序在第44行对上传文件的MIME类型进行验证,这里进行白名单验证,在$sparr
数组中定义了六个允许上传的MIME类型,然后把我们上传文件的MIME去除两端空格并转变成小写得到$imgfile_type
,然后判断$imgfile_type
是否在数组$sparr
,如果不存在程序就会提示错误信息。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/e9d90cf5-ef59-410b-be99-0da0f1eb28f6_1653994102.png)
漏洞的产生还有一个非常重要的原因,从第57行开始分析,用户的UserID拼接上\’-\’再拼接上一段随机字符形成$filename_name
,$mdir
是年(年份后两位)月日,$mdir
跟$filename_name
拼接形成$filename=$mdir/$filename_name
,然后使用explode函数按照\’.\’分割文件名$imgfile_name
,形成数组$fs
内容(\’1\’,\’png\’,\’php\’),然后取出数组中的最后一个元素拼接到了$filename_name
参数后面组成文件名,而数组的最后一个元素正好是PHP,所以PHP文件就可以上传了。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/36aad86c-b4e6-455a-8e16-cfe657151de2_1653994104.png)
并且在最后也可以看到完成的路径。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/c6524006-2570-4fec-8c57-46cd7db7b4e1_1653994107.png)
程序的最后就是把上传文件的信息保存到了数据库中。
遗留问题
上面我们留下了两个疑问:1.直接上传PHP文件可不可行,2.为什么部分脚本文件上传失效。接下来我们将解决这两个问题。
1.直接上传PHP文件可不可以
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/80705281-82ab-414c-a0c7-6bae60e734e2_1653994110.png)
从返回信息中可以看到,不允许我们上传这种类型的文件,在select_images_post.php文件中包含了config.php文件,config.php文件包含了common.inc.php文件,common.inc.php文件包含了uploadsafe.inc.php文件,在uploadsafe.inc.php文件的第33行对文件的后缀进行了验证,定义了一些禁止上传的文件后缀$cfg_not_allowall
,所以直接上传PHP文件是不可以的,上传PHP文件要配合 select_images_post.php文件中替换为空的操作进行利用。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/2d753d31-63eb-4866-80b1-4323fe7b5966_1653994113.png)
这里的提示信息跟上面我们看到的是一样的。并且在这里面也发现了验证MIME类型。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/ad4b9167-395e-4655-9af5-9832fd1b7deb_1653994116.png)
2.为什么部分脚本文件上传不能利用
通过观察我们上传的PHP脚本文件,可以发现脚本文件被二次渲染了。
比如这个:
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/cd5da8c6-892d-432d-aef8-8ad9831dd707_1653994119.png)
但是对于PNG图片把恶意代码插入的IDAT数据块的脚本文件可以避免被二次渲染,并且可以成功利用。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/5ab7afef-19c3-40d2-8fd6-a1779bd65670_1653994123.png)
并成功执行命令。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/cb2e660c-b9cf-40bc-8e66-a2a412e50dbe_1653994126.png)
漏洞修复
修复方式一
在给文件名拼接后缀时,对后缀进行二次验证。
比如说在select_images_post.php的第60行添加如下代码。
如果再上传1.png.p*hp文件,程序执行到$fs[count($fs)-1]
会取出最后一个数组成员php,而$cfg_imgtype
是jpg|gif|png
,不包含,所以程序提示报错信息,上传失败。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/9fae2d6b-5e17-485c-8503-d066f736598c_1653994131.png)
修复方式二
在官方DedeCMS V5.7.93版本中,uploadsafe.inc.php文件中由原先只要文件名中包含$cfg_not_allowall
参数定义的这些文件后缀,改成了使用pathinfo()方法获取文件的后缀,然后判断后缀是否存在黑名单中,按照之前的文件名来说的话,这里获取的后缀是p*hp,依然不在黑名单数组中。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/97075b52-470c-4e72-b794-8c706769d79d_1653994134.png)
因为更新迭代,此时的富文本编辑器中的数据提交到了select_images_post_wangEditor.php文件中,这里的正则匹配特殊字符替换成空,但是这里的文件后缀也采用pathinfo()方法获取文件后缀,之前文件名1.png.p*hp经过特殊字符替换成空,然后pathinfo()方法获取获取到的文件后缀为php,这里的白名单$cfg_imgtype
是jpg|gif|png
,显然php并不在其中,所以返回提示信息”您所上传的图片类型不在许可列表”。
官方已修复该漏洞,请注意升级。补丁链接:https://www.dedecms.com/download。
漏洞总结
从上面可以知道上传p*hp
后缀的原因是在正则替换之前存在着一个黑名单验证,传入p*hp
后缀后可以绕过这个黑名单验证然后被正则把*替换为空,而文件后缀获取时会取出最后一个数组成员php,并没有进行二次验证,所以造成了这次文件上传漏洞的产生。
DedeCMS V5.7 SP2后台文件上传(CVE-2019-8362)
漏洞复现
复现环境:phpstudy、DedeCMS V5.7 SP2、php5.6.9
首先准备一个压缩包1.zip,压缩包里面的文件名为1.jpg.php,文件内容为:
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/bec256a0-b750-456d-b9f8-e5345fee3b3c_1653994137.png)
安装完成之后登录到系统后台,默认账号/密码是admin/admin,点击在左侧当导航栏中的核心按钮,然后选中附件管理中的文件式管理器,进入其中的soft目录中。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/a9ba34d6-14b1-4625-94ba-b34385eb4a2a_1653994140.png)
然后把之前准备好的压缩包上传上去。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/8180b9b5-34ba-4541-9231-f2983f2a39cb_1653994143.png)
然后访问album_add.php文件发布新图集,这里需要提前创建一个图集主栏目,上传方式选择从ZIP压缩包中解压图片,选择之前上传的1.zip。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/b7e466dc-a5c5-4414-8629-4125056db8ef_1653994145.png)
上传完成之后,我们点击预览文档。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/2e5ed5a5-017d-4eee-bab3-b1495441d95d_1653994148.png)
然后就会跳转到前台页面,点击下面的testZip。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/4cf3890f-14e5-4859-9cba-d295ab2c696b_1653994151.png)
当点击testZip,页面跳转,之前压缩包内的1.jpg.php里面的代码会执行。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/3ad86542-1e45-4c49-a556-04f9ab27c45d_1653994154.png)
我们来看一下这里正常上传图片的效果,当压缩包内是图片的话,这个会显示出图片,至于链接的话就是查看图片的链接。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/ee28aa99-ad26-4b36-947b-27e713508680_1653994158.png)
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/b822a2c0-f725-4c09-af5a-ae0444c50d8c_1653994161.png)
漏洞分析
使用burpSuite抓包,发现提交到了album_add.php文件,在/dede/album_add.php中找到这个文件,跟进程序看到底是怎么处理的。
album_add.php文件的前半部分都是一些验证赋值操作,或者是验证关于相关栏目的信息,但是因为上传的时候选择的是从压缩包中解压图片,所以$formzip
参数值为1,这个后面会用到。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/9d37f114-6bad-41da-8c87-b2d9bf317c71_1653994164.png)
因为$formzip
参数值为1,所以会解压压缩包中的图片,其实可以发现在程序的173行调用ExtractAll()方法完成解压操作,传入$zipfile
和$tmpzipdir
两个参数,$zipfile
是压缩包的保存路径,$tmpzipdir
是创建出来存在解压文件的路径。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/dc8b5566-1733-40b7-9eab-8de74b2773c5_1653994168.png)
跟进到ExtractAll()方法,查看程序的下一步执行。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/d3334325-d10f-4b55-bf0c-eb4cbf48ba65_1653994172.png)
在程序第309行会调用get_List()方法获取压缩包中的信息。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/13f13561-d7b0-44d0-8cb5-9963abf88e2b_1653994175.png)
这里要提一下ReadCentralFileHeaders()方法,在这个方法中会读取到压缩包中的文件名等信息。
然后回到ExtractAll()方法,接着调用Extract()方法解压单个文件,这个方法中也会调用ReadCentralFileHeaders()方法读取到压缩包中的文件名等信息,然后在361行调用ExtractFile()方法,并把获取压缩包中文件信息($header
)、压缩包路径($zip
)、创建的目录($to
)这三个参数一起传入。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/79561f4d-89f3-40a2-b401-7522131d5ad6_1653994178.png)
ExtractFile()方法中的大致流程就是首先会创建的目录下面创建一个gz文件,文件名称是$header[‘filename’].gz
拼接的,也就是1.jpg.php.gz,接下来在创建一个$header[‘filename’]
文件,此时这个文件名就是1.jpg.php,再把读取到的内容写入进去,这个过程中并没有对内容进行校验。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/b90a0d61-4ab6-48d1-af50-7a7cc5867760_1653994181.png)
从调试中可以看到,写入的内容就是上传压缩包中文件的内容,最后删除gz文件。完成解压。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/bfef7858-6e06-48db-8b41-13715ccf042c_1653994184.png)
完成解压之后程序回到album_add.php文件中,在程序第176行,程序调用了GetMatchFiles()方法,并且传入了三个参数,分别是$tmpzipdir
、jpg|png|gif
、$imgs
。
![](https://files.mdnice.com/user/31365/b18c4a4a-2add-489d-bf93-a6ce09479c32.png)
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/12514162-f68a-4d52-ae04-c760e35dafdb_1653994187.png)
在GetMatchFiles()方法中可以看到,其实就是读取$tmpzipdir
目录中的文件,在程序第163行会有正则匹配文件名中是否包含jpg|png|gif
,如果包含的话才会加到数组中,这也就是为什么要在文件名中加上jpg的原因,此时程序执行结束回到album_add.php文件, 继续跟进分析。
回到album_add.php文件。此时的$imgs
就是读取到的文件名,此时会进行循环操作,循环中首先会组成一个保存路径$savepath
,是由$cfg_image_dir
拼接上年月组成,$iurl
是由$savepath
进行一系列的拼接组成,最关键的点是在程序的第184行,取出$imgold
参数的最后四个字符,$imgold
就是GetMatchFiles()方法读取到的文件路径,其中最后四个字符就是.php
,然后拼接在$iurl
上面,$cfg_basedir
跟$iurl
组成文件名,所以此时的文件后缀就是php,然后利用copy()方法,把$imgold
中的内容复制到$iurl
中,并且删除之前创建的ziptmp中的目录,最后就是进行一些图片的尺寸以及数据库的操作。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/3f2b435c-163e-48a2-a271-ceab4e83a3ba_1653994191.png)
程序继续向下进行,在第209行,会把$iurl
参数参入到数据库中,就是把发表的信息都存入到数据库中。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/ec607b92-4e89-44b7-86eb-0248aee89637_1653994194.png)
最后如果选择删除压缩包,会把压缩包删除,再通过RmDirFiles()方法删除对应创建的目录$tmpzipdir
。
当前台调用的时候可以发现跳转的链接,点击超链接进入。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/d034cf8e-be62-41f0-b795-0bfb601c1ada_1653994197.png)
当要点击标题链接时,在左下角也会发现同样的URL,这个URL就是我们保存的PHP文件的路径。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/5f5f274a-3f3f-4be2-a4ca-4458c74773d9_1653994200.png)
并且从代码中我们也可以看见,这个链接是写死在HTML文件中的。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/7e3a86d1-2b82-4ae9-8d4c-e39b54020d38_1653994203.png)
所以点击链接就会跳转到PHP文件,执行文件中的代码。
漏洞修复
我们把/dede/file_class.php中第161行代码修改成:
1 |
else if (in_array(pathinfo($filename, PATHINFO_EXTENSION), explode("|", $fileexp),true) === TRUE) |
这里之前是验证文件名,现在改成验证文件后缀,后续就会验证文件后缀是否存在$fileexp
中了。
最新版中使用pathinfo()方法获取到文件的后缀,然后判断是否后缀是否在白名单数组中。然后再重复一次之前的上传流程,此时在这个断点处可以看到,php后缀并不满足判断条件,所以此时的文件名并不就加入到$filearr
中,$filearr
数组就是当时作为参数传入的$imgs
数组,因为$imgs
为空数组,所以接下来的循环复制操作也就不会执行了。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/151850c5-1247-41a9-b210-fd31e3de81cf_1653994206.png)
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/409ea852-c6a9-4665-93c3-7166ae4df28a_1653994209.png)
操作完成之后,再看之前的前台操作页面,已经没有之前的跳转链接了。
![](http://alphalab1-wordpress.stor.sinaapp.com/uploads/2022/05/26c986f2-b73a-441f-908a-e29213ca28b0_1653994212.png)
官方已修复该漏洞,请注意升级。补丁链接:https://www.dedecms.com/download。
漏洞总结
在分析过程中可以看到,对于压缩包中文件的后缀并没有进行验证,文件后缀的获取也没有进行二次验证,而是直接获取文件名称的最后四个字符拼接上去,最终造成了文件上传漏洞的产生。
总结
从上面两个实例中可以看到,第一个漏洞有用到黑名单验证,但并不是验证的文件后缀,两个漏洞都用白名单对文件名进行了验证,保存文件时对于文件后缀的获取并没有进行二次验证,而是直接获取拼接,所以才会造成文件上传漏洞的产生,造成系统的Getshell。对于上传文件其实也要对内容进行验证的,并且这两个地方本来都是上传图片的功能,验证上传文件的头部信息,对上传文件进行二次渲染,在保存文件时对后缀进行二次验证,这样就可以极大程度的避免文件上传漏洞的产生。