01java反序列化基础

java反射的相关操作

一些重要的方法

  • 获取类的⽅法: forName

  • 实例化类对象的⽅法: newInstance

  • 获取函数的⽅法: getMethod

  • 执⾏函数的⽅法: invoke

// eg.反射获取任意类的任意方法并执行
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            // 获取类名
            Class<?> clazz = Class.forName("com.example.SomeClass");

            // 获取方法名和参数类型
            String methodName = "someMethod";
            Class<?>[] parameterTypes = {String.class, int.class};

            // 获取方法
            Method method = clazz.getMethod(methodName, parameterTypes);

            // 创建类的实例
            Object obj = clazz.newInstance();

            // 准备参数
            Object[] arguments = {"example", 123};

            // 执行方法
            Object result = method.invoke(obj, arguments);

            // 打印结果
            System.out.println("Method returned: " + result);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (java.lang.reflect.InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

forName

  • forName 不是获取“类”的唯⼀途径

    • obj.getClass() 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过
      obj.getClass() 来获取它的类

    • Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接
      拿它的 class 属性即可。这个⽅法其实不属于反射。

    • Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

  • forName的重载

    • forName(String name)和Class forName(String name, boolean initialize, ClassLoader loader)两个重载Class

    • ClassLoader loader就是⼀个“加载器”,一般是一个类的完整路径,如java.lang.Runtime

    • boolean initialize决定是否进行“类初始化”,forName(String name)默认initialize=true

  • 关于类初始化的补充:下面代码的执行顺序为static{}, 构造函数的 super(),{},构造函数,static{}即为类初始化

    public class TrainPrint {
     {
     System.out.printf("Empty block initial %s\n", this.getClass());
     }
     static {
     System.out.printf("Static initial %s\n", TrainPrint.class);
     }
     public TrainPrint() {
     super();
     System.out.printf("Initial %s\n", this.getClass());
     }
    }
    

newInstance

  • class.newInstance() 的作用就是调用这个类的无参构造函数,于是乎不成功是因为:

    • 你使用的类没有无参构造函数

    • 你使用的类构造函数是私有的,例如java.lang.Runtime,可以采用类的其他静态方法获取实例

newInstance的补充getConstructor

  • Java和C++不同,C++的类必须有一个无参构造函数(显示定义或者编译器自动生成),而Java一但显示定义了任意构造函数,编译器就不会再自动生成无参构造函数,这就造成了一个问题,Java中的类可能没有无参构造函数也没有可获取实例的其他方法,此时就需要getConstructor获取有参构造函数
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));
);

关于类的私有方法

  • 类的私有方法可以通过getDeclared 系列的反射调用,与普通的 getMethod 、 getConstructor 区别是:

    • getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法

    • getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私
      有的方法,但从父类里继承来的就不包含了

    Class clazz = Class.forName("java.lang.Runtime");
    Constructor m = clazz.getDeclaredConstructor();
    // setAccessible(true)修改作用域是必须得
    m.setAccessible(true);
    clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
    

反射的一些特性

  • 无需import类

  • 可以访问私有方法

不同语言的序列化对比

PHP序列化

<?php
 class Connection
 {
    protected $link;
    private $dsn, $username, $password;
    
    public function __construct($dsn, $username, $password)
    {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        $this->connect();
    }
    
    private function connect()
    {
        $this->link = new PDO($this->dsn, $this->username, $this
>password);
    }
 }
  • 这里的$link是一个对象,没有自定义__sleep函数时,$link序列化为null.个人的理解,序列化的结果是字符串,对象当然不能直接序列化.
<?php
 class Connection
 {
    protected $link;
    private $dsn, $username, $password;
    
    public function __construct($dsn, $username, $password)
    {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        $this->connect();
    }
    
    private function connect()
    {
        $this->link = new PDO($this->dsn, $this->username, $this
>password);
    }
    
    public function __sleep()
    {
        return array('dsn', 'username', 'password');
    }
    
    public function __wakeup()
    {
        $this->connect();
    }
  • 这里添加了一个__sleep,返回由属性组成的数组,又新添了一个__wakeup,这个wakeup完成了反序列化后对于$link的实例化
  • P牛对于PHP反序列化的思考:__wakeup的作用在反序列化后,执行一些初始化操作。但其实我们很少利用序列化数据传递资源类型 的对象,而其他类型的对象,在反序列化的时候就已经赋予其值了。 所以你会发现,PHP的反序列化漏洞,很少是由__wakeup这个方法触发的,通常触发在析构函数 __destruct里。其实大部分PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以 控制对象的属性,进而在后续的代码中进行危险操作。

Java序列化

  • 两个条件

    • 实现 java.io.Serializable 接口
    • 所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的
  • java对象序列化后和php不同,是字节码而非字符串

  • 下述代码输出了序列化后的person类

package com.individuals.javaSecurity.myclass;

import java.io.IOException;
import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject("this is a object");
    }
    private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        String message = (String) s.readObject();
        System.out.println(message);
    }
}
import java.io.*;

public class SerializationUtils {

    // 序列化对象并转换为十六进制字符串
    public static String serializeObjectToHex(Serializable object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
            objectOutputStream.writeObject(object);
        }
        byte[] serializedBytes = byteArrayOutputStream.toByteArray();
        return bytesToHex(serializedBytes);
    }

    // 反序列化十六进制字符串回对象
    public static Object deserializeHexToObject(String hexString) throws IOException, ClassNotFoundException {
        byte[] bytes = hexStringToByteArray(hexString);
        try (ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            return objectInputStream.readObject();
        }
    }

    // 将字节数组转换为十六进制字符串
    public static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            hexString.append(String.format("%02X", b));
        }
        return hexString.toString();
    }

    // 将十六进制字符串转换为字节数组
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }
}

