前言

审计iCMS 7.0.13的时候发现一天前刚刚爆出了一个新的CVE,CVE-2019-7160后台getshell漏洞。

于是跟进分析一下这个漏洞。

实验环境:osx+apache2+php7+mysql5.7
icms官网:https://www.icmsdev.com/

漏洞分析

大致流程

此漏洞需要先登录后台,利用do_IO()上传ZIP文件至根目录,再利用do_local_app()对ZIP文件进行解压生成shell文件。

上传文件

首先定位到do_IO()

public function do_IO(){
        files::$watermark_enable = $_GET['watermark'];
        $udir      = iSecurity::escapeStr($_GET['udir']);
        $name      = iSecurity::escapeStr($_GET['name']);
        $ext       = iSecurity::escapeStr($_GET['ext']);
        iFS::check_ext($ext,0) OR iUI::json(array('state'=>'ERROR','msg'=>'不允许的文件类型'));
        iFS::$ERROR_TYPE = true;
        $F = iFS::IO($name,$udir,$ext);
        $F ===false && iUI::json(iFS::$ERROR);
        iUI::json(array(
            "value"    => $F["path"],
            "url"      => iFS::fp($F['path'],'+http'),
            "fid"      => $F["fid"],
            "fileType" => $F["ext"],
            "image"    => in_array($F["ext"],files::$IMG_EXT)?1:0,
            "original" => $F["oname"],
            "state"    => ($F['code']?'SUCCESS':$F['state'])
        ));
    }

它处理通过流数据上传的文件,并且我们可控路径(udir),文件名(name),文件类型(ext)。

继续跟进$F = iFS::IO($name,$udir,​$ext);,定位到IFS类中的IO函数

public static function IO($FileName = '', $udir = '', $FileExt = 'jpg',$type='3',$filedata=null) {
        $filedata===null && $filedata = file_get_contents('php://input');
        if (empty($filedata)) {
            return false;
        }

        $fileMd5 = md5($filedata);
        $FileName OR $FileName = $fileMd5;
        $FileSize = strlen($filedata);
        $FileExt = self::valid_ext($FileName . "." . $FileExt); //判断文件类型
        if ($FileExt === false) {
            return false;
        }

        list($RootPath, $FileDir) = self::mk_udir($udir,$fileMd5,$FileExt); // 文件保存目录方式
        $FilePath = $FileDir . $FileName . "." . $FileExt;
        $FileRootPath = $RootPath . $FileName . "." . $FileExt;
        self::write($FileRootPath, $filedata);
        $fid = self::insert_filedata(array($FileName,'',$FileDir,'',$FileExt,$FileSize), $type);
        self::hook('upload',array($FileRootPath,$FileExt));
        $value = array(
            1,$fid,$fileMd5,$FileSize,
            '',$FileName,$FileName.".".$FileExt,
            $FileDir,$FileExt,
            $FileRootPath,$FilePath,$RootPath
        );
        return self::_data($value);
    }

这个函数以php://input读取数据,之后通过mk_udir函数创建文件存储路径。

我们可以通过控制udir和name两个变量使文件存放在任意位置。

例如

/icms/admincp.php?app=files&do=IO&frame=iPHP&ext=zip&udir=../&name=../app/test&watermark=fals

此时会在app目录下生成一个test.zip

解压文件

利用后台的安装本地应用功能。

定位到do_local_app()

public function do_local_app(){
      $zipfile = trim($_POST['zipfile']);
      echo $zipfile;
      if(preg_match("/^iCMS\.APP\.(\w+)\-v\d+\.\d+\.\d+\.zip$/", $zipfile,$match)){
        apps_store::$zip_file = iPATH.$zipfile;
        apps_store::$msg_mode = 'alert';
        apps_store::install_app($match[1]);
        iUI::success('应用安装完成','js:1');
      }else{
        iUI::alert('What the fuck!!');
      }
    }

首先会接受一个POST的数据包,如果符合正则匹配则进入install_app(),否则输出What the fuck!!。

跟进install_app()函数

public static function install_app($app=null) {
        self::$success = false;

        $archive_files = self::setup_zip();
        $msg = null;
        //安装应用数据
        $setup_msg = self::setup_app_data($archive_files,$app);

看到set_zip(),跟进一下

public static function setup_zip() {
        $zip_file = self::$zip_file;
        if(!file_exists($zip_file)){
            return self::msg("安装包不存在",false);
        }

        iPHP::vendor('PclZip');
        $zip = new PclZip($zip_file);
        if (false == ($archive_files = $zip->extract(PCLZIP_OPT_EXTRACT_AS_STRING))) {
            iFS::rm($zip_file);
            return self::msg("ZIP包错误",false);
        }

        if (0 == count($archive_files)) {
            iFS::rm($zip_file);
            return self::msg("空的ZIP文件",false);
        }
        return $archive_files;
    }

看到对文件进行解压操作,这就足够利用了。

再回到do_local_app()函数入,构造一个符合正则的文件名例如

iCMS.APP.1-v1.1.1.zip

GETSHELL

只要成功解压我们上传的ZIP文件,释放出php文件即可GETSHELL。

POC

构造文件

利用phpinfo()测试,把php文件压缩成一个ZIP

上传

发包上传文件,构造name和udir使得文件上传至根目录。

解压

getshell

源链接

Hacking more

...