Skip to content

Commit 9364bdc

Browse files
Add dangerous type blacklist feature to Akka.Serialization.Hyperion (#5208)
* Add deserialization type blacklist security feature * Add spec * Fix spec * Add documentation * Improve documentation Co-authored-by: Aaron Stannard <[email protected]>
1 parent cfa1f7f commit 9364bdc

File tree

6 files changed

+171
-27
lines changed

6 files changed

+171
-27
lines changed

docs/articles/networking/serialization.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,57 @@ akka {
304304
}
305305
```
306306

307+
## Danger of polymorphic serializer
308+
One of the danger of polymorphic serializers is the danger of unsafe object type injection into
309+
the serialization-deserialization chain. This issue applies to any type of polymorphic serializer,
310+
including JSON, BinaryFormatter, etc. In Akka, this issue primarily affects developers who allow third parties to pass messages directly
311+
to unsecured Akka.Remote endpoints, a [practice that we do not encourage](https://getakka.net/articles/remoting/security.html#akkaremote-with-virtual-private-networks).
312+
313+
Generally, there are two approaches you can take to alleviate this problem:
314+
1. Implement a schema-based serialization that are contract bound, which is more expensive to setup at first but fundamentally faster and more secure.
315+
2. Implement a filtering or blacklist to block dangerous types.
316+
317+
An example of using a schema-based serialization in Akka can be read under the title "Using Google
318+
Protocol Buffers to Version State and Messages" in [this documentation](https://petabridge.com/cluster/lesson3)
319+
320+
Hyperion chose to implement the second approach by blacklisting a set of potentially dangerous types
321+
from being deserialized:
322+
323+
- System.Security.Claims.ClaimsIdentity
324+
- System.Windows.Forms.AxHost.State
325+
- System.Windows.Data.ObjectDataProvider
326+
- System.Management.Automation.PSObject
327+
- System.Web.Security.RolePrincipal
328+
- System.IdentityModel.Tokens.SessionSecurityToken
329+
- SessionViewStateHistoryItem
330+
- TextFormattingRunProperties
331+
- ToolboxItemContainer
332+
- System.Security.Principal.WindowsClaimsIdentity
333+
- System.Security.Principal.WindowsIdentity
334+
- System.Security.Principal.WindowsPrincipal
335+
- System.CodeDom.Compiler.TempFileCollection
336+
- System.IO.FileSystemInfo
337+
- System.Activities.Presentation.WorkflowDesigner
338+
- System.Windows.ResourceDictionary
339+
- System.Windows.Forms.BindingSource
340+
- Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider
341+
- System.Diagnostics.Process
342+
- System.Management.IWbemClassObjectFreeThreaded
343+
344+
Be warned that these class can be used as a man in the middle attack vector, but if you need
345+
to serialize one of these class, you can turn off this feature using this inside your HOCON settings:
346+
```
347+
akka.actor.serialization-settings.hyperion.disallow-unsafe-type = false
348+
```
349+
350+
> [!IMPORTANT]
351+
> This feature is turned on as default since Akka.NET v1.4.24
352+
353+
> [!WARNING]
354+
> Hyperion is __NOT__ designed as a safe serializer to be used in an open network as a client-server
355+
> communication protocol, instead it is designed to be used as a server-server communication protocol,
356+
> preferably inside a closed network system.
357+
307358
## Cross platform serialization compatibility in Hyperion
308359
There are problems that can arise when migrating from old .NET Framework to the new .NET Core standard, mainly because of breaking namespace and assembly name changes between these platforms.
309360
Hyperion implements a generic way of addressing this issue by transforming the names of these incompatible names during deserialization.

src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionConfigTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public void Hyperion_serializer_should_have_correct_defaults()
3535
Assert.True(serializer.Settings.VersionTolerance);
3636
Assert.True(serializer.Settings.PreserveObjectReferences);
3737
Assert.Equal("NoKnownTypes", serializer.Settings.KnownTypesProvider.Name);
38+
Assert.True(serializer.Settings.DisallowUnsafeType);
3839
}
3940
}
4041

@@ -50,6 +51,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_flags()
5051
serialization-settings.hyperion {
5152
preserve-object-references = false
5253
version-tolerance = false
54+
disallow-unsafe-type = false
5355
}
5456
}
5557
");
@@ -59,6 +61,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_flags()
5961
Assert.False(serializer.Settings.VersionTolerance);
6062
Assert.False(serializer.Settings.PreserveObjectReferences);
6163
Assert.Equal("NoKnownTypes", serializer.Settings.KnownTypesProvider.Name);
64+
Assert.False(serializer.Settings.DisallowUnsafeType);
6265
}
6366
}
6467

@@ -82,6 +85,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_types_provider_with
8285
Assert.True(serializer.Settings.VersionTolerance);
8386
Assert.True(serializer.Settings.PreserveObjectReferences);
8487
Assert.Equal(typeof(DummyTypesProviderWithDefaultCtor), serializer.Settings.KnownTypesProvider);
88+
Assert.True(serializer.Settings.DisallowUnsafeType);
8589
}
8690
}
8791

@@ -105,6 +109,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_types_provider_with
105109
Assert.True(serializer.Settings.VersionTolerance);
106110
Assert.True(serializer.Settings.PreserveObjectReferences);
107111
Assert.Equal(typeof(DummyTypesProvider), serializer.Settings.KnownTypesProvider);
112+
Assert.True(serializer.Settings.DisallowUnsafeType);
108113
}
109114
}
110115

src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionSerializerSetupSpec.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
35
using System.Linq;
6+
using System.Runtime.InteropServices;
7+
using System.Runtime.Serialization;
8+
using System.Security.Claims;
9+
using System.Security.Principal;
410
using System.Text;
511
using System.Threading.Tasks;
612
using Akka.Actor;
@@ -36,14 +42,16 @@ public void Setup_should_be_converted_to_settings_correctly()
3642
{
3743
var setup = HyperionSerializerSetup.Empty
3844
.WithPreserveObjectReference(true)
39-
.WithKnownTypeProvider<NoKnownTypes>();
45+
.WithKnownTypeProvider<NoKnownTypes>()
46+
.WithDisallowUnsafeType(false);
4047
var settings =
4148
new HyperionSerializerSettings(
4249
false,
4350
false,
4451
typeof(DummyTypesProvider),
4552
new Func<string, string>[] { s => $"{s}.." },
46-
new Surrogate[0]);
53+
new Surrogate[0],
54+
true);
4755
var appliedSettings = setup.ApplySettings(settings);
4856

4957
appliedSettings.PreserveObjectReferences.Should().BeTrue(); // overriden
@@ -52,6 +60,7 @@ public void Setup_should_be_converted_to_settings_correctly()
5260
appliedSettings.PackageNameOverrides.Count().Should().Be(1); // from settings
5361
appliedSettings.PackageNameOverrides.First()("a").Should().Be("a..");
5462
appliedSettings.Surrogates.ToList().Count.Should().Be(0); // from settings
63+
appliedSettings.DisallowUnsafeType.ShouldBe(false); // overriden
5564
}
5665

5766
[Fact]
@@ -115,5 +124,31 @@ public void Setup_surrogate_should_work()
115124
surrogated.Count.Should().Be(1);
116125
surrogated[0].Should().BeEquivalentTo(expected);
117126
}
127+
128+
[Theory]
129+
[MemberData(nameof(DangerousObjectFactory))]
130+
public void Setup_disallow_unsafe_type_should_work(object dangerousObject, Type type)
131+
{
132+
var serializer = new HyperionSerializer((ExtendedActorSystem)Sys, HyperionSerializerSettings.Default);
133+
var serialized = serializer.ToBinary(dangerousObject);
134+
serializer.Invoking(s => s.FromBinary(serialized, type)).Should().Throw<SerializationException>();
135+
}
136+
137+
public static IEnumerable<object[]> DangerousObjectFactory()
138+
{
139+
var isWindow = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
140+
141+
yield return new object[]{ new FileInfo("C:\\Windows\\System32"), typeof(FileInfo) };
142+
yield return new object[]{ new ClaimsIdentity(), typeof(ClaimsIdentity)};
143+
if (isWindow)
144+
{
145+
yield return new object[]{ WindowsIdentity.GetAnonymous(), typeof(WindowsIdentity) };
146+
yield return new object[]{ new WindowsPrincipal(WindowsIdentity.GetAnonymous()), typeof(WindowsPrincipal)};
147+
}
148+
#if NET471
149+
yield return new object[]{ new Process(), typeof(Process)};
150+
#endif
151+
yield return new object[]{ new ClaimsIdentity(), typeof(ClaimsIdentity)};
152+
}
118153
}
119154
}

src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
using System;
99
using System.Collections.Generic;
10+
using System.Diagnostics;
1011
using System.IO;
1112
using System.Linq;
12-
using System.Reflection;
1313
using System.Runtime.InteropServices;
1414
using System.Runtime.Serialization;
1515
using Akka.Actor;
@@ -86,7 +86,8 @@ public HyperionSerializer(ExtendedActorSystem system, HyperionSerializerSettings
8686
serializerFactories: null,
8787
knownTypes: provider.GetKnownTypes(),
8888
ignoreISerializable:true,
89-
packageNameOverrides: settings.PackageNameOverrides));
89+
packageNameOverrides: settings.PackageNameOverrides,
90+
disallowUnsafeTypes: settings.DisallowUnsafeType));
9091
}
9192

9293
/// <summary>
@@ -164,7 +165,8 @@ public sealed class HyperionSerializerSettings
164165
versionTolerance: true,
165166
knownTypesProvider: typeof(NoKnownTypes),
166167
packageNameOverrides: new List<Func<string, string>>(),
167-
surrogates: new Surrogate[0]);
168+
surrogates: new Surrogate[0],
169+
disallowUnsafeType: true);
168170

169171
/// <summary>
170172
/// Creates a new instance of <see cref="HyperionSerializerSettings"/> using provided HOCON config.
@@ -185,7 +187,7 @@ public static HyperionSerializerSettings Create(Config config)
185187
throw ConfigurationException.NullOrEmptyConfig<HyperionSerializerSettings>("akka.actor.serialization-settings.hyperion");
186188

187189
var typeName = config.GetString("known-types-provider", null);
188-
var type = !string.IsNullOrEmpty(typeName) ? Type.GetType(typeName, true) : null;
190+
var type = !string.IsNullOrWhiteSpace(typeName) ? Type.GetType(typeName, true) : null;
189191

190192
var framework = RuntimeInformation.FrameworkDescription;
191193
string frameworkKey;
@@ -232,7 +234,8 @@ public static HyperionSerializerSettings Create(Config config)
232234
versionTolerance: config.GetBoolean("version-tolerance", true),
233235
knownTypesProvider: type,
234236
packageNameOverrides: packageNameOverrides,
235-
surrogates: surrogates);
237+
surrogates: surrogates,
238+
disallowUnsafeType: config.GetBoolean("disallow-unsafe-type", true));
236239
}
237240

238241
/// <summary>
@@ -268,6 +271,12 @@ public static HyperionSerializerSettings Create(Config config)
268271
/// Hyperion directly.
269272
/// </summary>
270273
public readonly IEnumerable<Surrogate> Surrogates;
274+
275+
/// <summary>
276+
/// If set, will cause the Hyperion serializer to block potentially dangerous and unsafe types
277+
/// from being deserialized during run-time
278+
/// </summary>
279+
public readonly bool DisallowUnsafeType;
271280

272281
/// <summary>
273282
/// Creates a new instance of a <see cref="HyperionSerializerSettings"/>.
@@ -278,7 +287,7 @@ public static HyperionSerializerSettings Create(Config config)
278287
/// <exception cref="ArgumentException">Raised when `known-types-provider` type doesn't implement <see cref="IKnownTypesProvider"/> interface.</exception>
279288
[Obsolete]
280289
public HyperionSerializerSettings(bool preserveObjectReferences, bool versionTolerance, Type knownTypesProvider)
281-
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, new List<Func<string, string>>())
290+
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, new List<Func<string, string>>(), new Surrogate[0], true)
282291
{ }
283292

284293
/// <summary>
@@ -295,16 +304,8 @@ public HyperionSerializerSettings(
295304
bool versionTolerance,
296305
Type knownTypesProvider,
297306
IEnumerable<Func<string, string>> packageNameOverrides)
298-
{
299-
knownTypesProvider = knownTypesProvider ?? typeof(NoKnownTypes);
300-
if (!typeof(IKnownTypesProvider).IsAssignableFrom(knownTypesProvider))
301-
throw new ArgumentException($"Known types provider must implement an interface {typeof(IKnownTypesProvider).FullName}");
302-
303-
PreserveObjectReferences = preserveObjectReferences;
304-
VersionTolerance = versionTolerance;
305-
KnownTypesProvider = knownTypesProvider;
306-
PackageNameOverrides = packageNameOverrides;
307-
}
307+
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, packageNameOverrides, new Surrogate[0], true)
308+
{ }
308309

309310
/// <summary>
310311
/// Creates a new instance of a <see cref="HyperionSerializerSettings"/>.
@@ -321,6 +322,26 @@ public HyperionSerializerSettings(
321322
Type knownTypesProvider,
322323
IEnumerable<Func<string, string>> packageNameOverrides,
323324
IEnumerable<Surrogate> surrogates)
325+
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, packageNameOverrides, surrogates, true)
326+
{ }
327+
328+
/// <summary>
329+
/// Creates a new instance of a <see cref="HyperionSerializerSettings"/>.
330+
/// </summary>
331+
/// <param name="preserveObjectReferences">Flag which determines if serializer should keep track of references in serialized object graph.</param>
332+
/// <param name="versionTolerance">Flag which determines if field data should be serialized as part of type manifest.</param>
333+
/// <param name="knownTypesProvider">Type implementing <see cref="IKnownTypesProvider"/> to be used to determine a list of types implicitly known by all cooperating serializer.</param>
334+
/// <param name="packageNameOverrides">An array of package name overrides for cross platform compatibility</param>
335+
/// <param name="surrogates">A list of <see cref="Surrogate"/> instances that are used to de/serialize complex objects into a much simpler serialized objects.</param>
336+
/// <param name="disallowUnsafeType">Block unsafe types from being deserialized.</param>
337+
/// <exception cref="ArgumentException">Raised when `known-types-provider` type doesn't implement <see cref="IKnownTypesProvider"/> interface.</exception>
338+
public HyperionSerializerSettings(
339+
bool preserveObjectReferences,
340+
bool versionTolerance,
341+
Type knownTypesProvider,
342+
IEnumerable<Func<string, string>> packageNameOverrides,
343+
IEnumerable<Surrogate> surrogates,
344+
bool disallowUnsafeType)
324345
{
325346
knownTypesProvider = knownTypesProvider ?? typeof(NoKnownTypes);
326347
if (!typeof(IKnownTypesProvider).IsAssignableFrom(knownTypesProvider))
@@ -331,6 +352,7 @@ public HyperionSerializerSettings(
331352
KnownTypesProvider = knownTypesProvider;
332353
PackageNameOverrides = packageNameOverrides;
333354
Surrogates = surrogates;
355+
DisallowUnsafeType = disallowUnsafeType;
334356
}
335357
}
336358
}

0 commit comments

Comments
 (0)