前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出反序列化漏洞

深入浅出反序列化漏洞

原创
作者头像
鱼丸
发布2022-04-28 15:46:07
4130
发布2022-04-28 15:46:07
举报
文章被收录于专栏:鱼丸的渗透测试

一、前置知识

阅读本文前应该先去了解,什么是类,什么是对象,推荐搜索关键词,php对象和类,java对象和类

二、反序列化

用大白话来讲,序列化就是把(类的实例化对象)对象序列化成字符串,反序列化就是把字符串又转化回对象。打个比方,序列化就是把你洗菜,做菜,炒菜最后做出一盘红烧排骨的一系列动作,写成菜谱。而反序列化就是你拿着菜谱,按照菜谱的步骤又做出一盘红烧排骨。

1.php反序列化

serialize():序列化函数 unserialize():反序列化函数

下面我先用php代码举个栗子。

代码语言:javascript
复制
//序列化
<?php
    //类meat
	class meat
	{
		var $say = "tastes delicious!";
		var $cost = 198;
		function welcome()
		{
			echo 'nice to meet you.';
		}
	}
	$pork = new meat();
	echo serialize($pork);
?>

访问这个php文件,就得出了序列化之后的字符串

代码语言:javascript
复制
O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:198;}

//说明如下
O:4:"meat":2:     
O:object 说明我们序列化的是一个对象,后面的4代表类名占四位字符,类的名字为meat,2代表类有两个变量
s:3:"say";s:17:"tastes delicious!";
第一个变量名为字符串,占三个字符,为say,第一个变量值为字符串,占17个字符为tastes delicious!
s:4:"cost";i:198;
第二个变量名为字符串占四个字符,为cost,第二个变量值为整形,为198

从序列化的字符串中我们可以看出来,序列化的字符串中只含有类名还有类中变量的信息,没有类中函数的信息,之所以这么做的原因是同一类对象中每个对象都具备相同的函数,但是每个对象的变量值却不一定相同,所以我们序列化时只要保存对象的变量就可以了

思考一下,是不是我们如果能控制序列化后的字符串,我们就能控制反序列化后的对象。下面将刚刚序列化后的字符串反序列化回去。

代码语言:javascript
复制
<?php
    //类meat
	class meat
	{
		var $say = "tastes delicious!";
		var $cost = 198;
		function welcome()
		{
			echo 'nice to meet you.';
		}
	}	
	//注意反序列化时当前类的定义一定要在当前文件中
	$str = 'O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:198;}';
	$pork = unserialize($str);
	$pork->welcome();
	echo '<br/>';
	var_dump($pork);
?>

代码运行结果如下,这就是一整个序列化和反序列化的过程

2.php魔术方法

从上文看到,我们能控制的只有序列化后的字符串,序列化的字符串只能控制对象中的变量,反序列化后,能不能调用对象中的函数,不是我们能控制的,源码中对象的成员函数有没有调用,是在源码中写死的,所以此时就引出了魔术方法。

魔术方法,之所以被冠名为魔术方法,就因为它们很神奇,神奇的地方就是,它们不需要手动调用,只要满足了触发条件,就能自动被调用。

代码语言:javascript
复制
__construct():当一个对象创建时被调用
__destruct():当一个对象销毁时被调用
__toString():当对象被当作字符串时被调用
__sleep():当对象被序列化时被调用
__wakeup():当对象被反序列化时被调用
__get():当调用一个未定义的属性时被调用
__set():给一个未定义的属性赋值时调用
__invoke():以调用函数的方式调用一个对象时被调用。

如果我们控制的序列化字符串中的变量,在这些魔术方法中的话,魔术方法被触发,我们的恶意参数就会生效,这就是反序列化漏洞。

下面我们简单演示一下魔术方法是如何被触发的。

代码语言:javascript
复制
<?php
    //类meat
	class meat
	{
		var $say = "tastes delicious!";
		var $cost = 198;
		function welcome()
		{
			echo 'nice to meet you.';
		}
		//当对象被反序列化时被调用
		function __wakeup()
		{
			echo $this->cost;
			echo '<br/>';
		}
	}
	$str = 'O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:99999;}';
	$pork = unserialize($str); 、
	$pork->welcome();
	echo '<br/>';
	var_dump($pork);
