记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

thinksns(Arbitrary file upload)

2016-12-26 23:50

快元旦了,不知道该写哪一类的文章就只有发一些自己手里有的各种坚果了。都会慢慢的发出来的,有知名cms,也会有不是很知名的

漏洞文件:/apps/public/Lib/Action/AttachAction.class.php

public function ajaxUpload()
    {
        
        $d['type_name'] = 11;
        D('feedback_type')->add($d);
        $attach_type = t($_REQUEST['type']);

        $options['uid'] = $this->mid;

        
        $options['allow_exts'] = t(jiemi($_REQUEST['exts']));
        $options['allow_size'] = t(jiemi($_REQUEST['size']));
        $jiamiData = jiemi(t($_REQUEST['token']));
        list($options['allow_exts'], $options['need_review'], $fid) = explode('||', $jiamiData);
        $options['limit'] = intval(jiemi($_REQUEST['limit']));
        $options['now_pageCount'] = intval($_REQUEST['now_pageCount']);

        $data['upload_type'] = $attach_type;

        $info = model('Attach')->upload($data, $options);
        //ÉÏ´«³É¹¦
        echo json_encode($info);
}

可以看见存在很多变量都被控制,在上传中type这种名称的变量一般都是用于判断是否上传图片或者上传file等。这里我们选择定义成file,因为image几乎都是有图片类型限制的,file有时候会有疏忽。Exts这个变量一看就是用于类型判断或者后缀判断,但这里可以看见他在输入后还进入到了一个jiemi(解密)命名的函数,进入看看

function jiemi($text, $key = null)
{
    if (empty($key)) {
        $key = C('SECURE_CODE');
    }

    return tsauthcode($text, 'DECODE', $key);
}

当key为空的时候就从配置文件中获取key的值
	'DB_PORT'       => 3306,        // Êý¾Ý¿â¶Ë¿Ú
	'DB_PREFIX'     => 'ts_',// Êý¾Ý¿â±íǰ׺£¨ÒòΪÂþÓεÄÔ­Òò£¬Êý¾Ý¿â±íǰ׺±ØÐëдÔÚ±¾Îļþ£©
	'DB_CHARSET'    => 'utf8',      // Êý¾Ý¿â±àÂë
	'SECURE_CODE'   => '283415851f59f1f6fe',  // Êý¾Ý¼ÓÃÜÃÜÔ¿
	'COOKIE_PREFIX' => 'TS4_',      // # cookie
);

可以看见这里的code就是他的key,然后进入tsauthcode进行解密

