代码审计-phpok框架5.3注入漏洞

0x01 框架路由和底层过滤

路由:

入口:

index.php,api.php,admin.php

init.php 2804行,新建$app对象,实例化的PHPOK的主类

$app = new _init_phpok();
include_once($app->dir_phpok."phpok_helper.php");
$app->init_site();
$app->init_view();

 

 

 然后进行行为处理:

根据入口文件的不同,再调用不同的action方法。

    /**
     * 执行应用,三个入口(前端,接口,后台)都是从这里执行,进行初始化处理
     * token 及 user_id 在 phpok5.0 中将剥离,不会放在核心引挈里
    **/
    final public function action()
    {
        $this->init_assign();
        $this->init_plugin();
        if($this->app_id == 'admin'){
            $this->action_admin();
            exit;
        }
        $this->_userToken();
        if($this->app_id == 'api'){
            $this->action_api();
            exit;
        }
        $this->action_www();
        exit;
    }

 

 

get函数中的过滤

final public function get($id,$type="safe",$ext="")
    {
        $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'){
                return 0;
            }else{
                return '';
            }
        }
        //判断内容是否有转义,所有未转义的数据都直接转义
        $addslashes = false;
        if(function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()){
            $addslashes = true;
        }
        if(!$addslashes){
            $val = $this->_addslashes($val);
        }
        return $this->format($val,$type,$ext);
    }
_addslashes:
    private function _addslashes($val)
    {
        if(is_array($val)){
            foreach($val as $key=>$value){
                $val[$key] = $this->_addslashes($value);
            }
        }else{
            $val = addslashes($val);
        }
        return $val;
    }

 

format:
final public function 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]);
                        continue;
                    }
                }
                $msg[$key] = $this->format($value,$type,$ext);
            }
            if($msg && count($msg)>0){
                return $msg;
            }
            return 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);
            return addslashes($msg);
        }
        $msg = stripslashes($msg);
        //格式化处理内容
        switch ($type){
            case 'safe_text':
                $msg = strip_tags($msg);
                $msg = str_replace(array("\\","'",'"',"<",">"),'',$msg);
            break;
            case 'system':
                $msg = !preg_match("/^[a-zA-Z][a-z0-9A-Z\_\-]+$/u",$msg) ? false : $msg;
            break;
            case 'id':
                $msg = !preg_match("/^[a-zA-Z][a-z0-9A-Z\_\-]+$/u",$msg) ? false : $msg;
            break;
            case 'checkbox':
                $msg = strtolower($msg) == 'on' ? 1 : $this->format($msg,'safe');
            break;
            case 'int':
                $msg = intval($msg);
            break;
            case 'intval':
                $msg = intval($msg);
            break;
            case 'float':
                $msg = floatval($msg);
            break;
            case 'floatval':
                $msg = floatval($msg);
            break;
            case 'time':
                $msg = strtotime($msg);
            break;
            case 'html':
                $msg = $this->lib('string')->safe_html($msg,$this->url);
            break;
            case 'func':
                $msg = function_exists($ext) ? $ext($msg) : false;
            break;
            case 'text':
                $msg = strip_tags($msg);
            break;
            default:
                $msg = str_replace(array("\\","'",'"',"<",">"),array("&#92;","&#39;","&quot;","&lt;","&gt;"),$msg);
            break;
        }
        if($msg){
            $msg = addslashes($msg);
        }
        return $msg;
    }

 

进行了转义和编码 但是在api接口orderby变量拼接时不需要单引号等预定义字符闭合语句,导致转义编码失效,造成注入。

 

 

 

 

0x02 注入分析

注入成因位置:framework/model/list.php 的907行(arc_all函数当中)

$orderby可控 

 

 