import java.io.IOException;

import static com.individuals.javaSecurity.utils.SerializationUtils.*;

public class TestSer {
    public static void main(String[] args) throws IOException {
        Person person = new Person("lda",123);
        System.out.println(serializeObjectToHex(person));
    }
}
  • java -jar SerializationDumper-v1.13.jar 序列化对象.值得注意的是我们在序列化时,写入的字符串"this is a object"在objectAnnotation中
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 43 - 0x00 2b
        Value - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6e
      serialVersionUID - 0xf9 30 f2 ab 12 b1 36 32
      newHandle 0x00 7e 00 00
      classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
      fieldCount - 2 - 0x00 02
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 3 - 0x00 03
            Value - age - 0x616765
        1:
          Object - L - 0x4c
          fieldName
            Length - 4 - 0x00 04
            Value - name - 0x6e616d65
          className1
            TC_STRING - 0x74
              newHandle 0x00 7e 00 01
              Length - 18 - 0x00 12
              Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 02
    classdata
      com.individuals.javaSecurity.myclass.Person
        values
          age
            (int)123 - 0x00 00 00 7b
          name
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 3 - 0x00 03
                Value - lda - 0x6c6461
        objectAnnotation
          TC_STRING - 0x74
            newHandle 0x00 7e 00 04
            Length - 16 - 0x00 10
            Value - this is a object - 0x746869732069732061206f626a656374
          TC_ENDBLOCKDATA - 0x78
  • 尝试反序列化我们的序列化对象,可以看当初写入的字符串被打印出来了
import java.io.IOException;

import static com.individuals.javaSecurity.utils.SerializationUtils.deserializeHexToObject;

public class TestUnser {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = (Person)deserializeHexToObject("ACED00057372002B636F6D2E696E646976696475616C732E6A61766153656375726974792E6D79636C6173732E506572736F6EF930F2AB12B136320300024900036167654C00046E616D657400124C6A6176612F6C616E672F537472696E673B78700000007B7400036C6461740010746869732069732061206F626A65637478");
    }
}

image-20240526132132418

  • 这里借助gpt简单解释一下序列化对象中的objectAnnotation和 classAnnotations:
classAnnotations
classAnnotations 是与类相关的注解信息。在序列化过程中,Java会在序列化流中包括与类相关的注解信息,这些信息包括:

类的元数据:例如类的名称、类的签名(包括字段和方法的签名)。
类的序列化版本UID:用于验证反序列化时类版本的一致性。
类的父类信息:如果类是从其他类继承而来,这些信息也会被包含在内。
objectAnnotation
objectAnnotation 是与对象相关的注解信息。在序列化过程中,Java会在序列化流中包括与对象相关的注解信息,这些信息包括:

对象的字段值:对象的非静态和非瞬态字段的当前值。
引用的其他对象:如果对象包含对其他对象的引用,这些被引用对象也会被序列化。
对象的定制序列化数据:如果类实现了 writeObject 方法,这些方法中自定义序列化的数据也会被包含在 objectAnnotation 中。

Python反序列化

  • Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于 栈的虚拟机。我们可以向栈上增、删对象,也可以执行一些指令,比如函数的执行等,甚至可以用这个 虚拟机执行一个完整的应用程序。 所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加 危险。

总结

  • 总结一下,从危害上来看,Python的反序列化危害是最大的;从应用广度上来看,Java的反序列化是最 常被用到的;从反序列化的原理上来看,PHP和Java是类似又不尽相同的。

补充:SerializationDumper的使用

  • SerializationDumper是一个分析序列化对象的工具
  • 使用方法很简单:java -jar SerializationDumper-v1.13.jar ,后面可以直接加反序列化对象的十六进制串或者从文件读取

几个案例分析

public class Person implements Serializable {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject("this is a object");
    }
    private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        String message = (String) s.readObject();
        System.out.println(message);
    }
}
public class TestSer {
    public static void main(String[] args) throws IOException {
        Person person = new Person("lda",123);
        System.out.println(serializeObjectToHex(person));
    }
}
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 43 - 0x00 2b
        Value - com.individuals.javaSecurity.myclass.Person - 0x636f6d2e696e646976696475616c732e6a61766153656375726974792e6d79636c6173732e506572736f6e
      serialVersionUID - 0xf9 30 f2 ab 12 b1 36 32
      newHandle 0x00 7e 00 00
      classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
      fieldCount - 2 - 0x00 02
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 3 - 0x00 03
            Value - age - 0x616765
        1:
          Object - L - 0x4c
          fieldName
            Length - 4 - 0x00 04
            Value - name - 0x6e616d65
          className1
            TC_STRING - 0x74
              newHandle 0x00 7e 00 01
              Length - 18 - 0x00 12
              Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 02
    classdata
      com.individuals.javaSecurity.myclass.Person
        values
          age
            (int)123 - 0x00 00 00 7b
          name
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 3 - 0x00 03
                Value - lda - 0x6c6461
        objectAnnotation
          TC_STRING - 0x74
            newHandle 0x00 7e 00 04
            Length - 16 - 0x00 10
            Value - this is a object - 0x746869732069732061206f626a656374
          TC_ENDBLOCKDATA - 0x78
posted @   需要努力呀  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示

目录导航