【SEACMS 8.9版本】从变量覆盖到变量覆盖的SQL注入漏洞

liudao  2004天前

1.jpg


0x01

最近审计了一波seacms,官网:https://www.seacms.net 。版本是最新版本的(审计时),和那些单入口框架不同,这个cms审计起来很圆润,但是过滤的就很干干巴巴,印象中好像看到了3个过滤拦截页面,360webscan(cmseasy也有)一个,好像自带还有两个,丧心病狂。但还是盘出来一个可以绕过的注入。


0x02

首先了解了下参数传递方式。核心文件是/include/common.php,很多文件都包含了这个文件。看下这个文件,114-117行:

foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
    foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

GET、POST和Cookie的参数会经过一次变量覆盖,然后把值$_v赋给对应的变量$$_k,这里会对$_v进行处理,单引号啥的都会被转义。这里既然可以进行变量覆盖,也就是说像一些全局变量我都可以控制!!!感觉这里问题很大。

接下来看20-26行:

foreach($_REQUEST as $_k=>$_v)
{
    if( strlen($_k)>0 && m_eregi('^(cfg_|GLOBALS)',$_k) && !isset($_COOKIE[$_k]) )
    {
        exit('Request var not allow!');
    }
}

这里的代码在上一步进行变量覆盖之前进行了一次过滤,不能覆盖全局变量$GLOBALS和一些配置变量(以$cfg_开头)。但是过滤的是$_REQUEST,也就是说只能过滤$_POST和$_GET。这里把想覆盖的变量放到在Cookie里面就可以绕过。比如下图,cfg_runmode,cfg_paramset两个配置变量的值都被赋值为1。

11.png


0x03

上一步确定了思路,接下来找一波包含common.php的文件。大概有这么多,忽略掉后台的。

12.png

这些文件在进行变量覆盖后都会做一些整数化处理,比如

14.png13.png

类似这种处理的地方特别多。


0x04

第二处变量覆盖。

在/detail/index.php文件中。echoContent($vId)方法中,第38行进行了sql语句拼接,$row=$dsql->GetOne("Select d.*,p.body as v_playdata,p.body1 as v_downdata,c.body as v_content From `sea_data` d left join `sea_playdata` p on p.v_id=d.v_id left join `sea_content` c on c.v_id=d.v_id where d.v_id='$vId'"),这里把$vId拼接了进去,并且需要单引号闭合才可以注入。

if($GLOBALS['cfg_runmode']==2||$GLOBALS['cfg_paramset']==0){
    $paras=str_replace(getfileSuffix(),'',$_SERVER['QUERY_STRING']);
    $id=intval($paras);
    $id = (isset($id) && is_numeric($id) ? $id : 0);
}else{
    $id=$$GLOBALS['cfg_paramid'];
}
if($id==0){
    showmsg('参数丢失,请返回!', -1);
    exit;
}

echoContent($id);

function echoContent($vId)
{
    global $dsql,$cfg_iscache,$mainClassObj,$t1,$cfg_user;
    $row=$dsql->GetOne("Select d.*,p.body as v_playdata,p.body1 as v_downdata,c.body as v_content From `sea_data` d left join `sea_playdata` p on p.v_id=d.v_id left join `sea_content` c on c.v_id=d.v_id where d.v_id='$vId'");
    if(!is_array($row)){ShowMsg("该内容已被删除或者隐藏","../index.php",0,10000);exit();}
    $vType=$row['tid'];

在第23行,调用了echoContent($id)方法。看下传进去的$id如何取值。

第16行可以看到,$id=$$GLOBALS['cfg_paramid'],这里进行了第二次变量覆盖。

15.png

这里有一个条件,if($GLOBALS['cfg_runmode']==2||$GLOBALS['cfg_paramset']==0),需要$cfg_runmode的值不为2并且$cfg_paramset的值不为0。这里很好绕过,在0x02中说过,把想覆盖的变量放到在Cookie里面就可以绕过。

接下来考虑把什么变量的值赋给$id。在最后一步想了很多方法,把注入代码放入参数里面都会被转义处理。然后考虑把注入代码放到$_SERVER变量传进去,这样可以绕过360webscan和自带的过滤。

这样又出现了一个问题。$_SERVER是数组,$id=$$GLOBALS['cfg_paramid']无法根据key取数组中的值,包括$_GET,$_POST,$GLOBALS等。并且在GET,POST中加注入代码会触发360webscan。

16.png

在这一步卡了很久,最后想到一个方法。


0x05

回到最开始,/include/common.php,114-117行:

foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
    foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

这里在循环完成后没有把$_k这个变量销毁,可以借用这个变量。在Cookie中加上cfg_paramid=_k,$$GLOBALS['cfg_paramid']的值即为$_k的值,然后在Cookie最后一个参数名加上注入代码,比如:1'and(updatexm l(1,concat(1,user(),1),1))and'1=123,$_k的值就被赋值为1'and(updatexm l(1,concat(1,user(),1),1))and'1,$id就被赋值为1'and(updatexm l(1,concat(1,user(),1),1))and'1。并且不会被拦截,也不会被转义。最后POC如下:

17.png


0x06

该漏洞已通报厂商和cnnvd,1月31号更新最新版本9.0已修复该漏洞。

有骚思路欢迎一起讨论,大佬勿喷!


本文由白帽汇原创,转载请注明来源:https://nosec.org/home/detail/2222.html

白帽汇从事信息安全,专注于安全大数据、企业威胁情报。

公司产品:FOFA-网络空间安全搜索引擎、FOEYE-网络空间检索系统、NOSEC-安全讯息平台。

为您提供:网络空间测绘、企业资产收集、企业威胁情报、应急响应服务。

最新评论

昵称
邮箱
提交评论