易优CMS RCE
漏洞分析
Eyou是由TP衍生出来的一款cms,一般审这种我喜欢先看看TP版本
/core/base.php
<?php
define('THINK_VERSION', '5.0.24');
define('THINK_START_TIME', microtime(true));
define('THINK_START_MEM', memory_get_usage());
define('EXT', '.php');
5.0.24最恶心的版本,先试着找一些可getshell的点,比如文件写入或上传之类的
/application/user/controller/Uploadify.php
public function delupload()
{
if (IS_POST) {
$action = input('post.action/s','del');
$filename= input('post.filename/s');
$filename= empty($filename) ? input('url') : $filename;
$filename= str_replace('../','',$filename);
$filename= trim($filename,'.');
$filename= trim($filename,'/');
if(eyPreventShell($filename) && $action=='del' && !empty($filename) && file_exists($filename)){
var_dump($filename);
$fileArr = explode('/', $filename);
if ($fileArr[2] != $this->users_id) {
return false;
}
$filetype = preg_replace('/^(.*)\.(\w+)$/i', '$2', $filename);
$phpfile = strtolower(strstr($filename,'.php')); //排除PHP文件
$size = getimagesize($filename);
$fileInfo = explode('/',$size['mime']);
if($fileInfo[0] != 'image' || $phpfile || !in_array($filetype, explode(',', config('global.image_ext')))){
exit;
}
if(@unlink($filename)){
echo 1;
}else{
echo 0;
}
exit;
}
}
}
在找的过程中发现了这么一处有意思的点unlink以及file_exists操作了filename,如filename可控的情况下,我们就可以通过phar来导致rce
$filename= input('post.filename/s');
$filename= empty($filename) ? input('url') : $filename;
$filename= str_replace('../','',$filename);
$filename= trim($filename,'.');
$filename= trim($filename,'/');
但是这里有一些过滤,简单看看,首先str_replace不用看。这个过滤很简单,直接/....//这样就可以过了,trim用了两次,主要针对.和/。但是这个函数主要是针对两侧,所以也根本影响我们的利用。但是测试的时候发现
直接把phar:给尼玛替换了,我想了一下问题也只能出在input获取层了,因为是tp的框架加上一般都不会去改它的获取函数就忽略了
'extra_file_list' => array(APP_PATH . 'helper' . EXT, THINK_PATH . 'helper' . EXT, APP_PATH . 'function' . EXT),
// 默认输出类型
'default_return_type' => 'html',
// 默认AJAX 数据返回格式,可选json xml ...
'default_ajax_return' => 'json',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
// 默认时区
'default_timezone' => 'PRC',
// 是否开启多语言
'lang_switch_on' => $lang_switch_on,
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => 'strip_sql,htmlspecialchars', // htmlspecialchars
在获取的时候通过了strip_sql,htmlspecialchars两个函数
function strip_sql($string)
{
$pattern_arr = array(
"/\bunion\b/i",
"/\bselect\b/i",
"/\bupdate\b/i",
"/\bdelete\b/i",
"/\boutfile\b/i",
// "/\bor\b/i",
"/\bchar\b/i",
"/\bconcat\b/i",
"/\btruncate\b/i",
"/\bdrop\b/i",
"/\binsert\b/i",
"/\brevoke\b/i",
"/\bgrant\b/i",
"/\breplace\b/i",
// "/\balert\b/i",
"/\brename\b/i",
// "/\bmaster\b/i",
"/\bdeclare\b/i",
// "/\bsource\b/i",
// "/\bload\b/i",
// "/\bcall\b/i",
"/\bexec\b/i",
"/\bdelimiter\b/i",
"/\bphar\b\:/i",
"/\bphar\b/i",
"/\@(\s*)\beval\b/i",
"/\beval\b/i",
);
$replace_arr = array(
'union',
'select',
'update',
'delete',
'outfile',
// 'or',
'char',
'concat',
'truncate',
'drop',
'insert',
'revoke',
'grant',
'replace',
// 'alert',
'rename',
// 'master',
'declare',
// 'source',
// 'load',
// 'call',
'exec',
'delimiter',
'phar',
'phar',
'@eval',
'eval',
);
return is_array($string) ? array_map('strip_sql', $string) : preg_replace($pattern_arr, $replace_arr, $string);
}
好了!破案了,这种替换我是没招的但是他写的很死!比如遇到phar:这样的时候才去替换,不会说因为出现了phar就去进行替换操作!那我们文章的str_replace这不就用上了?
payload:p../h../a../r://./p../har.p../har
可以看见完美了但是在进入到file_exists之前还有一个eyPreventShell函数
function eyPreventShell($data = '')
{
$data = true;
if (is_string($data) && (preg_match('/^phar:\/\//i', $data) || stristr($data, 'phar://'))) {
$data = false;
} else if (is_numeric($data)) {
$data = intval($data);
}
return $data;
}
他自己给传入的data复了一次值(不知道程序员怎么想)导致可以直接忽视这个函数
用一个echo来证明一下
完美