1、原理
PHP反序列化也叫PHP对象注入,形成的原因是程序未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行、文件操作、执行数据库操作等参数不可控。反序列化攻击在Java、Python等面向对象语言中均存在。序列化是广泛存在于PHP、Java等编程语言中的一种将有结构的对象/数组转化为无结构的字符串并储存信息的一种技术。
2、序列化(以PHP语言为例)
PHP类都含有的特定元素:类属性、类常量、类方法。
序列化就是将一个类压缩成一个字符串的方法。
eg:
<?php class userInfo { private $passwd='123456'; protected $sex = 'male'; //定义了三个属性 public $name ='Myon'; public function modifyPasswd($passwd) { $this->passwd = $passwd; //将函数传进来的值传给passwd } public function getPasswd() { echo $this->$passwd; //输出passwd } } $Myon = new userInfo(); //创建 userInfo对象实例 $Myon->modifyPasswd('123abc'); //调用modifyPasswd函数并将123abc值传进去 $data = serialize($Myon); //序列化 echo $data; ?>
输出结果:
O:8:"userInfo":3{s:16:"userInfopasswd";s:6:"123abc";s:6:"*sex";s:4:"male";s:4:"name";s:4:"Myon";}
对序列化后的字符串进行解读:
大括号外表示“Object”对象名称是“userInfo”,长度为8,这个对象有3个属性。
(特别注意:在CTF中常有一个对 _wakeup() 函数的绕过,当序列化字符串中表示对象属性个数的值大于实际属性个数时,就会跳过wakeup方法的执行。)
在前面的文章中有对CTF题PHP反序列化及绕过实例的讲解,可以参考
大括号内表示这些属性的具体信息及它们的值
根据属性的权限不同,在序列化中的表示方法也不同
从代码中可以看出,三个属性的权限分别是private,protected和public
(1)private权限是私有权限,只能在本类内使用,子类不能继承。
(2)protected权限是私有权限,即只能在类内部使用,子类可以继承这个变量。
(3)public权限就是正常的变量权限,一般声明的变量权限均为public。
标红的是private,前面加上了本类名称;
标蓝的是protected,前面加上了星号;
标绿的是public,没有任何前缀。
一个类经过序列化之后,存储在字符串的信息只有类名称和类内属性键值对,序列化字符串中没有将类方法一并序列化。
3、反序列化
反序列化与序列化是相对应的,就是将含有类信息的序列化过的字符串“解压缩”还原成类。
反序列化的类想要使用原先的类方必须依托于域,脱离了域的反序列化的类是无法调用序列化之前的类方法的。
<?php class userInfo { private $passwd='123456'; protected $sex = 'male'; //定义了三个属性 public $name ='Myon'; public function modifyPasswd($passwd) { $this->passwd = $passwd; //将函数传进来的值传给passwd } public function getPasswd() { echo $this->$passwd; //输出passwd } } $Myon = new userInfo(); //创建 userInfo对象实例 $Myon->modifyPasswd('123abc'); //调用modifyPasswd函数并将123abc值传进去 $data = serialize($Myon); //序列化 $new_Myon = unserialize($data); //反序列化数据 $new_Myon->getPasswd(); ?>
理论上这里类方法应该被成功执行,但是...
不过有一点可以确定,如果我们单独将序列化后的字符串作为输入,在一个新的域下执行代码片段,肯定是会报错的。
<?php $data = "O:8:\"userInfo\":3{s:16:\"userInfopasswd\";s:6:\"123abc\";s:6:\"*sex\";s:4:\"male\";s:4:\"name\";s:4:\"Myon\";}" $new_Myon =unserialize($data);//反序列化数据 $new_Myon->getPasswd(); ?>
4、PHP魔法函数
(1)__wakeup()
在PHP中如果需要进行反序列化,会先检查类中是否存在_wakeup()函数,如果存在,则会先调用此类方法,预先准备对象需要的资源。
<?php class example { public $color = 'black';//定义color属性 public function __wakeup() { $this->color = 'white';//将white赋值给color } public function printColor() { echo $this->color . PHP_EOL; //输出color } } $my = new example; //实例化对象 $data = serialize($my); //进行序列化 $new_my = unserialize($data); //反序列化 $new_my->printColor(); //调用printColor()函数 ?>
可以看到类属性color已经被__wakeup()函数自动调用并修改了
这种函数被称为PHP魔法函数,它在一定条件下不需要被调用而可以自动调用
(2)__destruct()
在对象的所有引用都被删除或类被销毁时自动调用
<?php class example { public $color ='black'; //定义color属性 public function __destruct() { echo "__destruct()". PHP_EOL; //打印__destruct() } } echo "initializing...". PHP_EOL; //打印 initializing... $my = new example; //创建对象实例 echo "serializing..." . PHP_EOL; //打印 serializing... $data = serialize($my); // 序列化 ?>
可以看到在序列化类的时候,__destruct()函数自动执行了 。
(3)__construct()
此函数会在创建一个类的实例时自动调用
<?php class example { public $color='black'; //定义color属性 public function __construct() { echo"____construct()". PHP_EOL; //打印__construct() } } echo "initializing...". PHP_EOL; //打印initializing... $my = new example; //创建对象实例 echo "serializing...". PHP_EOL; //打印 serializing... $data = serialize($my); //序列化 ?>
可以看到在序列化之前,实例化时__construct()函数就被调用了。
(4)__toString()
此函数会在类被当作字符串时调用
<?php class example { public $color='black'; //定义color属性 public function __toString() { return"_toString()". PHP_EOL;//打印__toString() } } echo "initializing...". PHP_EOL; //打印initializing... $my = new example; //创建对象实例 echo "echo...". PHP_EOL; //打印echo... echo $my;//输出$my echo "serializing...". PHP_EOL; // 打印 serializing... $data = serialize($my); //序列化 ?>
可以看到当实例化对象被当作字符串使用时,__toString()函数自动调用。
其他触发此函数的情况:
反序列化对象与字符串连接时。
反序列化对象参与格式化字符串时。
反序列化对象与字符串进行==比较时(PHP进行==比较时会转换参数类型)。
反序列化对象参与格式化SQL语句,绑定参数时。
反序列化对象在经过PHP字符串函数,如strlen()、addslashes()时。
在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串时,toString会被调用。
反序列化的对象作为class_exists()的参数时。
(5)__get()
在读取不可访问的属性值时自动调用
<?php class example { private $color ='black'; // 定义私有属性 color public function __get($color) { return"__get()". PHP_EOL; //打印__get() } } $my = new example; //创建对象实例 echo $my->color; //输出 color 属性 ?>
因为试图访问私有变量color导致__get()函数自动调用
(6)__call()
调用未定义的方法时调用
<?php class example { private $color='black'; //定义私有属性 color public function __call($function,$parameters) { echo $function."('.Sparameters.')".PHP_EOL;//打印两个参数 return"__call()". PHP_EOL; } } $my = new example; //创建对象实例 echo $my->notExistFunction("patameters"); //调用未定义方法?> ?>
可以看到__call()函数被调用
也就是说你想让调用方法未定义,那么这个方法名就会作为 __call的第一个参数传入,因此不存在方法的参数会被装进数组中作为 __call的第二个参数传入。