发现framework/phpok_call.php中的_arclist函数中调用了arc_all 函数

        $this->cache->save($cache_id,$array);
            }
            return $array;
        }
        $orderby = $rs['orderby'] ? $rs['orderby'] : $project['orderby'];
        if(!$rs['is_list']){
            $rs['psize'] = 1;
        }
        $offset = $rs['offset'] ? intval($rs['offset']) : 0;
        $psize = $rs['is_list'] ? intval($rs['psize']) : 1;
        $rslist = $this->model('list')->arc_all($project,$condition,$field,$offset,$psize,$orderby);
        if(!$rslist){
            if($cache_id){
                $this->cache->save($cache_id,$array);
            }
            return $array;
        }
        $ids = array();
        foreach($rslist as $key=>$value){
            $ids[] = $value['id'];
            foreach($nlist as $k=>$v){
                $myval = $this->lib('form')->show($v,$value[$k]);
                if($v['form_type'] == 'url' && $value[$k]){
                    $tmp = unserialize($value[$k]);

 

 

 

_arclist函数中 $orderby变量是通过$rs来赋值的   跟踪$rs

//读取文章列表
    private function _arclist($rs,$cache_id='')
    {
        if(!$rs['pid'] && !$rs['phpok']){
            return false;
        }
        if(!$rs['pid']){
            $tmp = $this->model('id')->id($rs['phpok'],$rs['site'],true);
            if(!$tmp || $tmp['type'] != 'project'){
                unset($tmp,$rs);
                return false;
            }
            $rs['pid'] = $tmp['id'];
            unset($tmp);
        }
        if(!$rs['pid']){
            unset($rs);
            return false;
        }
        $project = $this->_project(array('pid'=>$rs['pid'],'site'=>$rs['site']));
        if(!$project || !$project['status'] || !$project['module']){
            return false;
        }
        $module = $this->model('module')->get_one($project['module']);
        if(!$module || $module['status'] != 1){
            return false;
        }
        //如果使用了独立模块
        if($module['mtype']){
            return $this->_arclist_single($rs,$cache_id,$project,$module);
        }
        $array = array('project'=>$project);
        $this->model('list')->is_biz($project['is_biz']);
        $this->model('list')->is_biz($project['is_userid']);
        $this->model('list')->multiple_cate(0);
        if($project['cate']){
            $this->model('list')->multiple_cate($project['cate_multiple']);
        }
        if($project['cate'] || $rs['cateid']){
            $cateid = $rs['cateid'] ? $rs['cateid'] : $project['cate'];
            $cate = $this->_cate(array("pid"=>$rs['pid'],"cateid"=>$cateid,'site'=>$rs['site']));
            if($cate){
                $array['cate'] = $cate;
                $rs['cateid'] = $cateid;
            }
        }
        $flist = $this->model('module')->fields_all($project['module']);
        $nlist = array();
        if($project['cate'] && $project['cate_multiple']){
            $list_fields = $this->model('fields')->list_fields();
            $field = 'DISTINCT l.id';
            foreach($list_fields as $key=>$value){
                if($value == 'id'){
                    continue;
                }
                $field .= ",l.".$value;
            }
        }else{
            $field = "l.*";
        }
        if($rs['fields'] && $rs['fields'] != '*'){
            $tmp = explode(",",$rs['fields']);
            if($flist){
                foreach($flist as $key=>$value){
                    if(in_array($value['identifier'],$tmp)){
                        $field .= ",ext.".$value['identifier'];
                        $nlist[$value['identifier']] = $value;
                    }
                }
            }
        }else{
            if($flist){
                foreach($flist as $key=>$value){
                    $field .= ",ext.".$value['identifier'];
                    $nlist[$value['identifier']] = $value;
                }
            }
        }
        if($project['is_biz']){
            $field.= ",b.price,b.currency_id,b.weight,b.volume,b.unit";
        }
        $condition = $this->_arc_condition($rs,$flist,$project);
        $array['total'] = $this->model('list')->arc_count($project['module'],$condition);
        if(!$array['total']){
            if($cache_id){
                $this->cache->save($cache_id,$array);
            }
            return $array;
        }
        $orderby = $rs['orderby'] ? $rs['orderby'] : $project['orderby'];
        if(!$rs['is_list']){
            $rs['psize'] = 1;
        }
        $offset = $rs['offset'] ? intval($rs['offset']) : 0;
        $psize = $rs['is_list'] ? intval($rs['psize']) : 1;
        $rslist = $this->model('list')->arc_all($project,$condition,$field,$offset,$psize,$orderby);
...

 

发现$rs为_arclist函数传参进来的,所以跟踪谁调用了_arclist函数

 

发现在framework/api/project_control.php中load_model函数中  209行调用了_arclist函数 并且传入了$dt  所以要跟踪$dt

if(!$pageid) $pageid = 1;
        $offset = ($pageid-1) * $psize;
        $dt['offset'] = $offset;
        $dt['psize'] = $psize;
        $dt['is_list'] = 1;
        if($attr){
            $dt['attr'] = $attr;
        }
        $fields = $this->get('fields');
        if(!$fields){
            $fields = '*';
        }
        $dt['fields'] = $fields;
        $info = $this->call->phpok('_arclist',$dt);
        unset($dt);
        if(!$info['rslist']){
            $this->error(P_Lang('已是最后一条数据'));
        }
...

 

 

 

 

 

发现$dt[‘orderby’]是通过$sort这个变量赋值的  所以跟踪$sort

//自定义排序
        if($sort){
            $dt['orderby'] = $sort;
            $pageurl .= '&sort='.rawurlencode($sort);
            $this->rlist['sort'] = $sort;
        }
        if(substr($pageurl,-1) == "&" || substr($pageurl,-1) == "?"){
            $pageurl = substr($pageurl,0,-1);
        }
...

 

 

 

发现$sort是通过get函数接受   此时是在load_module函数当中

 

 

 

发现在framework/api/project_control.php中的index函数调用了load_module

发现存在$project $parent_rs变量 所以跟踪这两个变量

 

$tag = $this->get("tag");
        $uid = $this->get('uid','int');
        $attr = $this->get('attr');
        //价格,支持价格区间
        $price = $this->get('price','float');
        $sort = $this->get('sort');
        //读取列表信息
        $psize = $rs["psize"] ? $rs['psize'] : $this->config['psize'];
        $pageurl = $this->url($rs['identifier']);

 

index_f:

//栏目
    public function index_f()
    {
        $id = $this->get("id");
        if(!$id){
            $this->error(P_Lang('未指ID'));
        }
        $tmp = $this->model('id')->id($id,$this->site['id'],true);
        if(!$tmp || $tmp['type'] != 'project'){
            $this->error(P_Lang('项目不存在'));
        }
        $pid = $tmp['id'];
        if(!$this->model('popedom')->check($pid,$this->user_groupid,'read')){
            $this->error(P_Lang('您没有阅读权限,请联系网站管理员'));
        }
        $project = $this->call->phpok('_project',array('pid'=>$pid));
        if(!$project || !$project['status']){
            $this->error(P_Lang('项目不存在或未启用'));
        }
        if($project["module"] && !$project['is_api']){
            $this->error(P_Lang('此项目接口不可用'));
        }
        $this->rlist['page_rs'] = $project;
        if($project['parent_id']){
            $parent_rs = $this->call->phpok('_project',array('pid'=>$project['parent_id']));
            if(!$parent_rs || !$parent_rs['status']){
                $this->error(P_Lang('父级项目不存在或未启用'));
            }
            $this->rlist['parent_rs'] = $parent_rs;
        }
        if($project["module"]){
            $this->load_module($project,$parent_rs);
        }
        //没有进入success函数
        $this->success($this->rlist);
    }

 

 load_module函數需要两个变量参数,条件如下。

 

$project:

 

 

 

构造链接:

 

 

 

$parent_rs:

 

 构造链接:

 

 

 

 

 poc

 

posted @ 2020-02-23 17:30  卿先生  阅读(758)  评论(0编辑  收藏