• 代码审计-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

  • 相关阅读:
    VC++中使用内存映射文件处理大文件
    802.1x协议解析
    Jscript中window.setInterval和window.setTimeout区别
    在C#中使用代理的方式触发事件
    JavaScript实用的一些技巧
    控制C#编的程序内存的占用
    纯C#钩子实现及应用(转)
    DES的建立过程
    C# 显示占用内存
    解决“由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题”
  • 原文地址:https://www.cnblogs.com/-qing-/p/12350669.html
Copyright © 2020-2023  润新知