Skip to content

Commit

Permalink
Add dangerous type blacklist feature to Akka.Serialization.Hyperion (#…
Browse files Browse the repository at this point in the history
…5208)

* Add deserialization type blacklist security feature

* Add spec

* Fix spec

* Add documentation

* Improve documentation

Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
Arkatufus and Aaronontheweb authored Aug 17, 2021
1 parent cfa1f7f commit 9364bdc
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 27 deletions.
51 changes: 51 additions & 0 deletions docs/articles/networking/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,57 @@ akka {
}
```

## Danger of polymorphic serializer
One of the danger of polymorphic serializers is the danger of unsafe object type injection into
the serialization-deserialization chain. This issue applies to any type of polymorphic serializer,
including JSON, BinaryFormatter, etc. In Akka, this issue primarily affects developers who allow third parties to pass messages directly
to unsecured Akka.Remote endpoints, a [practice that we do not encourage](https://getakka.net/articles/remoting/security.html#akkaremote-with-virtual-private-networks).

Generally, there are two approaches you can take to alleviate this problem:
1. Implement a schema-based serialization that are contract bound, which is more expensive to setup at first but fundamentally faster and more secure.
2. Implement a filtering or blacklist to block dangerous types.

An example of using a schema-based serialization in Akka can be read under the title "Using Google
Protocol Buffers to Version State and Messages" in [this documentation](https://petabridge.com/cluster/lesson3)

Hyperion chose to implement the second approach by blacklisting a set of potentially dangerous types
from being deserialized:

- System.Security.Claims.ClaimsIdentity
- System.Windows.Forms.AxHost.State
- System.Windows.Data.ObjectDataProvider
- System.Management.Automation.PSObject
- System.Web.Security.RolePrincipal
- System.IdentityModel.Tokens.SessionSecurityToken
- SessionViewStateHistoryItem
- TextFormattingRunProperties
- ToolboxItemContainer
- System.Security.Principal.WindowsClaimsIdentity
- System.Security.Principal.WindowsIdentity
- System.Security.Principal.WindowsPrincipal
- System.CodeDom.Compiler.TempFileCollection
- System.IO.FileSystemInfo
- System.Activities.Presentation.WorkflowDesigner
- System.Windows.ResourceDictionary
- System.Windows.Forms.BindingSource
- Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider
- System.Diagnostics.Process
- System.Management.IWbemClassObjectFreeThreaded

Be warned that these class can be used as a man in the middle attack vector, but if you need
to serialize one of these class, you can turn off this feature using this inside your HOCON settings:
```
akka.actor.serialization-settings.hyperion.disallow-unsafe-type = false
```

> [!IMPORTANT]
> This feature is turned on as default since Akka.NET v1.4.24
> [!WARNING]
> Hyperion is __NOT__ designed as a safe serializer to be used in an open network as a client-server
> communication protocol, instead it is designed to be used as a server-server communication protocol,
> preferably inside a closed network system.
## Cross platform serialization compatibility in Hyperion
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.
Hyperion implements a generic way of addressing this issue by transforming the names of these incompatible names during deserialization.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public void Hyperion_serializer_should_have_correct_defaults()
Assert.True(serializer.Settings.VersionTolerance);
Assert.True(serializer.Settings.PreserveObjectReferences);
Assert.Equal("NoKnownTypes", serializer.Settings.KnownTypesProvider.Name);
Assert.True(serializer.Settings.DisallowUnsafeType);
}
}

Expand All @@ -50,6 +51,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_flags()
serialization-settings.hyperion {
preserve-object-references = false
version-tolerance = false
disallow-unsafe-type = false
}
}
");
Expand All @@ -59,6 +61,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_flags()
Assert.False(serializer.Settings.VersionTolerance);
Assert.False(serializer.Settings.PreserveObjectReferences);
Assert.Equal("NoKnownTypes", serializer.Settings.KnownTypesProvider.Name);
Assert.False(serializer.Settings.DisallowUnsafeType);
}
}

Expand All @@ -82,6 +85,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_types_provider_with
Assert.True(serializer.Settings.VersionTolerance);
Assert.True(serializer.Settings.PreserveObjectReferences);
Assert.Equal(typeof(DummyTypesProviderWithDefaultCtor), serializer.Settings.KnownTypesProvider);
Assert.True(serializer.Settings.DisallowUnsafeType);
}
}

Expand All @@ -105,6 +109,7 @@ public void Hyperion_serializer_should_allow_to_setup_custom_types_provider_with
Assert.True(serializer.Settings.VersionTolerance);
Assert.True(serializer.Settings.PreserveObjectReferences);
Assert.Equal(typeof(DummyTypesProvider), serializer.Settings.KnownTypesProvider);
Assert.True(serializer.Settings.DisallowUnsafeType);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Akka.Actor;
Expand Down Expand Up @@ -36,14 +42,16 @@ public void Setup_should_be_converted_to_settings_correctly()
{
var setup = HyperionSerializerSetup.Empty
.WithPreserveObjectReference(true)
.WithKnownTypeProvider<NoKnownTypes>();
.WithKnownTypeProvider<NoKnownTypes>()
.WithDisallowUnsafeType(false);
var settings =
new HyperionSerializerSettings(
false,
false,
typeof(DummyTypesProvider),
new Func<string, string>[] { s => $"{s}.." },
new Surrogate[0]);
new Surrogate[0],
true);
var appliedSettings = setup.ApplySettings(settings);

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

[Fact]
Expand Down Expand Up @@ -115,5 +124,31 @@ public void Setup_surrogate_should_work()
surrogated.Count.Should().Be(1);
surrogated[0].Should().BeEquivalentTo(expected);
}

[Theory]
[MemberData(nameof(DangerousObjectFactory))]
public void Setup_disallow_unsafe_type_should_work(object dangerousObject, Type type)
{
var serializer = new HyperionSerializer((ExtendedActorSystem)Sys, HyperionSerializerSettings.Default);
var serialized = serializer.ToBinary(dangerousObject);
serializer.Invoking(s => s.FromBinary(serialized, type)).Should().Throw<SerializationException>();
}

public static IEnumerable<object[]> DangerousObjectFactory()
{
var isWindow = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

yield return new object[]{ new FileInfo("C:\\Windows\\System32"), typeof(FileInfo) };
yield return new object[]{ new ClaimsIdentity(), typeof(ClaimsIdentity)};
if (isWindow)
{
yield return new object[]{ WindowsIdentity.GetAnonymous(), typeof(WindowsIdentity) };
yield return new object[]{ new WindowsPrincipal(WindowsIdentity.GetAnonymous()), typeof(WindowsPrincipal)};
}
#if NET471
yield return new object[]{ new Process(), typeof(Process)};
#endif
yield return new object[]{ new ClaimsIdentity(), typeof(ClaimsIdentity)};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Akka.Actor;
Expand Down Expand Up @@ -86,7 +86,8 @@ public HyperionSerializer(ExtendedActorSystem system, HyperionSerializerSettings
serializerFactories: null,
knownTypes: provider.GetKnownTypes(),
ignoreISerializable:true,
packageNameOverrides: settings.PackageNameOverrides));
packageNameOverrides: settings.PackageNameOverrides,
disallowUnsafeTypes: settings.DisallowUnsafeType));
}

/// <summary>
Expand Down Expand Up @@ -164,7 +165,8 @@ public sealed class HyperionSerializerSettings
versionTolerance: true,
knownTypesProvider: typeof(NoKnownTypes),
packageNameOverrides: new List<Func<string, string>>(),
surrogates: new Surrogate[0]);
surrogates: new Surrogate[0],
disallowUnsafeType: true);

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

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

var framework = RuntimeInformation.FrameworkDescription;
string frameworkKey;
Expand Down Expand Up @@ -232,7 +234,8 @@ public static HyperionSerializerSettings Create(Config config)
versionTolerance: config.GetBoolean("version-tolerance", true),
knownTypesProvider: type,
packageNameOverrides: packageNameOverrides,
surrogates: surrogates);
surrogates: surrogates,
disallowUnsafeType: config.GetBoolean("disallow-unsafe-type", true));
}

/// <summary>
Expand Down Expand Up @@ -268,6 +271,12 @@ public static HyperionSerializerSettings Create(Config config)
/// Hyperion directly.
/// </summary>
public readonly IEnumerable<Surrogate> Surrogates;

/// <summary>
/// If set, will cause the Hyperion serializer to block potentially dangerous and unsafe types
/// from being deserialized during run-time
/// </summary>
public readonly bool DisallowUnsafeType;

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

/// <summary>
Expand All @@ -295,16 +304,8 @@ public HyperionSerializerSettings(
bool versionTolerance,
Type knownTypesProvider,
IEnumerable<Func<string, string>> packageNameOverrides)
{
knownTypesProvider = knownTypesProvider ?? typeof(NoKnownTypes);
if (!typeof(IKnownTypesProvider).IsAssignableFrom(knownTypesProvider))
throw new ArgumentException($"Known types provider must implement an interface {typeof(IKnownTypesProvider).FullName}");

PreserveObjectReferences = preserveObjectReferences;
VersionTolerance = versionTolerance;
KnownTypesProvider = knownTypesProvider;
PackageNameOverrides = packageNameOverrides;
}
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, packageNameOverrides, new Surrogate[0], true)
{ }

/// <summary>
/// Creates a new instance of a <see cref="HyperionSerializerSettings"/>.
Expand All @@ -321,6 +322,26 @@ public HyperionSerializerSettings(
Type knownTypesProvider,
IEnumerable<Func<string, string>> packageNameOverrides,
IEnumerable<Surrogate> surrogates)
: this(preserveObjectReferences, versionTolerance, knownTypesProvider, packageNameOverrides, surrogates, true)
{ }

/// <summary>
/// Creates a new instance of a <see cref="HyperionSerializerSettings"/>.
/// </summary>
/// <param name="preserveObjectReferences">Flag which determines if serializer should keep track of references in serialized object graph.</param>
/// <param name="versionTolerance">Flag which determines if field data should be serialized as part of type manifest.</param>
/// <param name="knownTypesProvider">Type implementing <see cref="IKnownTypesProvider"/> to be used to determine a list of types implicitly known by all cooperating serializer.</param>
/// <param name="packageNameOverrides">An array of package name overrides for cross platform compatibility</param>
/// <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>
/// <param name="disallowUnsafeType">Block unsafe types from being deserialized.</param>
/// <exception cref="ArgumentException">Raised when `known-types-provider` type doesn't implement <see cref="IKnownTypesProvider"/> interface.</exception>
public HyperionSerializerSettings(
bool preserveObjectReferences,
bool versionTolerance,
Type knownTypesProvider,
IEnumerable<Func<string, string>> packageNameOverrides,
IEnumerable<Surrogate> surrogates,
bool disallowUnsafeType)
{
knownTypesProvider = knownTypesProvider ?? typeof(NoKnownTypes);
if (!typeof(IKnownTypesProvider).IsAssignableFrom(knownTypesProvider))
Expand All @@ -331,6 +352,7 @@ public HyperionSerializerSettings(
KnownTypesProvider = knownTypesProvider;
PackageNameOverrides = packageNameOverrides;
Surrogates = surrogates;
DisallowUnsafeType = disallowUnsafeType;
}
}
}
Loading

0 comments on commit 9364bdc

Please sign in to comment.