目录
php类与对象
类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。
<?php
class people{
//定义类属性(类似变量),public 代表可见性(公有)
public $name = 'joker';
//定义类方法(类似函数)
public function smile(){
echo $this->name." is smile...\n";
}
}
$psycho = new people(); //根据people类实例化对象
$psycho->smile();
?>
通过运行代码可以看到内容如下
代码定义类一个people类,并在在类中定义了一个public类型的变量$name和类方法smile。然后实例化一个对象$psycho,去调用people类里面的smile方法,打印出结果。
魔术方法
魔术方法是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以__
(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
方法名 | 作用 |
---|---|
__construct | 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值; |
__destruct | 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用; |
__toString | 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法; |
__wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用该方法 |
__sleep() | 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化); |
__destruct() | 对象被销毁时触发; |
__call() | 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法; |
__callStatic() | 在静态上下文中调用不可访问的方法时触发; |
__get() | 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性); |
__set() | 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行; |
__isset() | 当对不可访问属性调用isset()或empty()时触发; |
__unset() | 当对不可访问属性调用unset()时触发; |
__invoke() | 当脚本尝试将对象调用为函数时触发。 |
__toString
的具体触发场景:
-
echo($obj) / print($obj) 打印时会触发;
-
反序列化对象与字符串连接时;
-
反序列化对象参与格式化字符串时;
-
反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型);
-
反序列化对象参与格式化SQL语句,绑定参数时;
-
反序列化对象在经过php字符串函数,如 strlen()、addslashes()时;
-
在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用;
-
反序列化的对象作为 class_exists() 的参数的时候。
魔术方法使用举例
序列化 / 反序列化
在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql
数据库中时,我们时常需要将数组进行序列号操作。
-
php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
-
php反序列化(
unserialize
):就是在适当的时候把这个字符串再转化成原来的变量使用
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
常见的php系列化和反系列化方式主要有:serialize
,unserialize
、json_encode
、json_decode
。
序列化
php的序列化:变量转换为可保存和传输的字符串过程。
<?php
class object{
public $team = 'joker';
private $team_name = 'hahaha';
protected $team_group = 'biubiu';
function hahaha(){
$this->$team_members = '777';
}
}
$object = new object();
echo serialize($object);
?>
以上是序列化之后的结果,o代表是一个对象,6是对象object的长度,3的意思是有三个类属性,后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。值得一提的是,类方法并不会参与到实例化里面。
即格式为:O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}
序列化中个字母的含义
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string
需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化。
-
使用public修饰进行序列化后,变量$team的长度为4,正常输出。
-
使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。
-
使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。
通过对比发现私有变量和公有变量的储存方法不一样,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:
1.受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]
2.受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]
其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少
反序列化
反序列化的话,就依次根据规则进行反向复原。这边定义一个字符串,然后使用反序列化函数unserialize进行反序列化处理,最后使用var_dump进行输出:
<?php
$ser = 'O:6:"object":3:{s:4:"team";s:5:"joker";s:17:"objectteam_name";s:6:"hahaha";s:13:"*team_group";s:6:"biubiu";}';
$ser = unserialize($ser);
var_dump($ser);
?>
漏洞分析
php反序列化漏洞---对象注入
在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。
挖掘反序列化漏洞的条件是:
-
代码中有可利用的类,并且类中有
__wakeup()
,__sleep()
,__destruct()
这类特殊条件下可以自己调用的魔术方法。 -
unserialize()
函数的参数可控。
<?php
class A{
var $test = "demo";
function __destruct(){
@eval($this->test);
}
}
$test = $_POST['test'];
$len = strlen($test)+1;
$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($p); // 反序列化同时触发_destruct函数
?>
如上代码,最终的目的是通过调用__destruct()
这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到unserialize()
反序列化的时候,会触发__destruct()
方法,同时也可以触发__wakeup()
方法。但是如果想注入恶意payload,还需要对$test的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的$test变量进行覆盖。