?>    

此时我们就是修改了序列化字符串中变量的值,将它从198修改成了99999,刚好该参数在魔术方法__wakeup中,在反序列化时此魔术方法被触发,这样子我们的恶意参数就生效了,原本程序想要输出198,结果被我们篡改成了99999。

3.java反序列化

java反序列化和php反序列化根本原理都是一样的,这不过其中的函数有不同。

java规定如果一个对象想要进行序列化操作,那么这个对象对应的类必须实现序列化接口,也就是Serializable

接口。一个可以被序列化的对象的类定义如下:

代码语言:javascript
复制
import java.io.Serializable;
public class Person implements Serializable {
    private String name;
}

java的序列化使用的是ObjectOutputStream(对象输出流)类的writeObject()方法。

java的反序列化使用的是ObjectInputStream(对象输入流)类的readObject()方法。

3.1序列化过程

代码语言:javascript
复制
public class Person implements Serializable 
{
    private String name;
    private int age;
 
    Person(String name, int age) 
    {
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() 
    {
        return "姓名:"+name+"年龄:"+age;
    }
}

public static void main(String[] args) throws IOException 
{
        //实例化Person对象
        Person jack = new Person("jack", 12);
        //生成一个文件对象,文件不存在将自动创建文件
        File f = new File("F:" + File.separator + "serTest.txt");
        //构造一个对象输出流oos
        ObjectOutputStream oos = null;
        //构造一个文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream(f);
        //构造对象输出流
        oos = new ObjectOutputStream(fileOutputStream);
        //序列化一个对象到文件变成二进制内容,二进制字节流文件,直接打开是乱码
        oos.writeObject(jack);
        oos.close();
}

3.2反序列化过程

代码语言:javascript
复制
public class Person implements Serializable 
{
    private String name;
    private int age;
 
    Person(String name, int age) 
    {
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() 
    {
        return "姓名:"+name+"年龄:"+age;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException
{
        //生成一个文件对象
        File f = new File("F:" + File.separator + "serTest.txt");
        //构建对象输入流对象
        ObjectInputStream oos = null;
        //构建文件输入流对象
        FileInputStream fileOutputStream = new FileInputStream(f);
        oos = new ObjectInputStream(fileOutputStream);
        //读取序列化
        Person jack=(Person)oos.readObject();
        System.out.println(jack);
   
}

3.3反序列化漏洞demo

如果类是这么写的,在反序列化的时候就会弹出计算器了。

代码语言:javascript
复制
public class Person implements Serializable 
{
    private String name;
    private int age;
 
    Person(String name, int age) 
    {
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() 
    {
        return "姓名:"+name+"年龄:"+age;
    }
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        
        // 执行默认的 readObject() 方法
        in.defaultReadObject();
        
        // 执行打开计算器程序命令
        Runtime.getRuntime().exec("calc");
    }
}
    

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前置知识
  • 二、反序列化
    • 1.php反序列化
      • 2.php魔术方法
        • 3.java反序列化
          • 3.1序列化过程
          • 3.2反序列化过程
          • 3.3反序列化漏洞demo
      相关产品与服务
      网站渗透测试
      网站渗透测试(Website Penetration Test,WPT)是完全模拟黑客可能使用的攻击技术和漏洞发现技术,对目标系统的安全做深入的探测,发现系统最脆弱的环节。渗透测试和黑客入侵最大区别在于渗透测试是经过客户授权,采用可控制、非破坏性质的方法和手段发现目标和网络设备中存在弱点,帮助管理者知道自己网络所面临的问题,同时提供安全加固意见帮助客户提升系统的安全性。腾讯云网站渗透测试由腾讯安全实验室安全专家进行,我们提供黑盒、白盒、灰盒多种测试方案,更全面更深入的发现客户的潜在风险。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档