diff --git a/PSFramework/PSFramework.psd1 b/PSFramework/PSFramework.psd1
index e2417384..a7554df0 100644
--- a/PSFramework/PSFramework.psd1
+++ b/PSFramework/PSFramework.psd1
@@ -4,7 +4,7 @@
RootModule = 'PSFramework.psm1'
# Version number of this module.
- ModuleVersion = '1.11.343'
+ ModuleVersion = '1.12.345'
# ID used to uniquely identify this module
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
diff --git a/PSFramework/bin/PSFramework.dll b/PSFramework/bin/PSFramework.dll
index e3484727..df69ae40 100644
Binary files a/PSFramework/bin/PSFramework.dll and b/PSFramework/bin/PSFramework.dll differ
diff --git a/PSFramework/bin/PSFramework.pdb b/PSFramework/bin/PSFramework.pdb
index 5b2617dc..fd07ac09 100644
Binary files a/PSFramework/bin/PSFramework.pdb and b/PSFramework/bin/PSFramework.pdb differ
diff --git a/PSFramework/bin/PSFramework.xml b/PSFramework/bin/PSFramework.xml
index 73d9f8f7..57b50953 100644
--- a/PSFramework/bin/PSFramework.xml
+++ b/PSFramework/bin/PSFramework.xml
@@ -7546,6 +7546,20 @@
Allows remapping assembly-names for objects being deserialized, using an abbreviated name only, to help avoid having to be version specific.
+
+
+ Compress string using default zip algorithms
+
+ The string to compress
+ Returns a compressed string as byte-array.
+
+
+
+ Expand a string using default zig algorithms
+
+ The compressed string to expand
+ Returns an expanded string.
+
Compares two completion results
diff --git a/PSFramework/changelog.md b/PSFramework/changelog.md
index f221e33f..2e0082b6 100644
--- a/PSFramework/changelog.md
+++ b/PSFramework/changelog.md
@@ -1,5 +1,25 @@
# CHANGELOG
+## 1.12.345 (2024-09-17)
+
+> Breaking Change
+
+SerializationTypeConverter changed from using BinaryFormatter to using DataContractSerializer instead, avoiding a critical security vulnerability. This change will _not_ affect anybody not using this component to prevent Deserialized objects when sending objects from formal classes from one PowerShell process to another (e.g. with remoting). Regular PowerShell execution - including remoting - remains unaffected (only without the vulnerability).
+
+Actual impact on modules implementing this component:
+
+- "Failure" always means a fallback to "Deserialized." objects, not actual exceptions.
+- The new version must be deployed on both ends of the communication, otherwise implemented deserialization will fail.
+- The new version will fail to import clixml files exported with the old version
+- All sub-properties must adhere to the serialization rules, not just the top level class. Previously it was possible to have your own class have an "object"-typed property and only the content of that property would be a "deserialized." object, rather the entire item. This no longer works.
+
+This critical security vulnerability superseded the reliability promise, but should fortunately have little impact on almost all existing use of the module.
+
+> Change List
+
+- Sec: Critical security update to the `SerializationTypeConverter` class and PS Object Serialization extension component.
+- Fix: ConvertTo-PSFHashtable - `-Remap` fails when trying to fix the casing on a key. (#641)
+
## 1.11.343 (2024-07-18)
- Fix: Disable-PSFLoggingProvider - fails with timeout error.
diff --git a/library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs b/library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs
index d8677dbc..9188d763 100644
--- a/library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs
+++ b/library/PSFramework/Commands/ConvertToPSFHashtableCommand.cs
@@ -182,8 +182,9 @@ protected override void ProcessRecord()
{
if (result.ContainsKey(key))
{
- result[Remap[key]] = result[key];
+ object value = result[key];
result.Remove(key);
+ result[Remap[key]] = value;
}
}
}
diff --git a/library/PSFramework/PSFramework.csproj b/library/PSFramework/PSFramework.csproj
index 1346d4cb..e57a127a 100644
--- a/library/PSFramework/PSFramework.csproj
+++ b/library/PSFramework/PSFramework.csproj
@@ -53,6 +53,7 @@
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll
False
+
diff --git a/library/PSFramework/Serialization/SerializationTypeConverter.cs b/library/PSFramework/Serialization/SerializationTypeConverter.cs
index 3de488ce..9880678b 100644
--- a/library/PSFramework/Serialization/SerializationTypeConverter.cs
+++ b/library/PSFramework/Serialization/SerializationTypeConverter.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.IO;
+using System.IO.Compression;
using System.Management.Automation;
using System.Reflection;
using System.Runtime.Serialization;
-using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
namespace PSFramework.Serialization
{
@@ -14,7 +17,7 @@ namespace PSFramework.Serialization
///
public class SerializationTypeConverter : PSTypeConverter
{
- private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(SerializationTypeConverter.CurrentDomain_AssemblyResolve);
+ private static ResolveEventHandler AssemblyHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
///
/// Whether the source can be converted to its destination
@@ -87,7 +90,7 @@ private bool CanConvert(object sourceValue, Type destinationType, out byte[] ser
error = new NotSupportedException(string.Format("Unsupported Source Type: {0}", sourceValue.GetType().FullName));
return false;
}
- if (!SerializationTypeConverter.CanSerialize(destinationType))
+ if (!CanSerialize(destinationType))
{
error = new NotSupportedException(string.Format("Unsupported Type Conversion: {0}", destinationType.FullName));
return false;
@@ -125,24 +128,19 @@ private object DeserializeObject(object sourceValue, Type destinationType)
throw ex;
}
object obj;
- using (MemoryStream memoryStream = new MemoryStream(buffer))
+
+ AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
+ try
{
- AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
- try
- {
- BinaryFormatter binaryFormatter = new BinaryFormatter();
- obj = binaryFormatter.Deserialize(memoryStream);
- PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
- IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
- if (deserializationCallback != null)
- {
- deserializationCallback.OnDeserialization(sourceValue);
- }
- }
- finally
- {
- AppDomain.CurrentDomain.AssemblyResolve -= SerializationTypeConverter.AssemblyHandler;
- }
+ obj = ConvertFromXml(ExpandString(buffer), destinationType);
+ PSFCore.PSFCoreHost.WriteDebug("Serializer.DeserializeObject.Obj", obj);
+ IDeserializationCallback deserializationCallback = obj as IDeserializationCallback;
+ if (deserializationCallback != null)
+ deserializationCallback.OnDeserialization(sourceValue);
+ }
+ finally
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= AssemblyHandler;
}
return obj;
}
@@ -152,13 +150,13 @@ private object DeserializeObject(object sourceValue, Type destinationType)
///
public static void RegisterAssemblyResolver()
{
- AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;
+ AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler;
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Sender", sender);
PSFCore.PSFCoreHost.WriteDebug("Serializer.AssemblyResolve.Args", args);
-
+
// 1) Match directly against existing assembly
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
@@ -169,7 +167,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
string shortName = args.Name.Split(',')[0];
if (AssemblyShortnameMapping.Count > 0 && AssemblyShortnameMapping[shortName])
for (int i = 0; i < assemblies.Length; i++)
- if (String.Equals(assemblies[i].FullName.Split(',')[0],shortName, StringComparison.InvariantCultureIgnoreCase))
+ if (String.Equals(assemblies[i].FullName.Split(',')[0], shortName, StringComparison.InvariantCultureIgnoreCase))
return assemblies[i];
if (AssemblyMapping.Count == 0)
@@ -201,7 +199,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
/// Whether the object can be serialized
public static bool CanSerialize(object obj)
{
- return obj != null && SerializationTypeConverter.CanSerialize(obj.GetType());
+ return obj != null && CanSerialize(obj.GetType());
}
///
@@ -211,7 +209,7 @@ public static bool CanSerialize(object obj)
/// Whether the specified type can be serialized
public static bool CanSerialize(Type type)
{
- return SerializationTypeConverter.TypeIsSerializable(type) && !type.IsEnum || (type.Equals(typeof(Exception)) || type.IsSubclassOf(typeof(Exception)));
+ return TypeIsSerializable(type) && !type.IsEnum || (type.Equals(typeof(Exception)) || type.IsSubclassOf(typeof(Exception)));
}
///
@@ -237,7 +235,7 @@ public static bool TypeIsSerializable(Type type)
for (int i = 0; i < genericArguments.Length; i++)
{
Type type2 = genericArguments[i];
- if (!SerializationTypeConverter.TypeIsSerializable(type2))
+ if (!TypeIsSerializable(type2))
{
return false;
}
@@ -252,16 +250,9 @@ public static bool TypeIsSerializable(Type type)
/// A memory stream.
public static object GetSerializationData(PSObject psObject)
{
- object result;
- using (MemoryStream memoryStream = new MemoryStream())
- {
- BinaryFormatter binaryFormatter = new BinaryFormatter();
- binaryFormatter.Serialize(memoryStream, psObject.BaseObject);
- result = memoryStream.ToArray();
- }
- return result;
+ return CompressString(ConvertToXml(psObject.BaseObject));
}
-
+
///
/// Allows remapping assembly-names for objects being deserialized, using the full assembly-name.
///
@@ -270,5 +261,71 @@ public static object GetSerializationData(PSObject psObject)
/// Allows remapping assembly-names for objects being deserialized, using an abbreviated name only, to help avoid having to be version specific.
///
public static readonly ConcurrentDictionary AssemblyShortnameMapping = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ public static string ConvertToXml(object Item)
+ {
+ if (Item == null)
+ throw new ArgumentNullException("item");
+
+ string result;
+ using (StringWriter writer = new StringWriter())
+ using (XmlTextWriter xmlWriter = new XmlTextWriter(writer))
+ {
+ DataContractSerializer serializer = new DataContractSerializer(Item.GetType());
+ serializer.WriteObject(xmlWriter, Item);
+ result = writer.ToString();
+ }
+ return result;
+ }
+
+ public static object ConvertFromXml(string Xml, Type ExpectedType)
+ {
+ object result;
+ using (StringReader reader = new StringReader(Xml))
+ using (XmlTextReader xmlReader = new XmlTextReader(reader))
+ {
+ DataContractSerializer serializer = new DataContractSerializer(ExpectedType);
+ result = serializer.ReadObject(xmlReader);
+ }
+ return result;
+ }
+
+ ///
+ /// Compress string using default zip algorithms
+ ///
+ /// The string to compress
+ /// Returns a compressed string as byte-array.
+ public static byte[] CompressString(string String)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(String);
+ using (MemoryStream outputStream = new MemoryStream())
+ using (GZipStream gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
+ {
+ gZipStream.Write(bytes, 0, bytes.Length);
+ gZipStream.Close();
+ outputStream.Close();
+ return outputStream.ToArray();
+ }
+ }
+
+ ///
+ /// Expand a string using default zig algorithms
+ ///
+ /// The compressed string to expand
+ /// Returns an expanded string.
+ public static string ExpandString(byte[] CompressedString)
+ {
+ using (MemoryStream inputStream = new MemoryStream(CompressedString))
+ using (MemoryStream outputStream = new MemoryStream())
+ using (GZipStream converter = new GZipStream(inputStream, CompressionMode.Decompress))
+ {
+ converter.CopyTo(outputStream);
+ converter.Close();
+ inputStream.Close();
+ string result = Encoding.UTF8.GetString(outputStream.ToArray());
+ outputStream.Close();
+ return result;
+ }
+ }
}
}