function tsauthcode($string, $operation = 'DECODE', $key = '')
{
    $ckey_length = 4;
    $key = md5($key ? $key : SITE_URL);
    $keya = md5(substr($key, 0, 16));
    $keyb = md5(substr($key, 16, 16));
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    for ($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    for ($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    for ($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if ($operation == 'DECODE') {
        if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            return substr($result, 26);
        } else {
            return '';
        }
    } else {
        return $keyc.str_replace('=', '', base64_encode($result));
    }
}

是不是看着很熟悉,没错就是Ucenter的解密函数。看到这里了,我们先假象一种场景,如果我们得到了key,自定义后缀或者类型然后进行加密控制exts变量。如果这里exts是用于后缀的验证话,那么是不是就是任意文件上传?既然提出了这种假象,我们就先记录下,继续往下面看。

public function upload($data = null, $input_options = null, $thumb = false)
    {
        //echo json_encode($data);
        $system_default = model('Xdata')->get('admin_Config:attach');
        if (empty($system_default['attach_path_rule']) || empty($system_default['attach_max_size']) || empty($system_default['attach_allow_extension'])) {
            $system_default['attach_path_rule'] = 'Y/md/H/';
            $system_default['attach_max_size'] = '2';        // ĬÈÏ2M
            $system_default['attach_allow_extension'] = 'jpg,gif,png,jpeg,bmp,zip,rar,doc,xls,ppt,docx,xlsx,pptx,pdf';
            model('Xdata')->put('admin_Config:attach', $system_default);
        }
这里是判断我们的system_default的内容是否为空,当为空的时候就进入if中赋值
if ($data['upload_type'] === 'image') {
            $image_default = model('Xdata')->get('admin_Config:attachimage');
            $system_default['attach_max_size'] = $image_default['attach_max_size'];
            $system_default['attach_allow_extension'] = $image_default['attach_allow_extension'];
            $system_default['auto_thumb'] = $image_default['auto_thumb'];
        }

这里就是我们提到的type,当type为image的时候就对$system_default进行传值
  $default_options = array();
        $default_options['custom_path'] = date($system_default['attach_path_rule']);                    // Ó¦Óö¨ÒåµÄÉÏ´«Ä¿Â¼¹æÔò£º'Y/md/H/'
        $default_options['max_size'] = floatval($system_default['attach_max_size']) * 1024 * 1024;        // µ¥Î»: Õ×
        $default_options['allow_exts'] = $system_default['attach_allow_extension'];                    // 'jpg,gif,png,jpeg,bmp,zip,rar,doc,xls,ppt,docx,xlsx,pptx,pdf'
        $default_options['save_path'] = UPLOAD_PATH.'/'.$default_options['custom_path'];
        $default_options['save_name'] = ''; //Ö¸¶¨±£´æµÄ¸½¼þÃû.ĬÈÏϵͳ×Ô¶¯Éú³É
        $default_options['save_to_db'] = true;

这里在代码中有注释说明,是载入配置文件中的默认配置

if ($data['upload_type'] == 'image') {
      
            $cloud = model('CloudImage');
            if ($cloud->isOpen()) {
                return $this->cloudImageUpload($options);
            } else {
                return $this->localUpload($options);
            }
        }

这里就是判断我们的type是否是image,当是的时候则进入,这里的isOpen是判断是否开启云上传,当开启的时候则上传到云上,没开启的时候则进入localUpload这个上传中,我们可以看的时候没开的时候

private function localUpload($options)
    {
        // ³õʼ»¯ÉÏ´«²ÎÊý
        $upload = new UploadFile($options['max_size'], $options['allow_exts'], $options['allow_types']);
        // ÉèÖÃÉÏ´«Â·¾¶
        $upload->savePath = $options['save_path'];
        // ÆôÓÃ×ÓĿ¼
        $upload->autoSub = false;
        // ±£´æµÄÃû×Ö
        $upload->saveName = $options['save_name'];
        // ĬÈÏÎļþÃû¹æÔò
        $upload->saveRule = $options['save_rule'];
        // ÊÇ·ñËõÂÔͼ
        if ($options['auto_thumb'] == 1) {
            $upload->thumb = true;
        }

        // ´´½¨Ä¿Â¼
        mkdir($upload->save_path, 0777, true);

        // Ö´ÐÐÉÏ´«²Ù×÷
        if (!$upload->upload()) {
            // ÉÏ´«Ê§°Ü£¬·µ»Ø´íÎó
            $return['status'] = false;
            $return['info'] = $upload->getErrorMsg();

            return $return;
        } else {
            $upload_info = $upload->getUploadFileInfo();
            // ±£´æÐÅÏ¢µ½¸½¼þ±í
            $data = $this->saveInfo($upload_info, $options);
            // Êä³öÐÅÏ¢
            $return['status'] = true;
            $return['info'] = $data;
            // ÉÏ´«³É¹¦£¬·µ»ØÐÅÏ¢
            return $return;
        }
}

前面的一系列传值我们跳过,直接进入upload中看

foreach ($files as $key => $file) {
            //¹ýÂËÎÞЧµÄÉÏ´«
            if (!empty($file['name'])) {
                $file['key'] = $key;
                $file['extension'] = $this->getExt($file['name']);
                $file['savepath'] = $savePath;
                $file['savename'] = uniqid().substr(str_shuffle('0123456789abcdef'), rand(0, 9), 7).'.'.$file['extension'];
                //$this->getSaveName($file);

                if ($GLOBALS['fromMobile'] == true && empty($file['extension'])) {
                    //Òƶ¯É豸ÉÏ´«µÄÎÞºó׺µÄͼƬ£¬Ä¬ÈÏΪjpg
                        $file['extension'] = 'jpg';
                    $file['savename'] = trim($file['savename'], '.').'.jpg';
                } else {
                    // ×Ô¶¯¼ì²é¸½¼þ
                    if ($this->autoCheck) {
                        if (!$this->check($file)) {
                            return false;
                        }
                    }
                }
首先是遍历FILES信息把相应的值赋给对应的变量,然后
if ($GLOBALS['fromMobile'] == true && empty($file['extension'])) {
                    //Òƶ¯É豸ÉÏ´«µÄÎÞºó׺µÄͼƬ£¬Ä¬ÈÏΪjpg
                        $file['extension'] = 'jpg';
                    $file['savename'] = trim($file['savename'], '.').'.jpg';

这里的frommobile默认是开启的true,但这里的$file[‘extension’]在上面遍历的时候有赋值所以不为空则为假不进入

else {
                    // ×Ô¶¯¼ì²é¸½¼þ
                    if ($this->autoCheck) {
                        if (!$this->check($file)) {
                            return false;
                        }
                    }
                }

重点是check检查类型等

if (!$this->checkType($file['type'])) {
            $this->error = 'ÉÏ´«ÎļþMIMEÀàÐͲ»ÔÊÐí£¡';

            return false;
        }
        //¼ì²éÎļþÀàÐÍ
        if (!$this->checkExt($file['extension'])) {
            $this->error = 'ÉÏ´«ÎļþÀàÐͲ»ÔÊÐí';

            return false;
        }

类型是可以绕的,checkext瞅瞅

private function checkExt($ext)
    {
        if (in_array($ext, array('php', 'php3', 'exe', 'sh', 'html', 'asp', 'aspx'))) {
            $this->error = '²»ÔÊÐíÉÏ´«¿ÉÖ´ÐеĽű¾Îļþ£¬È磺php¡¢exe¡¢htmlºó׺µÄÎļþ';

            return false;
        }

        if (!empty($this->allowExts)) {
            return in_array(strtolower($ext), $this->allowExts, true);
        }

        return true;
    }

首先第一个in_array这里的判断一看就是黑名单,大小写什么的直接就可以绕过,而这里的allowexts则是我们最初的加密的那个内容。这里是当不为空的时候则判断,所以我们不填此参数达到绕过

$return['info'] = $upload->getErrorMsg();

            return $return;
        } else {
            $upload_info = $upload->getUploadFileInfo();
            // ±£´æÐÅÏ¢µ½¸½¼þ±í
            $data = $this->saveInfo($upload_info, $options);
            // Êä³öÐÅÏ¢
            $return['status'] = true;
            $return['info'] = $data;
            // ÉÏ´«³É¹¦£¬·µ»ØÐÅÏ¢
            return $return;
        }

由于上传到云是需要管理自己去配置的,所以默认是不开启,只要采用的是正常上传都会存在。本地亲测试成功复现.

本文由91ri团队原创,转载请注明出处。

 

知识来源: www.91ri.org/16550.html

阅读:100094 | 评论:0 | 标签:代码审计 漏洞利用

想收藏或者和大家分享这篇好文章→复制链接地址

“thinksns(Arbitrary file upload)”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云