Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dangerous type blacklist feature to Akka.Serialization.Hyperion #5208

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Aaronontheweb marked this conversation as resolved.
Show resolved Hide resolved
```

> [!IMPORTANT]
> This feature is turned on as default since Akka.NET v1.4.24

> [!WARNING]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok good, you covered this here.

> 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>();
Aaronontheweb marked this conversation as resolved.
Show resolved Hide resolved
}

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(
Aaronontheweb marked this conversation as resolved.
Show resolved Hide resolved
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