前言
昨天看了极致CMS1.7,今天再来看看极致CMS1.9版本修复了什么漏洞,哪些漏洞还能用,是不是还有新的东西。
下载链接:
极致CMS
这次我下的是1.9.2的稳定版。
下载到本地搭建一下环境,然后就是安装。安装的时候注意到了这里:
我记得1.7版本安装的时候管理员和密码默认都是空的让自己填,这里的话默认的管理员和密码格式大概是jizhicms加上四个数字,这玩意怎么说呢,说是弱口令叭,但是9999*9999也挺难爆的,去爆破还不如找洞舒服。
看看后台
安装好之后还是先进一下后台,登录那里发现验证码那里比1.7的验证码难识别的多,感觉靠百度的那个基本不行了。而且试了一下,一个验证码可以利用无数次仍然没改,所以这个验证码的用处其实不大。
进入后台还是老样子传马试试,这次改了配置增加php的文件类型,仍然无法上传成功。
看一下源码;
$fileType = $this->webconf['fileType'];
if(strpos($fileType,strtolower($pix))===false || stripos($pix,'php')!==false){
$data['error'] = "Error: 文件类型不允许上传!";
$data['code'] = 1002;
JsonReturn($data);
}
额外增加了对php的过滤,因此没法直接上传php了。考虑上传phtml这样的,但是默认肯定是不解析的,考虑传.htaccess,但是因为文件名不可控也不行。又全局查找了一下,能上传文件的有5个地方,全都进行了php后缀的过滤,因此文件上传这块感觉就已经彻底GG了。
又拿1.7版本的SQL注入的EXP打了一下也没成功,看一下代码上发生了什么改变:
$openid = format_param($openid,1);
$islive = M('member')->find(array('openid'=>$openid));
发现对$openid
进行了一层format_parm函数的过滤,感觉有点眼熟啊,跟进一下:
/**
参数过滤,格式化
**/
function format_param($value=null,$int=0,$default=false){
if($value==null){ return '';}
if($value===false && $default!==false){ return $default;}
switch ($int){
case 0://整数
return (int)$value;
case 1://字符串
$value = SafeFilter($value);
$value=htmlspecialchars(trim($value), ENT_QUOTES);
if(version_compare(PHP_VERSION,'7.4','>=')){
$value = addslashes($value);
}else{
if(!get_magic_quotes_gpc())$value = addslashes($value);
}
return $value;
case 2://数组
if($value=='')return '';
array_walk_recursive($value, "array_format");
return $value;
case 3://浮点
return (float)$value;
case 4:
if(version_compare(PHP_VERSION,'7.4','>=')){
$value = addslashes($value);
}else{
if(!get_magic_quotes_gpc())$value = addslashes($value);
}
return trim($value);
}
}
原来是这个过滤函数,SafeFilter是进行XSS过滤的不用管,$value=htmlspecialchars(trim($value), ENT_QUOTES);
把单双引号都给html转义了,
此外还有一层对单双引号这样的加反斜杠的过滤。
再接着看一下find函数有什么变化:
跟进findAll方法,主要的变化就是加了一层这个:
$conditions = $this->__prepera_format($conditions);
具体不分析了,跟进一下看看代码逻辑,发现对于我们要进行SQL注入的话并无影响。接下来的就是进入query函数,也是有了变化:
//执行 SQL 语句,返回PDOStatement对象,可以理解为结果集
public function query($sql){
$this->arrSql[] = $sql;
$this->Statement = $this->pdo->query($sql);
if ($this->Statement) {
return $this;
}else{
$msg = $this->pdo->errorInfo();
if($msg[2]){
//Error_msg('数据库错误:' . $msg[2] . end($this->arrSql));
$log_name = date('Y-m-d-H-i-s-').time();
register_log('数据库错误:' . $msg[2] . end($this->arrSql),$log_name);
exit;
}
}
}
1.7版本是会直接把错误信息echo出来,这里的话是写入日志。不过没啥用,只要能注入的话就直接堆叠了,也不需要用到报错注入。
经过这波分析,基本可以确定的就是,CMS本身的find方法是存在漏洞的(说是漏洞也不太好),也就是说,find方法的第一个参数并没有在find方法内进行过滤,还是在进入find方法前进行了一波format_param函数的过滤,因此现在的思路就是找一个开发的遗漏,类似find方法这样的注入参数可控而且因为开发的疏忽,并没有进行format_param的过滤,就可以实现SQL注入了。
之前版本除去wechat这里的SQL注入外,url上的注入也是非常容易利用的,但是试了一下发现也不太行,REQUEST_URI
都会被html进行转义,跟进了一下,发现是Fr.php的route方法的第207行调用了format_param,相当于对$_SERVER['REQUEST_URI']
这整个部分都进行了一次过滤,因此关于路径上的SQL注入就彻底GG了。
尝试挖掘SQL注入(失败)
全局搜索了find方法一点一点的看,首先是这个HomeController下面的差点就成功的:
$id
可以通过路由或者get之类的传参,路由的话之前分析过了,都会被html编码一次,因此这里get传的话是一点都没有被过滤的,直接拼接进SQL语句:
$details = M($this->type['molds'])->find(array('id'=>$id,'isshow'=>1));
我本来想着已经成功了,结果发现不行,跟进一下会发现M($this->type['molds'])
出了问题:
跟进入就会发现本来的::table
就是jz_menu了,结果这里又拼接了一次jz_:
self::$table = DB_PREFIX.strtolower(self::$table);
导致查的表变成了jz_jz_menu
,然后就是前面我说的那个不影响SQL注入的预处理,把我自己打败了:
//预处理SQL
private function __prepera_format($rows)
{
$table = self::$table;
$stmt = $this->db->getTable($table);
$stmt->execute();
$columns = $stmt->fetchAll(PDO::FETCH_CLASS);
$newcol = array();
foreach ($columns as $key => $value) {
$field = strtolower($value->Field);
if(stripos($value->Type,'int')!==false || stripos($value->Type,'decimal')!==false){
if(isset($rows[$field])){
if($rows[$field]!=='' && $rows[$field]!==false){
$newcol[$field] = $rows[$field];
}else{
$newcol[$field] = 0;
}
}
}else{
if(isset($rows[$field])){
if($rows[$field]!=='' && $rows[$field]!==false ){
$newcol[$field] = $rows[$field];
}else{
$newcol[$field] = null;
}
}
}
}
return $newcol;
//return array_intersect_key($rows,$newcol);
}
他会先预查一次表,把然后传入的$rows
的键名在预查的表中才行,说白了就是如果正常查jz_menu
表,表中有id这一列,因此就不影响。但是这里查的是jz_jz_menu
表了,查不到任何东西,因此$rows
这里就被扔掉了,加上查的是不存在的表,直接报错,也就失败了,草了气死我了。
看了一下1.7版本同样存在这个问题,这个主要的原因主要还是在于正常的话会传$table
的名字,因此不会出现前面有前缀的情况。但是遇到默认的情况的话,开发处理的就有些问题了,
后来把find方法看完了,感觉没一个能SQL注入的,其他的update啥的以后再看了,太难了。
插件
这个插件仍然可以自由编辑代码安装后配置一下密码,再点配置输入密码仍然可以写文件,拿到shell。不过确实这是插件本身功能的问题,要不直接把这个插件删掉,要么就是更改功能,只能编辑html这样的静态文件,要么就是后台添加一个验证,安装插件需要验证一个单独的密码,这样可能才会好一点。不过现在SQL注入给修了,除了弱密码, 后台基本都进不来,所以其实挺安全的了。
此外1.7版本中任意下载指定url的zip文件然后解压的漏洞1.9.2版本也没有修。
任意文件夹下载那里倒是加上了一层过滤:
因此没法任意下载文件夹了。
总结
总的来说1.9版本基本上已经算是牢不可破了,前台想要SQL注入真是tm的难,我现在就期待开发赶紧修了那个双层表前缀的问题(笑),不知道别的师傅挖到了SQL注入没。
这个下午基本上还是没审出什么东西来,还是太菜了,明天有空再看一天,还找不到洞就算了。呜呜呜太菜了。