深入理解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字段要和服务器端保持一致。
文章图片
文章图片
在JVM 之间通信时,RMI远程对象和非远程对象的处理方式是不一样的,它并没有直接把远程对象复制一份给客户端,而是传递一个远程对象的Stub,Stub相当于是远程对象的引用或者代理。Stub对开发者是透明的,客户端可以像调用本地方法一样通过它来调用远程方法。Stub中包含了远程对象的定位信息,如Sockect端口,服务端主机地址等,并实现了远程调用过程中具体的底层通信细节,所以RMI远程调用逻辑是这样的:
文章图片
从逻辑上看,数据是在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命令。
注册远程对象
文章图片
获取远程对象
文章图片
RMI调用关系:
文章图片
从客户端角度看,服务端应用是有两个端口的,一个是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接口在初始化时,可以将RMI URL作为参数传入,而JNDI注入就出现在客户端的lookup()函数中,如果lookup()的参数可控就可能被攻击。
文章图片
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注入的方式十分简单,但是在jdk8u191之后进行了修复,使得我们现在的方法失效。
2.3 8u191之后的JNDI注入
来看看做了哪些修护
文章图片
文章图片
可以看到高版本的jdk默认将trustURLCodebase置为false,使得客户端无法动态的加载类。
2.4JNDI 绕过
我们可以让var8.getFactoryClassLocation()为空,跳过这个if进入else
文章图片
跟进Naming.getObjectInstance(),这里会从ref中拿到ObjectFactory对象,然后如果不为空使用这个ObjectFactory对象创建类的实例
文章图片
继续跟进factory.getObjectFactoryFromReference() ,创建了ELProcessor实例,然后取出key为“forceString”的RefAddr中的context,然后进行进一步解析
文章图片
解析后将 = 两边分开,右边的值可以替换setter方法,这里替换为eval()
文章图片
然后将等号左边的值传入
文章图片
然后通过反射,将参数放到参数数组里,通过method.invoke()执行
文章图片
最后弹出远程连接窗口,成功注入!
【深入理解JNDI注入】
文章图片
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)