URLDNS

hashmap:readObject
readObject:putVal
putVal:hash
hash:URL.hashcode == -1
getHostAddress => DNS查询

HashMap map = new HashMap();  
URL url = new URL("http://dyro20rs.dnslog.pw");
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,-1);
map.put(url,"2333");
field.set(url,-1);

cc2

反序列化的时候是

  1. 触发queue的readObject : heapify()即堆排序函数
  2. 触发heapify()里的siftDownUsingComparator(k, x)
  3. 触发comparator.compare((E) c, (E) queue[right]),由于comparator是TransformingComparator,即触发this.transformer.transform(obj1)
  4. this.transformer是构造时TransformingComparator传入的transformerInvokerTransformer
  5. 触发this.transformer.transformTemplatesImplnewTransformer()
  6. 接着触发getTransletInstance(),触发newInstance
    Pasted%20image%2020240118153951

结束

注意
_tfactory 需要是一个 TransformerFactoryImpl 对象,这里是因为调试发现已经有了就没有再写

cc4

cc2+cc3

和cc2不同的是不需要Pasted%20image%2020240119173453
而cc4 是利用TrAXFilter构造函数中的newTransformer而不是InvokerTransformer
剩下就都是cc2了,优先队列进去compare触发this.transformer.transform(obj1)然后触发构造函数加载字节码

cc5

反序列化的是BadAttributeValueExpException
Pasted%20image%2020240119174458

System.getSecurityManager()默认为null,然后val(即被toString的对象)可以根据反射修改

于是这里可以触发toString
而TideMapEntry的toString触发了map.get()(主要还是toString里的getValue)方法,map是由传入其构造函数的map决定的,所以,把Lazymap传入,就能触发他的get方法

cc6

和cc5一样,使用的是TideMapEntry
他的hashcode的方法使用到了getValue,这里面有map.get()

触发点是Lazymap里的get方法(要移除最开始添加的key才能触发Transform方法)
反序列化Hashset正常,readObject,他的key会触发map.put(key,xx)
进而触发hash(key),如果key是TideMapEntry,那么就完成了

把TideMapEntry放在hashset里直接add,反射放入真正的Transformchains,
反序列化的时候就是:

hashset:map.put(e,xxx)
hash(TideMapEntry,x,x)
hashcode->getValue()
lazymap.get()

cc7

反序列化的是hashtable

在反序列化的过程中会调用AbstractMap的equal方法,里面会调用m.get(key)
Pasted%20image%2020240119220753
注意一下hashcode的计算和key和Value都有关系,最好是固定一个,改变另一个碰撞,或者两个都碰撞

要用两次put,第一次不会进入for循环,只给tab[index]赋值,第二次put进入for循环,到达触发点,并且只有第一次和第二次hashcode一样才会index一样,才能访问到一个数据.使e不为null
也是要移除的,不然触发不了get,cc1不用移除是因为是entrySet,如果你put一个entrySet,cc1也是触发不了的,但是看cc7好像又有别的原因.
这里put放入的时候会调用一次equal,然后就会在第二个Lazymap2产生一次第一个Lazymap的key,不满足m.size = size的判断

cc1

那个chains链是调用每个内部的transform方法并传递给下一个,

Transformer[] transformer = new Transformer[]{
// 返回 java.lang.Runtime
new ConstantTransformer(Runtime.class),
// getClass获取到传入到runtime 会变成 java.lang.class 利用 java.lang.class 中的 getMethod 获取getRuntime
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}), // 返回的是getruntime的方法
// 上面返回的应该是getRuntime的这个静态方法 获取反射类中的invoke类执行getRuntime
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
// 调用返回实例中的exec
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"open -a Calculator"})
};
Transformer chainedTransformer = new ChainedTransformer(transformer);
chainedTransformer.transform(111);

经过调试发现,
第一个invoke,调用是method.invoke(input, iArgs)
method是getMethod,input是Runtime,iArgs是getRuntime,于是就拿到了getRuntime的Method类型的静态方法
第二个invoke
传入的是getRuntime的方法,获取到invoke,其实触发的就是invoke.invoke(input)input.invoke其实是一样的
第三个invoke
传入的是上一步得到的Runtime实例,调用exec方法

这个链子就通了,接下来就是弄到反序列化里去

TransformMap

他的put方法会自动调用传入的Transform
就触发了上述描述的链子

但是这样构造的链子还需要手动使用put方法,接下来讲解一下AnnotationInvocationHandler

传入一个map赋值为memberValues,在readObject的时候会调用*.entrySet().iterator(),令var5为map的一个元素,然后对其用setValue修改

到这里差不多讲完了,但是还有一点需要注意的,第一个元素位置传入的需要是一个注解,poc里是Target.class

一开始map.put("value","xx")第一个位置一定得是Value因为注解传入的是value=xxx这样才让var7不为空

LazyMap

Lazymap的get方法可以触发Transform链的那个方法

动态代理会使用invoke调用

反序列化的是AnnotationInvocationHandler,去找AnnotationInvocationHandler的readObject,最外是传入Lazymap的Proxy代理对象,会触发*.entrySet().iterator(),然后就会调用传入的Handler:map_handler的invoke方法,而在这个AnnotationInvocationHandler的invoke方法调用的过程中,就会触发Lazymap的get方法

cc3

首先认识一下TrAXFilter
Pasted%20image%2020240119172245
这里能直接触发Templates加载字节码

然后InstantiateTransformer这个类的Transform直接就获取传入类的构造函数并且初始化

如果传入TrAXFilter就直接over了
下半部分和cc1一样的,用动态代理触发Lazymap的get方法进而触发这个Transformchains

如果没有chains可以用newTransformer
参考y4 Github