序列化

序列化是将对象的状态信息(属性)转换为可以存储或传输的过程

将对象或者数组转化为可储存/传输的字符串

在php中使用函数serialize()来将对象或者数组进行序列化,并返回一个包含字节流的字符串来表示

例子:

1
2
3
4
5
<?php

$a=null;

echo serialize($a);

所有格式的第一位都是数据类型的英文字母简写,

是必要的,这才是完整的序列化

image-20240416220054349

数组序列化

image-20240416220406165

a对应array,3对应参数数量

s:6:”benben”,s代表string,6代表有6个字符,“benben”代表内容


解读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$user->name = "carlos";
$user->isLoggedIn = true;

序列化后,该对象类似如下:
O:4:"User":2:{s:4:"name";s:6:"carlos";s:10:"isLoggedIn";b:1;}(pop链)

解释如下:

O:4:"User" -具有4个字符的类名称的对象 "User"
2 -对象具有2个属性
s:4:"name" -第一个属性的键是4个字符的字符串 "name"
s:6:"carlos" -第一个属性的值是6个字符的字符串 "carlos"
s:10:"isLoggedIn" -第二个属性的键是10个字符的字符串 "isLoggedIn"
b:1 -第二个属性的值是布尔值 true
PHP序列化的本机方法是serialize()和unserialize()。如果您具有源代码访问权限,则应从unserialize()代码中的任意位置开始并进行进一步调查。

对象序列化

image-20240417185613989

不能序列化类,可以序列化对象,只序列化成员变量,不序列化成员函数


在private私有属性序列化时,会在变量名加‘’%00类名%00‘’,%00为url编码,它的ascii编码为空image-20240417190104919

一般来说,输出序列化格式要先转换成url编码(便于查看)

例如 echo urlencode(serialize($a))


protected受保护属性序列化是,在变量名前加“%00%00”*

image-20240417191011645


对象调用对象的序列化

image-20240417191455159


反序列化

1.反序列化之后的内容为一个对象

2.反序列化生成的对象的值,由反序列化里的值提供;与原有类预定义的值无关

3.反序列化不触发类的成员方法;需要调用方法后才能触发

作用

将序列化后的参数还原成实例化的对象

image-20240417192512872

反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控的,通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值


魔术方法

一个预定义好的,在特点情况下自动触发的行为方法

作用:魔术方法在特定条件下自动调用相关方法,最终导致触发代码执行

image-20240417204453584

使用魔术方法时,要知道触发时机是什么,因为动作不同,触发的魔术方法也不同

部分魔术方法需要参数


__construct()和__destruct()

__construct()

构造函数,在实例化一个对象时,首先回去自动执行的一个方法

触发时机: 实例化对象

功能: 提前清理不必要的内容


__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法,在方法调用时会触发一次,在反序列化时会触发一次

触发时机:对象引用完成或对象被销毁


__sleep()和__weakup()

__sleep

序列化serialize()函数会检查类中是否存在一个魔术方法__sleep().如果存在,该方法会被先调用,然后才执行序列化操作。此功能用于清理对象,并返回一个包含对象中所有应被序列化的变量,如果该方法未返回内容,则NULL被序列化,并产生一个E_NOTICE级别的错误

触发时机:序列化serialize()之前

功能:对象被序列化之前触发,返回需要被序列化存储的成员,删除不必要的属性

参数:成员属性

返回值:需要被序列化存储的成员属性


__weakup

unserialize() 会检查是否存在一个wakeup()方法。如果存在,则会先调用_wakeup() 方法,预先准备对象需要的资源预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

触发时机: 反序列化unserialize()之前

__weakup()在反序列化之前

__destruct()在反序列化之后


__toString()和__invoke()

__toString()

表达方式错误导致魔术方法触发

触发时机:把对象被当做字符串调用

image-20240417215833414

把类User实体化并赋值给$test,此时$test是个对象,调用对象可以使用print_r或者var_dump


__invoke()

格式表达错误导致魔术方法触发

触发时机:把对象当成函数调用

image-20240418190115539


__call()

触发时机:调用一个不存在的方法

参数:2个参数传参$arg1,$arg2

返回值:调用的不存在的方法的名称和参数

image-20240418190647710


__callStatic()

触发时机:静态调用或调用成员常量时使用的方法不存在

参数:2个参数传参$arg1,$arg2

