深入理解JNDI注入

深入理解JNDI注入

1.关于RMI

主要介绍RMI的调用流程,RMI注册表以及动态加载类的概念。

1.1 远程方法调用

RMI(Remote Method Invocation)是为Java环境设计的远程方法调用机制,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程的方法。RMI依赖的通信协议为JRMP(Java Remote Protocol,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写,这个协议就跟Http协议一样,规定了客户端和服务端通信要满足的规范。在RMI中对象是通过序列化方式进行编码传输的。

1.2远程对象

使用远程方法调用,必然会涉及参数的传递和执行结果的返回。参数或者返回值可以是基本数据类型,当然也有可能是对象的引用。所以这些需要被传输的对象必须可以被序列化,这要求相应的类必须实现Java.io.Serializable接口,并且客户端的serialVersionUID字段要和服务器端保持一致。

深入理解JNDI注入_第1张图片

深入理解JNDI注入_第2张图片

在JVM 之间通信时,RMI远程对象和非远程对象的处理方式是不一样的,它并没有直接把远程对象复制一份给客户端,而是传递一个远程对象的Stub,Stub相当于是远程对象的引用或者代理。Stub对开发者是透明的,客户端可以像调用本地方法一样通过它来调用远程方法。Stub中包含了远程对象的定位信息,如Sockect端口,服务端主机地址等,并实现了远程调用过程中具体的底层通信细节,所以RMI远程调用逻辑是这样的:

深入理解JNDI注入_第3张图片

从逻辑上看,数据是在Client和Server之间横向流动,但是实际上是从Client到Stub,然后从Skeleton到Server这样纵向流动。

Server端监听一个端口,这个端口是JVM随机选择的;

Client端并不知道Server远程对象的通信地址和端口,但是Stub中包含了这些信息,并封装了底层网络操作;

Client可以调用Stub上的方法;

Stub连接到Server端监听的通信端口并提交参数;

远程Server端上执行具体的方法,并返回结果给Stub;

Stub返回执行结果给Client端,从Client看来就好像Stub在本地执行了这个方法一样;

那怎么获取Stub呢?

1.3RMI注册表

Stub的获取方式有很多,常见的方法是调用某个远程服务上的方法,向远程服务器获取存根。但是调用远程方法又必须现有远程对象的Stub,所以这里有个死循环问题。JDK提供了一个RMI注册表(RMIRegistry)来解决这个问题。RMIRegistry也是一个远程对象,默认监听在传说中的1099端口上,可以使用代码启动RMIRegistry,也可以使用RMIRegistry命令。

注册远程对象

深入理解JNDI注入_第4张图片

获取远程对象
深入理解JNDI注入_第5张图片

RMI调用关系:

深入理解JNDI注入_第6张图片

从客户端角度看,服务端应用是有两个端口的,一个是RMI Resgistry端口(默认为1099),另一个是远程对象的通信端口(随机分配)

1.4 动态加载类

RMI核心特点之一是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程去下载这个类的class,动态加载对象的class文件。这可以动态的扩展远程远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI的应用。对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则需要有运行时动态加载额外类的能力。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。

JNDI注入就是利用了动态加载类的思路。

JNDI注入

2.1关于JNDI

简单的来说,JNDI(Java Naming And Directory Interface)是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象、和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或者一个远程Java对象。JDNI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。

JNDI支持多种命名和目录提供程序,RMI注册表服务提供程序(RMI Registry Service Provider)允许通过JNDI应用接口对RMI中注册的远程对象进行访问操作。将RMI服务绑定到JNDI的一个好处是更加透明、统一和松耦合,RMI客户端直接通过URL定位一个远程对象,而且该RMI服务可以和包含人员,组织和网络资源等信息的企业目录链接在一起。

深入理解JNDI注入_第7张图片

JNDI接口在初始化时,可以将RMI URL作为参数传入,而JNDI注入就出现在客户端的lookup()函数中,如果lookup()的参数可控就可能被攻击。

深入理解JNDI注入_第8张图片

2.2JNDI References进行注入

JNDI References注入是JNDI的核心部分。

在JNDI服务中,RMI服务端除了直接绑定远程对象外,还可以通过References类绑定一个外部的远程对象(当前名称目录系统之外的对象)。绑定了References之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端或获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。

整个利用过程如下:

目标代码中调用了context.lookup(url),且url为用户可控;

攻击者控制url参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server/name;

攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;

目标在lookp()操作时,会动态加载并实例化Factory类,接着调用Factory.getObjectlnstance()获取外部远程对象实例

攻击者可以在Factory类文件的构造方法,静态代码块,getObjectInstancc()方法等处写入恶意代码,达到RCE的效果。

深入理解JNDI注入_第9张图片

如此看来JNDI注入的方式十分简单,但是在jdk8u191之后进行了修复,使得我们现在的方法失效。

2.3 8u191之后的JNDI注入

来看看做了哪些修护

深入理解JNDI注入_第10张图片

深入理解JNDI注入_第11张图片

可以看到高版本的jdk默认将trustURLCodebase置为false,使得客户端无法动态的加载类。

2.4JNDI 绕过

我们可以让var8.getFactoryClassLocation()为空,跳过这个if进入else

深入理解JNDI注入_第12张图片

跟进Naming.getObjectInstance(),这里会从ref中拿到ObjectFactory对象,然后如果不为空使用这个ObjectFactory对象创建类的实例

深入理解JNDI注入_第13张图片

继续跟进factory.getObjectFactoryFromReference() ,创建了ELProcessor实例,然后取出key为“forceString”的RefAddr中的context,然后进行进一步解析

深入理解JNDI注入_第14张图片

解析后将 = 两边分开,右边的值可以替换setter方法,这里替换为eval()

深入理解JNDI注入_第15张图片
然后将等号左边的值传入

深入理解JNDI注入_第16张图片

然后通过反射,将参数放到参数数组里,通过method.invoke()执行

深入理解JNDI注入_第17张图片

最后弹出远程连接窗口,成功注入!

深入理解JNDI注入_第18张图片

你可能感兴趣的:(java)