java序列化基础知识
Java反序列化漏洞专题-基础篇(21/09/05更新类加载部分)_哔哩哔哩_bilibili
随便写个类
直接对他System.out.println(test);
的话是这个效果
为了更加直观,我们给他加个魔术方法__tostring
public String toString(){
System.out.println("OneClass{");
System.out.println("public apple:"+this.apple);
System.out.println("static banana:"+this.banana);
System.out.println("protected pear:"+this.pear);
System.out.println("private grapes:"+this.grapes);
System.out.println("public transient gold:"+this.gold);
return "}";
}
new一个试试
序列化反序列化都需要编写如下的函数
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
//用于序列化
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
//用于反序列化
调用
serialize(test);
unserialize("ser.bin");
被序列化的类必须继承序列化接口
测试代码
继承后:
public class OneClass implements Serializable
注意的点:
- 静态的属性不可以被序列化
- transeint关键字修饰的不可以被序列化
测试代码
OneClass.java
package org.apache.commons.collections;
import java.io.Serializable;
public class OneClass implements Serializable
{
public String apple;
public static String banana;
protected String pear;
private String grapes;
public transient String gold;
public OneClass(){
}
public OneClass(String apple,String bannana,String pear,String grapes,String gold){
this.apple = apple;
this.banana = bannana;
this.pear = pear;
this.grapes = grapes;
this.gold = gold;
}
public String toString(){
System.out.println("OneClass{");
System.out.println("public apple:"+this.apple);
System.out.println("static banana:"+this.banana);
System.out.println("protected pear:"+this.pear);
System.out.println("private grapes:"+this.grapes);
System.out.println("public transient gold:"+this.gold);
return "}";
}
}
ccone.java
package org.apache.commons.collections;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.rmi.CORBA.Tie;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class ccone {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(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 {
OneClass test = new OneClass("apple1", "banana2", "pear3", "grapes4", "gold");
// System.out.println(test);
serialize(test);
System.out.println(unserialize("ser.bin"));
}
}
但是这个static好像赋值附上去了,不懂
transient如果想要赋值的话,想要让他反序列化后能出现我们原本赋予的值,则需要重写readObject
参考vnctf的easyJava
进一步理解序列化反序列化
ObjectOutputStream.writeObject
writeObject
大致内容就是:
- 读取该对象及其超类顶层的信息
- 然后从最顶层超类向下输出对象实例实际数据值
ObjectOutputStream构造方法
-
bout写入一些类元数据还有对象中基本数据类型的值,在下面会分析
-
enableOverride 若是false,说明不支持重写序列化过程,一般用不上
-
writeStreamHeader 写入头信息
①STREAM_MAGIC 声明使用了序列化协议,bout 就是一个流,将对应的头数据写入该流中
②STREAM_VERSION 指定序列化协议版本
调试一下
ObjectOutputStream.writeObject
enableOverride是false所以来到这
代码很长,借用miku的简化代码
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
//①
desc = ObjectStreamClass.lookup(cl, true);
...
//②
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
//③
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
}
...
}
有很多个lookup
- ObjectStreamClass.lookup(cl, true);
它读到了blackcat,也就是一开始继承序列化接口这个类
ObjectStreamClass.lookup(cl, true) 和 throw new NotSerializableException
② 处根据 obj 的类型去执行序列化操作,如果不符合序列化要求,那么会③位置抛出 NotSerializableException 异常。 最后进入 writeOrdinaryObject
writeOrdinaryObject
步入后
bout.writeByte 写入类的元数据, TC_OBJECT . 声明这是一个新的对象,如果写入的是一个 String 类型的数据,那么就需要 TC_STRING 这个标识 writeClassDesc 方法主要作用就是自上而下 (从父类写到子类,注意只会遍历那些实现了序列化接口的类) 写入描述信息。该方法内部会不断的递归调用 desc.isExternalizable 判断需要序列化的对象是否实现了 Externalizable 接口。如果有,那么序列化的过程就会由程序员自己控制, writeExternalData 方法会回调,在这里就可以写需要序列化的数据 writeSerialData : 在没有实现 Externalizable 接口时,就执行这个方法
ObjectOutputstream.writeSerialData
接着
可以看到slotDesc是BlackCat
slotDesc.invokeWriteObject
这里面也有个writeObjectMethod.invoke(obj, new Object[]{ out });
整条链子很长有很多个invoke
Method.invoke
MethodAccessor.invoke
DelegatingMethodAccessorImpl.invoke
NativeMethodAccessorImpl.invoke
defaultWriteFields 这个方法就是 JVM 自动帮我们序列化了 Serialzable 序列化的源码分析就完成了
readObject
基本上是对称的