返回值:调用的不存在的方法的名称和参数

image-20240418191435744


__get()

触发时机:调用的成员属性不存在

参数:传参$arg1

返回值:不存在的成员属性的名称

image-20240418191634321


__set()

触发时机:给不存在的成员属性赋值

参数:传参$arg1,$arg2

返回值:不存在的成员属性的名称和赋的值

image-20240418193018916


__isset()

触发时机:对不可访问属性使用isset()或empty()时,__isset()会被调用

参数:传参$arg1-

返回值:不存在的成员属性的名称

image-20240418193446778


__unset()

触发时机:对不可访问属性使用unset()时

参数:传参$arg1

返回值:不存在的成员属性的名称

image-20240418193725716


__clone()

触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()

image-20240418200613653

总结

image-20240418200631047

image-20240418200640512


pop链

在反序列化中,我们能控制的数据就是对象中的属性值(成员变量所以在PHP反序列化中有一种漏洞利用方法叫”面向属性编程”POP( Property Oriented Programming).
POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的-种payload。

魔术方法触发前提:魔术方法所在类(或对象)被调用

poc编写

PoC (全称: Proof of concept) 中文译作概念验证在安全界可以理解成漏洞验证程序。Poc 是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。

编写一段不完整的程序,获取所需要的序列化字符串

反推法:从得到flag开始反推过程,推完后再正推构造pop链

image-20240418212751236


字符串逃逸基础

反序列化分隔符

在前面字符串没有问题的情况下,反序列化以;}结束,后面的字符串不影响正常的反序列化

没有问题是指:成员属性数量一致,成员属性名称长度一致,内容长度一致

“是字符还是格式符号,是由字符串长度3来判断的

image-20240419192125270

属性逃逸

一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸

字符串减少逃逸

反序列化字符串减少逃逸:多逃逸出一个成员属性

第一个字符串减少,吃掉有效代码,在第二个字符串构造代码

image-20240419192740377

字符串减少逃逸例题

image-20240419200451984


字符串增多逃逸

反序列化字符串增多逃逸:构造出一个逃逸成员属性,

第一个字符串增多,突出多余代码,把多余位代码构造成逃逸的成员属性

image-20240419194125878

思路:把吐出来的字符构造成功能性代码

image-20240419194321542

字符串增多逃逸例题

image-20240419195315320


weakup绕过

漏洞产生的原因:如果存在__wakeup方法,调用unserialize()前则先调用__weakup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过__weakup()执行

有版本限制,php在55.625, 77.010

image-20240421142133990


引用

&的使用

假设$a=1;

$b=&$a

就相当于$b=1;

引用常在某个符号被替换,但你又需要这个符号的时候使用,或者想要一个值跟另一个值相等

image-20240421144346553

结果:

image-20240421144425476


session反序列化漏洞

当session_start()被调用或者php.ini中session.auto_start为1时,php内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)

存储数据的格式有多种,常用的有三种

漏洞产生:写入格式和读取格式不同

image-20240421151355094

当网站序列化并存储session.与反序列化并读取session的方式不同

就可能导致session反序列化漏洞的产生

image-20240421152521147


phar 反序列化原理

什么是phar

jar是开发java程序一个应用,包括所有的可执行。可访问的文件,都打包进了一个jar文件,使得部署过程十分简单

phar(“Php ARchive”)是php里类似jar的一种打包文件

对于php 5.3或更高版本,Phar后缀文件是默认开启支持的,可以直接使用它

读取phar文件:

文件包含:phar伪协议,可读取.phar文件


phar结构

image-20240421160107797

第二个部分重要


stub phar文件标识,格式为xxx(头部信息)

image-20240421160244118


manifest压缩文件的属性等信息,以序列化存储

phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,可以通过此漏洞利用的函数:

image-20240421160446190


content 压缩文件的信息

image-20240421160557313


signature签名,放在文件末尾

image-20240421160628490


phar漏洞原理

image-20240421160904541

构造phar

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class A {
所需代码;
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='N1Xx';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("1.txt", "nlxx"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

再用?xxx=phar://phar.phar&a=xxx

image-20240421164527639

phar使用条件

phar文件能上传到服务器端(没有被过滤,有上传的地方)

要有可用反序列化魔术方法作为跳板

要有文件操作函数,如file_exists(),fopen(),file_get_contents()

文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