php反序列化基础知识前奏
文章初衷
序列化?反序列化?好高大尚的词语,在学习think PHP5版本的框架反序列化时,多序列化和反序列化的过程以及函数的触发调用有了很大的感触。遂此篇文章来做一个分享学习,若文章中出现一些知识点的错误,请大佬指出。
反序列化前奏
序列化和反序列化的出现,是为了漏洞的出现吗?答案当然是否,所谓的序列化是把对象/数据变成一串能存文件,字节传输的流,通俗来讲,一般会序列化为json格式或者xml格式,以及在PHP中的serializae()和unserialize()、python的pickle,而xml格式在现在的使用并不多见,故大多数是json的格式,那么反序列化就是相反的喽,便是将字符串转为对象。
那么为啥要搞序列化和反序列化呢?搞这个漏洞很多啊,POP链的构造从而导致RCE,这个出现的目的到底是为啥?网络上有很多的答案,但是我认为比较重要的有两点,一个时网络不能直接传输对象,前后端通信只能通过字符串进行,那么这个时候序列化的重要性就来了,他不就是转为字符串吗?第二个就是他能跨语言进行交互,Java对象序列化为json的格式中,给python和PHP都能用,这样不停方便吗?这就是本人认为的两大重要点。
那么这个漏洞是咋产生的呢?在序列化之后还是在序列化之前?漏洞产生的时间段是在序列化之后,因为我们可以进行构造一个具有危害的序列化之后的数据,就拿php来说可以使用代码执行和命令执行,从而来进行RCE,反序列化的最大利用点便是RCE,假如不能RCE,我要他干啥,没啥触发的。
php类与对象
类是啥?类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理,举个小例子。
php
<?php
class people{
//定义类属性(类似变量),public 代表可见性(公有) ----所谓的属性(公有属性、私有属性、保护属性)
public $name = 'joker';
//定义类方法(类似函数)----->所谓的方法可以当函数理解
public function smile(){
echo $this->name." is smile...\n";
}
}
$psycho = new people(); //根据people类实例化对象
$psycho->smile();
?>
使用Class定义了一个pepole,这个类中有公共属性,公共属性便是都可以进行调用。此类中有一个smile方法,使用this->拿到共有属性中的name值,使用new进行实例化对象,从而来进行调用函数进行打印。这样就能有漏洞吗?当然不是。这就要谈到PHP的魔术方法
魔术方法
啥又是个魔术方法,魔术方法是触发了某个事件后,魔术方法会自动执行,并不需要要手动调用,普通的函数需要进行调用才能执行,而魔术方法在触发一个事件自动执行。PHP中将所有的魔术方法(__xxxx())类似的命名。下面是一些常见的魔术方法。
| 方法名 | 作用 |
|---|---|
| __construct | 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值 构造函数 类初始 类实例化 new xxx 自动执行 |
| __destruct | 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用 当类执行结束后 自动执行 |
| __toString | 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法 相当相当重要 对象和字符串的拼接 stu =new stu . 'is running' |
| __wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用该方法 序列化功能 便于存续 序列化 类 序列化后 ssrf |
| __sleep() | 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化) serialize |
| __destruct() | 对象被销毁时触发 |
| __call() | 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法 第一不存在 private |
| __callStatic() | 在静态上下文中调用不可访问的方法时触发 |
| __get() | 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性) |
| __set() | 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行 |
| __isset() | 当对不可访问属性调用isset()或empty()时触发 private isset |
| __unset() | 当对不可访问属性调用unset()时触发 private $name |
| __invoke() | 当脚本尝试将对象调用为函数时触发 stu = new student() stu() |
详细的举一个例子吧:
php
<?php
class animal {
private $name = 'caixukun';
public function sleep(){
echo "<hr>";
echo $this->name . " is sleeping...\n";
}
public function __wakeup(){
echo "<hr>";
echo "调用了__wakeup()方法\n";
}
public function __construct(){
echo "<hr>";
echo "调用了__construct()方法\n";
}
public function __destruct(){
echo "<hr>";
echo "调用了__destruct()方法\n";
}
public function __toString(){
echo "<hr>";
echo "调用了__toString()方法\n";
}
public function __set($key, $value){
echo "<hr>";
echo "调用了__set()方法\n";
}
public function __get($key) {
echo "<hr>";
echo "调用了__get()方法\n";
}
}
$ji = new animal();
$ji->name = 1;
echo $ji->name;
$ji->sleep();
$ser_ji = serialize($ji);
//print_r($ser_ji);
print_r(unserialize($ser_ji))
?>
对上面的进行解释。在new一个实例化对象的时候,会自动调用__construct()方法,在对私有属性进行赋值时,会调用__set()f方法,由于魔术方法是用__进行的,所以sleep()并不算,在进行打印反序列化的数值时,调用了__wakeup()方法,最后两次使用__destruct()方法,如下图所示:
那么序列化之后的数据是啥样的?反序列化之后有没有影响?有没有一些特殊的?那么我们来看一个序列化的小例子。
php
<?php
// 类名别用 object,改成 MyObject 更安全
class MyObject{
public $team = 'joker';
private $team_name = 'hahaha';
protected $team_group = 'biubiu';
// 先声明 $team_member 属性
public $team_member;
function hahaha(): void{
// 给已声明的属性赋值(注意不要加 $)
$this->team_member = '奥力给';
}
}
$object = new MyObject();
// 调用方法,给属性赋值
$object->hahaha();
// 序列化对象
echo serialize($object);
?>
如上所示,以上时序列化之后的结果,o代表一个对象,8是这个object的长度,4是有四个类属性。s表示是string类型的数据。5表示长度为3,后买你便是具体的内容。那么你就会发现一个问题,为啥后面的长度和名称对不上,这就和私有属性和保护属性有关,私有属性会在前面加上类的名称长度会比正常大小多两个字节,那么这两个字节的具体内容是啥,其实是\0多出来的东西便是这两个,在前后各一个。在遇到保护属性时,会在前面加上*,并且还是会多处\0的字符,那么保护属性便是多出来三个字符,两个\0和*
那么这是序列化之后数据,我假如在这里加上eval、system等函数呢?传输到后端进行反序列化的时候会执行吗?答案是会执行,这就到了POP的链的构造。
今天的分享到此结束,继续加油,继续学习,后续出一篇think PHP5框架通过反序列化构造POP链从而RCE,从代码一步一步进行。