代码审计-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("\","'",""","<",">"),$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
------------------------------------------------------------------------------------