前言
有天和小透明聊起说他挖了一个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去调用即可