作者:且聽安全
原文鏈接:https://mp.weixin.qq.com/s/F2jFMkmN3K9yn_uuICStuA

概述

很多 .NET 應用程序在修復 BinaryFormatterSoapFormatterLosFormatterNetDataContractSerializerObjectStateFormatter 等反序列化漏洞時,喜歡通過自定義 SerializationBinder 來限定類型,從而達到緩解反序列化攻擊的目的。歷史上很多 .NET 反序列化漏洞都采用了這種方法,但是我們查看微軟官方的警告說明:

圖片

使用 SerializationBinder 無法完全修復反序列化漏洞隱患。最近看到老外發了一篇相關文章,感覺很有價值,自己也深入研究總結了兩種不安全的 SerializationBinder 限定方式,下面分享給大家。

SerializationBinder 綁定限制

常見修復方式就是對 BinaryFormatter 反序列化過程綁定 Binder 對象,通過 SerializationBinder 來檢查反序列化類型。構建如下 demo

反序列化操作如下:

using (var fileStream = new FileStream(file, FileMode.Open))
{
    BinaryFormatter formatter = new BinaryFormatter();
    fileStream.Position = 0;
    formatter.Binder=new SafeDeserializationBinder();
    formatter.Deserialize(fileStream);
}

自定義 SafeDeserializationBinder 繼承于 SerializationBinder ,通過黑名單機制進行檢查,當發現存在惡意類型時,比如 System.Data.DataSet ,將阻斷反序列化過程:

public class SafeDeserializationBinder : SerializationBinder
{
    List<String> blackTypeName = new List<string> { };

    private void _AddBlackList()
    {
        blackTypeName.Add("System.Data.DataSet");
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        this._AddBlackList();
        foreach (var t in blackTypeName)
        {
            if (typeName.Equals(t))
            {
                //todo
            }
        }
        return Type.GetType(typeName);
    }
}

Bypass 1 :無效的 null 返回值

大家很容易想到,當檢測到反序列化黑名單,直接返回 null

圖片

這樣真的可以阻斷反序列化漏洞嗎?我們可以進行測試。利用 YSoSerial.Net 特定生成 System.Data.DataSet 的反序列化載荷:

ysoserial.exe -o raw -f BinaryFormatter -g DataSet -c calc >payload.txt

圖片

確實返回了 null ,但是發現最終還是執行了反序列化操作并觸發了 RCE:

圖片

為什么呢?下面調試分析一下原因。BinaryFormatter 反序列化時將調用 ObjectReader#Bind 來獲取 Type 類型:

圖片

首先調用自定義的 SafeDeserializationBinder#BindToType ,當返回 null 時,函數并沒有直接結束,而是繼續調用 FastBindToType 來獲取 Type 對象:

圖片

首先嘗試從 typecache 緩存中提取,程序首次調用獲取不到值,繼續判斷 bSimpleAssembly 的取值(默認始終為 true),進而嘗試調用 GetSimplyNamedTypeFromAssembly

圖片

通過 FormatterServices#GetTypeFromAssembly 最終取到了 type 的值:

圖片

所以嘗試在 SerializationBinder 加載惡意 Type 時通過返回 null 是無法阻斷反序列化漏洞的。比如 Exchange CVE-2022-23277 就是由于在遇到黑名單時最終返回 null 從而導致被繞過。要想 SerializationBinder 有效,正確的做法是拋出異常,修改 demo 如下:

拋出異常將中斷后續處理流程,導致反序列化綁定的 Type 最終確定為 null ,從而無法觸發反序列化漏洞。

Bypass 2 :拋出異常真的安全嗎?

上面通過拋出異常的方式真的能夠完全修復漏洞嗎?答案是否定的。我們思考下既然反序列化操作可以通過 BindToType 檢查 TypeAssembly ,那么在生成序列化載荷時,就可能可以自定義 TypeAssembly ,查看 YSoSerial.Net 生成 System.Data.DataSet 的反序列化載荷的代碼段 ( /Generators/DataSetGenerator.cs ):

圖片

我們可以手動去修改 Type 的賦值過程,確保讓其不位于黑名單之中,比如:

圖片

重新編譯生成 YSoSerial.Net ,并再次生成新的 DataSet 反序列化載荷:

圖片

此時 typeName 并不在黑名單之中,所以不會拋出異常,但是卻成功返回了正確的 Type 類型,從而繞過檢查進而實現了 RCE :

圖片

比如 DevExpress CVE-2022-28684 反序列化漏洞就是通過類似上面這種方式實現 Bypass 的。

小結

通過 SerializationBinder 綁定 Type 類型來緩解反序列化漏洞,無論是直接返回 null 還是拋出異常,都存在被繞過的風險,最好的修復方式其實微軟官方已經給出了答案,那就是不要使用 BinaryFormatter 這類反序列化類:

圖片


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1927/