Metinfo注入(会员中心以及某种限制)

前言

有天和小透明聊起说他挖了一个metinfo的前台注入就想着自己也去挖一个,刚好上一次挖metinfo还是几年前了。

漏洞分析

  public function dochangepaygroup()
    {
        global $_M;
   
        $payclass = load::mod_class('pay/pay_op','new');
        $data = $payclass->de_code($_M['form']['codestr']);
        if ($data['no'] != $this->no) {
            return false;
        }
        $paygroup = jsondecode(base64_decode($data['attach']));

        if(bccomp((float)$data['total_fee'], (float)$paygroup['price']) !== 0){
            return false;
        }else{
            if ($data['total_fee'] != $paygroup['price']) {
                return false;
            }
        }
        $group_class = load::mod_class('user/user_op', 'new');

        $group_class->modity_group($data['uid'],$paygroup['groupid']);

    }

这里的$_M['form']['codestr']简单点的理解就是metinfo写的一种请求获取方式,from代表的是POST,codestr代表的是参数名。而我们从代码里面可以看见获取了codestr后经过了de_code以及base64_decode还有jsondecode,总共四层decode,如果我们在de_code保证内容可控的情况下,这个点已经是无视waf了的,我们继续跟进

   public function modity_group($uid, $group){
        global $_M;
        $sysuer = load::sys_class('user','new');
        $usre_group = $sysuer->editor_uesr_gorup($uid,$group);
        return $usre_group;
    }


}

传入的参在这里还未做任何操作,继续跟进

public function editor_uesr_gorup($userid, $group) {
        global $_M;
        if (!$userid) return false;
        if (!$this->get_user_by_id($userid)) return false;

        $mgroup = load::sys_class('group', 'new');
        $grouplist = $mgroup->get_group_list();
        $arr = array();
        foreach ($grouplist as $val) {
            $arr[] = $val['id'];
        }
        if (!in_array($group, $arr)) {
            return false;
        }

        $query = "UPDATE {$_M['table']['user']} SET groupid = '{$group}' WHERE id = '{$userid}'";
        DB::query($query);
        return $group;
    }

serid和group我们假设都可控的情况下,看看在这里有没可利用点,

 if (!$userid) return false;        if (!$this->get_user_by_id($userid)) return false;

userid非空时进入get_user_by_id函数,跟进

    public function get_user_by_id($id) {
        $user = $this->get_user_by_id_sql($id);
        return $this->analyze($user);
    }

直接带入get_user_by_id_sql

  public function get_user_by_id_sql($id) {
        global $_M;
        $query = "SELECT * FROM {$_M['table']['user']} WHERE id='{$id}'";
        $user = DB::get_one($query);
        return $user;
    }

以单引号包裹进入了sql查询,但因为这里经过了多次的decode并不受gpc等全局过滤影响所以我们在保证内容可控的情况下是可以导致注入,回跟一下de_code函数

 public function de_code($code) {
        global $_M;
        if($code=='')return false;
        $code = str_replace(' ', '+', urldecode($code));
        list($true_code, $time, $str, $is_array_flag,$lang) = explode('$M$', $code);
        $_M['lang'] = $lang;
        $c1 = substr($true_code, 0, 7);
        $c2 = substr($true_code, 9, 2);
        $c3 = substr($true_code, 13, 11);
        $c4 = substr($true_code, 26, 5);
        $c5 = substr($true_code, 33, 7);
        $true_code = $c1.$c2.$c3.$c4.$c5;
        if(md5($str.$this->key.$time) == $true_code){
            if($is_array_flag == 1){
                return jsondecode(base64_decode($str));
            }else{
                return base64_decode($str);
            }
        }else{
            return false;
        }
    }

整个de_code的核心在于

if(md5($str.$this->key.$time) == $true_code){
            if($is_array_flag == 1){
                return jsondecode(base64_decode($str));
            }else{
                return base64_decode($str);
            }
        }else{
            return false;
        }

用md5后(key与时间以及字符串拼接后的内容)去与true_code作匹配,而我们的key来自

    public function __construct()
    {
        global $_M;
        $this->payparams = array();
        $this->key = '848068e8fdde035d979196339b9a55b6';
        $this->validity = '';
    }

属于硬编码,而非安装时生成。注入的话!我们只需要构造en_code去调用即可

2 个赞