logo
DATABASE RESOURCES PRICING ABOUT US

CVE-2022-21969

Description

Microsoft Exchange Server Remote Code Execution Vulnerability. This CVE ID is unique from CVE-2022-21846, CVE-2022-21855. **Recent assessments:** **gwillcox-r7** at July 10, 2022 7:02am UTC reported: There is a nice writeup on this at <https://medium.com/@frycos/searching-for-deserialization-protection-bypasses-in-microsoft-exchange-cve-2022-21969-bfa38f63a62d>. The bug appears to be a deserialization bug that occurs when loading a specific file, however according to the demo video at <https://gist.github.com/Frycos/a446d86d75c09330d77f37ca28923ddd> it seems to be more of a local attack. That being said it would grant you an LPE to SYSTEM if you were able to trigger it. Furthermore Frycos mentions that he thinks Microsoft didn’t fix the root issue when he wrote the blog (as of January 12th 2022), so its possible the root issue wasn’t fixed, though Frycos mentioned he didn’t look into this further. From <https://twitter.com/MCKSysAr/status/1524518517990727683> it does seem like at the very least some exploitation attempts have been made to try exploit this although writing to `C:\Program Files\Microsoft\Exchange Server\V15\UnifiedMessaging\voicemail` to trigger the bug via making it process a voicemail has proven to be difficult to do. It does however note my tip, shown later in this writeup, of how to bypass the deny list by using `System.Runtime.Remoting.ObjRef` as was pointed out online, was valid. What follows below is some of my notes that I wrote up a while back and never published. Hopefully they are of help to someone. # Overview ## Background info Deserialization vulnerability leading to RCE potentially. Got a CVSS 3.1 score of 9.0 with a temporal score metric score of 7.8. Interesting that it mentions the attack vector is Adjacent and the article notes that this may be only cause of the way that he exploited it and may indicate they didn’t fix the root issue. Low attack complexity and low privileges required seems to indicate it may be authenticated but you don’t need many privileges??? I need to check on this further. High impact on everything else suggest this is a full compromise; this would be in line with leaking the hash. ## Affected * Microsoft Exchange Server 2019 Cumulative Update 11 prior to January 2022 security update. * Microsoft Exchange Server 2019 Cumulative Update 10 prior to January 2022 security update. * Microsoft Exchange Server 2016 Cumulative Update 22 prior to January 2022 security update. * Microsoft Exchange Server 2016 Cumulative Update 21 prior to January 2022 security update. * Microsoft Exchange Server 2013 Cumulative Update 23 prior to January 2022 security update. ## Fixed By KB5008631 ## Other vulns fixed in same patch CVE-2022-21846 <– NSA reported this one. CVE-2022-21855 <– Reported by Andrew Ruddick of MSRC. # Writeup Review Original writeup: <https://www.instapaper.com/read/1487196325> We have well known _sinks_ in [[.NET]] whereby one can make deserialization calls from unprotected formatters such as `BinaryFormatter`. These formatters as noted in [[CVE-2021-42321]] don’t have any `SerializationBinder` or similar binders attached to them, which means that they are open to deserialize whatever they like, without any binder limiting them to what they can deserialize. Initial search for vulnerabilities took place around Exchange’s `Rpc` functions, which use a binary protocol created by Microsoft for communication instead of using normal HTTP requests. Looking around we can see `Microsoft.Exchange.Rpc.ExchangeCertificates.ExchangeCertificateRpcServer` contains several function prototypes: // Microsoft.Exchange.Rpc.ExchangeCertificate.ExchangeCertificateRpcServer using System; using System.Security; using Microsoft.Exchange.Rpc; internal abstract class ExchangeCertificateRpcServer : RpcServerBase {     public unsafe static IntPtr RpcIntfHandle = (IntPtr)<Module>.IExchangeCertificate_v1_0_s_ifspec;     public abstract byte[] GetCertificate(int version, byte[] pInBytes);     public abstract byte[] CreateCertificate(int version, byte[] pInBytes);     public abstract byte[] RemoveCertificate(int version, byte[] pInBytes);     public abstract byte[] ExportCertificate(int version, byte[] pInBytes, SecureString password);     public abstract byte[] ImportCertificate(int version, byte[] pInBytes, SecureString password);     public abstract byte[] EnableCertificate(int version, byte[] pInBytes);     public ExchangeCertificateRpcServer()     {     } } These are then implemented in `Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer`. // Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer using System; using System.Security; using System.Security.AccessControl; using System.Security.Principal; using Microsoft.Exchange.Management.SystemConfigurationTasks; using Microsoft.Exchange.Rpc; using Microsoft.Exchange.Rpc.ExchangeCertificate; using Microsoft.Exchange.Servicelets.ExchangeCertificate; internal class ExchangeCertificateServer : ExchangeCertificateRpcServer {     internal const string RequestStoreName = "REQUEST";     private static ExchangeCertificateServer server;     public static bool Start(out Exception e)     {         e = null;         SecurityIdentifier securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);         FileSystemAccessRule accessRule = new FileSystemAccessRule(securityIdentifier, FileSystemRights.Read, AccessControlType.Allow);         FileSecurity fileSecurity = new FileSecurity();         fileSecurity.SetOwner(securityIdentifier);         fileSecurity.SetAccessRule(accessRule);         try         {             server = (ExchangeCertificateServer)RpcServerBase.RegisterServer(typeof(ExchangeCertificateServer), fileSecurity, 1u, isLocalOnly: false);             return true;         }         catch (RpcException ex)         {             RpcException ex2 = (RpcException)(e = ex);             return false;         }     }     public static void Stop()     {         if (server != null)         {             RpcServerBase.StopServer(ExchangeCertificateRpcServer.RpcIntfHandle);             server = null;         }     }     public override byte[] CreateCertificate(int version, byte[] inputBlob)     {         return ExchangeCertificateServerHelper.CreateCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);     }     public override byte[] GetCertificate(int version, byte[] inputBlob)     {         return ExchangeCertificateServerHelper.GetCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);     }     public override byte[] RemoveCertificate(int version, byte[] inputBlob)     {         return ExchangeCertificateServerHelper.RemoveCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);     }     public override byte[] ExportCertificate(int version, byte[] inputBlob, SecureString password)     {         return ExchangeCertificateServerHelper.ExportCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob, password);     }     public override byte[] ImportCertificate(int version, byte[] inputBlob, SecureString password)     {         return ExchangeCertificateServerHelper.ImportCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob, password);     }     public override byte[] EnableCertificate(int version, byte[] inputBlob)     {         return ExchangeCertificateServerHelper.EnableCertificate(ExchangeCertificateRpcVersion.Version1, inputBlob);     } } Examining these functions we can see a lot of them take a byte array input named `byte[] inputBlob`. If we follow the `ImportCertificate()` function here as an example we can see that the implementation will call into `Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper`, as is also true for the other functions. // Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper using System; using System.Collections.Generic; using System.Management.Automation; using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Exchange.Data; using Microsoft.Exchange.Data.Common; using Microsoft.Exchange.Data.Directory; using Microsoft.Exchange.Data.Directory.Management; using Microsoft.Exchange.Data.Directory.SystemConfiguration; using Microsoft.Exchange.Extensions; using Microsoft.Exchange.Management.FederationProvisioning; using Microsoft.Exchange.Management.Metabase; using Microsoft.Exchange.Management.SystemConfigurationTasks; using Microsoft.Exchange.Management.Tasks; using Microsoft.Exchange.Net; using Microsoft.Exchange.Security.Cryptography.X509Certificates; using Microsoft.Exchange.Servicelets.ExchangeCertificate; internal class ExchangeCertificateServerHelper { ...     public static byte[] ImportCertificate(ExchangeCertificateRpcVersion rpcVersion, byte[] inputBlob, SecureString password)     {         bool flag = false;         ExchangeCertificateRpc exchangeCertificateRpc = new ExchangeCertificateRpc(rpcVersion, inputBlob, null);         if (string.IsNullOrEmpty(exchangeCertificateRpc.ImportCert))         {             return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);         }         Server server = null;         ITopologyConfigurationSession topologyConfigurationSession = DirectorySessionFactory.Default.CreateTopologyConfigurationSession(ConsistencyMode.IgnoreInvalid, ADSessionSettings.FromRootOrgScopeSet(), 1159, "ImportCertificate", "d:\\dbs\\sh\\e19dt\\1103_100001\\cmd\\c\\sources\\Dev\\Management\\src\\ServiceHost\\Servicelets\\ExchangeCertificate\\Program\\ExchangeCertificateServer.cs");         try         {             server = ManageExchangeCertificate.FindLocalServer(topologyConfigurationSession);         }         catch (LocalServerNotFoundException)         {             flag = true;         }         if (flag || !ManageExchangeCertificate.IsServerRoleSupported(server))         {             return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.RoleDoesNotSupportExchangeCertificateTasksException, ErrorCategory.InvalidOperation);         }         X509Store x509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);         try         {             x509Store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);         }         catch (CryptographicException)         {             x509Store = null;         }         List<ServiceData> installed = new List<ServiceData>();         GetInstalledRoles(topologyConfigurationSession, server, installed);         try         {             byte[] array = null;             if (CertificateEnroller.TryAcceptPkcs7(exchangeCertificateRpc.ImportCert, out var thumbprint, out var untrustedRoot))             {                 X509Certificate2Collection x509Certificate2Collection = x509Store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false);                 if (x509Certificate2Collection.Count > 0)                 {                     if (!string.IsNullOrEmpty(exchangeCertificateRpc.ImportDescription))                     {                         x509Certificate2Collection[0].FriendlyName = exchangeCertificateRpc.ImportDescription;                     }                     ExchangeCertificate exchangeCertificate = new ExchangeCertificate(x509Certificate2Collection[0]);                     UpdateServices(exchangeCertificate, server, installed);                     exchangeCertificateRpc.ReturnCert = exchangeCertificate;                 }                 return exchangeCertificateRpc.SerializeOutputParameters(rpcVersion);             }             if (untrustedRoot)             {                 return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateUntrustedRoot, ErrorCategory.ReadError);             }             try             {                 array = Convert.FromBase64String(exchangeCertificateRpc.ImportCert);             }             catch (FormatException)             {                 return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateBase64DataInvalid, ErrorCategory.ReadError);             }             X509Certificate2 x509Certificate = null;             try             {                 X509KeyStorageFlags x509KeyStorageFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet;                 bool flag2 = password == null || password.Length == 0;                 X509Certificate2Collection x509Certificate2Collection2 = new X509Certificate2Collection();                 if (exchangeCertificateRpc.ImportExportable)                 {                     x509KeyStorageFlags |= X509KeyStorageFlags.Exportable;                 }                 x509Certificate2Collection2.Import(array, flag2 ? null : password.AsUnsecureString(), x509KeyStorageFlags);                 x509Certificate = ManageExchangeCertificate.FindImportedCertificate(x509Certificate2Collection2);             }             catch (CryptographicException)             {                 return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);             }             if (x509Certificate == null)             {                 return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateDataInvalid, ErrorCategory.ReadError);             }             if (!string.IsNullOrEmpty(exchangeCertificateRpc.ImportDescription))             {                 x509Certificate.FriendlyName = exchangeCertificateRpc.ImportDescription;             }             if (x509Store.Certificates.Find(X509FindType.FindByThumbprint, x509Certificate.Thumbprint, validOnly: false).Count > 0)             {                 return ExchangeCertificateRpc.SerializeError(rpcVersion, Strings.ImportCertificateAlreadyExists(x509Certificate.Thumbprint), ErrorCategory.WriteError);             }             x509Store.Add(x509Certificate);             ExchangeCertificate exchangeCertificate2 = new ExchangeCertificate(x509Certificate);             UpdateServices(exchangeCertificate2, server, installed);             exchangeCertificateRpc.ReturnCert = exchangeCertificate2;         }         finally         {             x509Store?.Close();         }         return exchangeCertificateRpc.SerializeOutputParameters(rpcVersion);     } ... We can see from this that most functions appear to be calling `Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.ExchangeCertificateRpc()`. This has some interesting code relevant to deserialization: // Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc using System.Collections.Generic; using Microsoft.Exchange.Rpc.ExchangeCertificate; public ExchangeCertificateRpc(ExchangeCertificateRpcVersion version, byte[] inputBlob, byte[] outputBlob) {     inputParameters = new Dictionary<RpcParameters, object>();     if (inputBlob != null)     {         switch (version)         {         case ExchangeCertificateRpcVersion.Version1:             inputParameters = (Dictionary<RpcParameters, object>)DeserializeObject(inputBlob, customized: false);             break;         case ExchangeCertificateRpcVersion.Version2:             inputParameters = BuildInputParameters(inputBlob);             break;         }     }     outputParameters = new Dictionary<RpcOutput, object>();     if (outputBlob != null)     {         switch (version)         {         case ExchangeCertificateRpcVersion.Version1:             outputParameters = (Dictionary<RpcOutput, object>)DeserializeObject(outputBlob, customized: false);             break;         case ExchangeCertificateRpcVersion.Version2:             outputParameters = BuildOutputParameters(outputBlob);             break;         }     } } Here we can see that the `byte[] inputBlob` from earlier is passed to `DeserializeObject(inputBlob, customized: false)` in the case that `ExchangeCertificateRpcVersion` parameter passed in is `ExchangeCertificateRpcVersion.Version1`. Okay so already we know we have one limitation in that we need to set the `version` parameter here to `ExchangeCertificateRpcVersion.Version1` somehow. Keeping this in mind lets explore further and look at the `Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject(inputBlob, customized:false)` call implementation. // Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc using System.IO; using Microsoft.Exchange.Data.Serialization; using Microsoft.Exchange.Diagnostics; private object DeserializeObject(byte[] data, bool customized) {     if (data != null)     {         using (MemoryStream serializationStream = new MemoryStream(data))         {             bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);             return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics).Deserialize(serializationStream);         }     }     return null; } Interesting so we can see that we create a new `MemoryStream` object from our `byte[] data` parameter and use this to create a serialization stream of type `MemoryStream`. We then check using `Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus` to see if `DeserializeLocation.ExchangeCertificateRpc` requires strict mode for deserialization or not and we set the boolean `strictModeStatus` to this result. Finally we create a binary formatter using `ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics)` and then call its `Deserialize()` method on the serialized `MemoryStream` object we created earlier using `byte[] data`. Note that before the November 2021 patch, this `DeserializeObject` function actually looked like this: // Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc using System.IO; using Microsoft.Exchange.Data.Serialization; using Microsoft.Exchange.Diagnostics; private object DeserializeObject(byte[] data, bool customized) {     if (data != null)     {         using (MemoryStream serializationStream = new MemoryStream(data))         {             BinaryFormatter binaryFormatter = new BinaryFormatter(); if (customized) { binaryFormatter.Binder = new CustomizedSerializationBinder(); } return binaryFormatter.Deserialize(memoryStream);         }     }     return null; } As we can see the earlier code here was using `BinaryFormatter` to deserialize the payload without using a proper `SerializationBinder` or really any protection at all for that matter. ## Looking At DeserializeObject() Deeper Lets look at the November 2022 edition of `Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject(inputBlob, customized:false)` again: // Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc using System.IO; using Microsoft.Exchange.Data.Serialization; using Microsoft.Exchange.Diagnostics; private object DeserializeObject(byte[] data, bool customized) {     if (data != null)     {         using (MemoryStream serializationStream = new MemoryStream(data))         {             bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);             return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.ExchangeCertificateRpc, strictModeStatus, allowedTypes, allowedGenerics).Deserialize(serializationStream);         }     }     return null; } What we want to check here now is the `ExchangeBinaryFormatterFactor.CreateBinaryFormatter` call. What does the code for this look like? // Microsoft.Exchange.Diagnostics.ExchangeBinaryFormatterFactory using System.Runtime.Serialization.Formatters.Binary; public static BinaryFormatter CreateBinaryFormatter(DeserializeLocation usageLocation, bool strictMode = false, string[] allowList = null, string[] allowedGenerics = null) {     return new BinaryFormatter     {         Binder = new ChainedSerializationBinder(usageLocation, strictMode, allowList, allowedGenerics)     }; } Ah our good old friend `ChainedSerializationBinder` and `BinaryFormatter`. Looks like we will need to create a `BinaryFormatter` serialized payload and `ChainedSerializationBinder` will be the validator. As mentioned in the article to bypass this logic we need to ensure that `strictMode` is set to `False` and that we are not using any fully qualified assembly name in the deny list defined in `Microsoft.Exchange.Diagnostics.ChainedSerializationBinder.GlobalDisallowedTypesForDeserialization`, which will pretty much kill all publicly known .NET deserialization gadgets from ysoserial.NET. For reference this is the code for `ChainedSerializationBinder` in November 2021 Update: // Microsoft.Exchange.Diagnostics.ChainedSerializationBinder using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Microsoft.Exchange.Diagnostics; public class ChainedSerializationBinder : SerializationBinder {     private const string TypeFormat = "{0}, {1}";     private static readonly HashSet<string> AlwaysAllowedPrimitives = new HashSet<string>     {         typeof(string).FullName,         typeof(int).FullName,         typeof(uint).FullName,         typeof(long).FullName,         typeof(ulong).FullName,         typeof(double).FullName,         typeof(float).FullName,         typeof(bool).FullName,         typeof(short).FullName,         typeof(ushort).FullName,         typeof(byte).FullName,         typeof(char).FullName,         typeof(DateTime).FullName,         typeof(TimeSpan).FullName,         typeof(Guid).FullName     };     private bool strictMode;     private DeserializeLocation location;     private Func<string, Type> typeResolver;     private HashSet<string> allowedTypesForDeserialization;     private HashSet<string> allowedGenericsForDeserialization;     private bool serializationOnly;     protected static HashSet<string> GlobalDisallowedTypesForDeserialization { get; private set; } = BuildDisallowedTypesForDeserialization();     protected static HashSet<string> GlobalDisallowedGenericsForDeserialization { get; private set; } = BuildGlobalDisallowedGenericsForDeserialization();     public ChainedSerializationBinder()     {         serializationOnly = true;     }     public ChainedSerializationBinder(DeserializeLocation usageLocation, bool strictMode = false, string[] allowList = null, string[] allowedGenerics = null)     {         this.strictMode = strictMode;         allowedTypesForDeserialization = ((allowList != null && allowList.Length != 0) ? new HashSet<string>(allowList) : null);         allowedGenericsForDeserialization = ((allowedGenerics != null && allowedGenerics.Length != 0) ? new HashSet<string>(allowedGenerics) : null);         typeResolver = typeResolver ?? ((Func<string, Type>)((string s) => Type.GetType(s)));         location = usageLocation;     }     public override void BindToName(Type serializedType, out string assemblyName, out string typeName)     {         string text = null;         string text2 = null;         InternalBindToName(serializedType, out assemblyName, out typeName);         if (assemblyName == null && typeName == null)         {             assemblyName = text;             typeName = text2;         }     }     public override Type BindToType(string assemblyName, string typeName)     {         if (serializationOnly)         {             throw new InvalidOperationException("ChainedSerializationBinder was created for serialization only.  This instance cannot be used for deserialization.");         }         Type type = InternalBindToType(assemblyName, typeName);         if (type != null)         {             ValidateTypeToDeserialize(type);         }         return type;     }     protected virtual Type InternalBindToType(string assemblyName, string typeName)     {         return LoadType(assemblyName, typeName);     }     protected Type LoadType(string assemblyName, string typeName)     {         Type type = null;         try         {             type = Type.GetType($"{typeName}, {assemblyName}");         }         catch (TypeLoadException)         {         }         catch (FileLoadException)         {         }         if (type == null)         {             string shortName = assemblyName.Split(',')[0];             try             {                 type = Type.GetType($"{typeName}, {shortName}");             }             catch (TypeLoadException)             {             }             catch (FileLoadException)             {             }             if (type == null)             {                 Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();                 IEnumerable<Assembly> source = assemblies.Where((Assembly x) => shortName == x.FullName.Split(',')[0]);                 Assembly assembly = (source.Any() ? source.First() : null);                 if (assembly != null)                 {                     type = assembly.GetType(typeName);                 }                 else                 {                     Assembly[] array = assemblies;                     foreach (Assembly assembly2 in array)                     {                         try                         {                             type = assembly2.GetType(typeName);                             if (!(type != null))                             {                                 continue;                             }                             return type;                         }                         catch                         {                         }                     }                 }             }         }         return type;     }     protected virtual void InternalBindToName(Type serializedType, out string assemblyName, out string typeName)     {         assemblyName = null;         typeName = null;     }     protected void ValidateTypeToDeserialize(Type typeToDeserialize)     {         if (typeToDeserialize == null)         {             return;         }         string fullName = typeToDeserialize.FullName;         bool flag = strictMode;         try         {             if (!strictMode && (allowedTypesForDeserialization == null || !allowedTypesForDeserialization.Contains(fullName)) && GlobalDisallowedTypesForDeserialization.Contains(fullName))             {                 flag = true;                 throw new InvalidOperationException($"Type {fullName} failed deserialization (BlockList).");             }             if (typeToDeserialize.IsConstructedGenericType)             {                 fullName = typeToDeserialize.GetGenericTypeDefinition().FullName;                 if (allowedGenericsForDeserialization == null || !allowedGenericsForDeserialization.Contains(fullName) || GlobalDisallowedGenericsForDeserialization.Contains(fullName))                 {                     throw new BlockedDeserializeTypeException(fullName, BlockedDeserializeTypeException.BlockReason.NotInAllow, location);                 }             }             else if (!AlwaysAllowedPrimitives.Contains(fullName) && (allowedTypesForDeserialization == null || !allowedTypesForDeserialization.Contains(fullName) || GlobalDisallowedTypesForDeserialization.Contains(fullName)) && !typeToDeserialize.IsArray && !typeToDeserialize.IsEnum && !typeToDeserialize.IsAbstract && !typeToDeserialize.IsInterface)             {                 throw new BlockedDeserializeTypeException(fullName, BlockedDeserializeTypeException.BlockReason.NotInAllow, location);             }         }         catch (BlockedDeserializeTypeException ex)         {             DeserializationTypeLogger.Singleton.Log(ex.TypeName, ex.Reason, location, (flag || strictMode) ? DeserializationTypeLogger.BlockStatus.TrulyBlocked : DeserializationTypeLogger.BlockStatus.WouldBeBlocked);             if (flag)             {                 throw;             }         }     }     private static HashSet<string> BuildDisallowedGenerics()     {         return new HashSet<string> { typeof(SortedSet<>).GetGenericTypeDefinition().FullName };     }     private static HashSet<string> BuildDisallowedTypesForDeserialization()     {         return new HashSet<string>         {             "Microsoft.Data.Schema.SchemaModel.ModelStore", "Microsoft.FailoverClusters.NotificationViewer.ConfigStore", "Microsoft.IdentityModel.Claims.WindowsClaimsIdentity", "Microsoft.Management.UI.Internal.FilterRuleExtensions", "Microsoft.Management.UI.FilterRuleExtensions", "Microsoft.Reporting.RdlCompile.ReadStateFile", "Microsoft.TeamFoundation.VersionControl.Client.PolicyEnvelope", "Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource", "Microsoft.VisualStudio.Editors.PropPageDesigner.PropertyPageSerializationService+PropertyPageSerializationStore", "Microsoft.VisualStudio.EnterpriseTools.Shell.ModelingPackage",             "Microsoft.VisualStudio.Modeling.Diagnostics.XmlSerialization", "Microsoft.VisualStudio.Publish.BaseProvider.Util", "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", "Microsoft.VisualStudio.Web.WebForms.ControlDesignerStateCache", "Microsoft.Web.Design.Remote.ProxyObject", "System.Activities.Presentation.WorkflowDesigner", "System.AddIn.Hosting.AddInStore", "System.AddIn.Hosting.Utils", "System.CodeDom.Compiler.TempFileCollection", "System.Collections.Hashtable",             "System.ComponentModel.Design.DesigntimeLicenseContextSerializer", "System.Configuration.Install.AssemblyInstaller", "System.Configuration.SettingsPropertyValue", "System.Data.DataSet", "System.Data.DataViewManager", "System.Data.Design.MethodSignatureGenerator", "System.Data.Design.TypedDataSetGenerator", "System.Data.Design.TypedDataSetSchemaImporterExtension", "System.Data.SerializationFormat", "System.DelegateSerializationHolder",             "System.Drawing.Design.ToolboxItemContainer", "System.Drawing.Design.ToolboxItemContainer+ToolboxItemSerializer", "System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler", "System.IdentityModel.Tokens.SessionSecurityToken", "System.IdentityModel.Tokens.SessionSecurityTokenHandler", "System.IO.FileSystemInfo", "System.Management.Automation.PSObject", "System.Management.IWbemClassObjectFreeThreaded", "System.Messaging.BinaryMessageFormatter", "System.Resources.ResourceReader",             "System.Resources.ResXResourceSet", "System.Runtime.Remoting.Channels.BinaryClientFormatterSink", "System.Runtime.Remoting.Channels.BinaryClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.BinaryServerFormatterSink", "System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider", "System.Runtime.Remoting.Channels.CrossAppDomainSerializer", "System.Runtime.Remoting.Channels.SoapClientFormatterSink", "System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.SoapServerFormatterSink", "System.Runtime.Remoting.Channels.SoapServerFormatterSinkProvider",             "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", "System.Runtime.Serialization.Formatters.Soap.SoapFormatter", "System.Runtime.Serialization.NetDataContractSerializer", "System.Security.Claims.ClaimsIdentity", "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", "System.Security.Principal.WindowsPrincipal", "System.Security.SecurityException", "System.Web.Security.RolePrincipal", "System.Web.Script.Serialization.JavaScriptSerializer",             "System.Web.Script.Serialization.SimpleTypeResolver", "System.Web.UI.LosFormatter", "System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem", "System.Web.UI.ObjectStateFormatter", "System.Windows.Data.ObjectDataProvider", "System.Windows.Forms.AxHost+State", "System.Windows.ResourceDictionary", "System.Workflow.ComponentModel.Activity", "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", "System.Xml.XmlDataDocument",             "System.Xml.XmlDocument"         };     }     private static HashSet<string> BuildGlobalDisallowedGenericsForDeserialization()     {         return new HashSet<string>();     } } **Interesting to note that this doesn’t seem to contain the entries for `System.Runtime.Remoting.ObjectRef`** which was a new gadget chain just added with <https://github.com/pwntester/ysoserial.net/pull/115> that relies on a rouge .NET remoting server like <https://github.com/codewhitesec/RogueRemotingServer>. There is a writeup on this at <https://codewhitesec.blogspot.com/2022/01/dotnet-remoting-revisited.html> that explains more but this would allow RCE via a serialized payload attached to the rouge .NET remoting server. Anyway so from earlier we know that the strict mode is determined via the line `bool strictModeStatus = Microsoft.Exchange.Data.Serialization.Serialization.GetStrictModeStatus(DeserializeLocation.ExchangeCertificateRpc);` so this provides our other bypass. Lets check if the result of this is `False` or not: So from here we can likely supply a `System.Runtime.Remoting.ObjectRef`, take advantage of the lack of strict checking on this, and get the whole exploit to work. The problem now is finding the whole chain to reach this vulnerable call and then trigger the deserialization. # January 2022 Patch Analysis * No adjustments to the `ChainedSerializationBinder` deny list at all. Here is the Jan 2022 version of the deny list:     private static HashSet<string> BuildDisallowedTypesForDeserialization()     {         return new HashSet<string>         {             "Microsoft.Data.Schema.SchemaModel.ModelStore", "Microsoft.FailoverClusters.NotificationViewer.ConfigStore", "Microsoft.IdentityModel.Claims.WindowsClaimsIdentity", "Microsoft.Management.UI.Internal.FilterRuleExtensions", "Microsoft.Management.UI.FilterRuleExtensions", "Microsoft.Reporting.RdlCompile.ReadStateFile", "Microsoft.TeamFoundation.VersionControl.Client.PolicyEnvelope", "Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource", "Microsoft.VisualStudio.Editors.PropPageDesigner.PropertyPageSerializationService+PropertyPageSerializationStore", "Microsoft.VisualStudio.EnterpriseTools.Shell.ModelingPackage",             "Microsoft.VisualStudio.Modeling.Diagnostics.XmlSerialization", "Microsoft.VisualStudio.Publish.BaseProvider.Util", "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", "Microsoft.VisualStudio.Web.WebForms.ControlDesignerStateCache", "Microsoft.Web.Design.Remote.ProxyObject", "System.Activities.Presentation.WorkflowDesigner", "System.AddIn.Hosting.AddInStore", "System.AddIn.Hosting.Utils", "System.CodeDom.Compiler.TempFileCollection", "System.Collections.Hashtable",             "System.ComponentModel.Design.DesigntimeLicenseContextSerializer", "System.Configuration.Install.AssemblyInstaller", "System.Configuration.SettingsPropertyValue", "System.Data.DataSet", "System.Data.DataViewManager", "System.Data.Design.MethodSignatureGenerator", "System.Data.Design.TypedDataSetGenerator", "System.Data.Design.TypedDataSetSchemaImporterExtension", "System.Data.SerializationFormat", "System.DelegateSerializationHolder",             "System.Drawing.Design.ToolboxItemContainer", "System.Drawing.Design.ToolboxItemContainer+ToolboxItemSerializer", "System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler", "System.IdentityModel.Tokens.SessionSecurityToken", "System.IdentityModel.Tokens.SessionSecurityTokenHandler", "System.IO.FileSystemInfo", "System.Management.Automation.PSObject", "System.Management.IWbemClassObjectFreeThreaded", "System.Messaging.BinaryMessageFormatter", "System.Resources.ResourceReader",             "System.Resources.ResXResourceSet", "System.Runtime.Remoting.Channels.BinaryClientFormatterSink", "System.Runtime.Remoting.Channels.BinaryClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.BinaryServerFormatterSink", "System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider", "System.Runtime.Remoting.Channels.CrossAppDomainSerializer", "System.Runtime.Remoting.Channels.SoapClientFormatterSink", "System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider", "System.Runtime.Remoting.Channels.SoapServerFormatterSink", "System.Runtime.Remoting.Channels.SoapServerFormatterSinkProvider",             "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", "System.Runtime.Serialization.Formatters.Soap.SoapFormatter", "System.Runtime.Serialization.NetDataContractSerializer", "System.Security.Claims.ClaimsIdentity", "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", "System.Security.Principal.WindowsPrincipal", "System.Security.SecurityException", "System.Web.Security.RolePrincipal", "System.Web.Script.Serialization.JavaScriptSerializer",             "System.Web.Script.Serialization.SimpleTypeResolver", "System.Web.UI.LosFormatter", "System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem", "System.Web.UI.ObjectStateFormatter", "System.Windows.Data.ObjectDataProvider", "System.Windows.Forms.AxHost+State", "System.Windows.ResourceDictionary", "System.Workflow.ComponentModel.Activity", "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", "System.Xml.XmlDataDocument",             "System.Xml.XmlDocument"         };     } Looking at this in [[Meld]] shows that the deny list for `ChainedSerializationBinder` did not change between November 2021 and January 2022. So we could use `System.Runtime.Remoting.ObjRef` to bypass this deny list, potentially also allowing RCE on the latest version. * Removed `Microsoft.Exchange.DxStore.Common.DxBinarySerializationUtil` which seemed to have some options for doing unsafe deserialization. using System; using System.IO; using FUSE.Weld.Base; using Microsoft.Exchange.Diagnostics; using Microsoft.Exchange.DxStore.Server; namespace Microsoft.Exchange.DxStore.Common; public static class DxBinarySerializationUtil { private static readonly string[] allowedTypes = new string[101] { typeof(ExceptionUri).FullName, typeof(Ranges).FullName, typeof(Range).FullName, typeof(Target).FullName, typeof(CommonSettings).FullName, typeof(DataStoreStats).FullName, typeof(DxStoreAccessClientException).FullName, typeof(DxStoreAccessClientTransientException).FullName, typeof(DxStoreAccessReply).FullName, typeof(DxStoreAccessReply.CheckKey).FullName, typeof(DxStoreAccessReply.DeleteKey).FullName, typeof(DxStoreAccessReply.DeleteProperty).FullName, typeof(DxStoreAccessReply.ExecuteBatch).FullName, typeof(DxStoreAccessReply.GetAllProperties).FullName, typeof(DxStoreAccessReply.GetProperty).FullName, typeof(DxStoreAccessReply.GetPropertyNames).FullName, typeof(DxStoreAccessReply.GetSubkeyNames).FullName, typeof(DxStoreAccessReply.SetProperty).FullName, typeof(DxStoreAccessRequest).FullName, typeof(DxStoreAccessRequest.CheckKey).FullName, typeof(DxStoreAccessRequest.DeleteKey).FullName, typeof(DxStoreAccessRequest.DeleteProperty).FullName, typeof(DxStoreAccessRequest.ExecuteBatch).FullName, typeof(DxStoreAccessRequest.GetAllProperties).FullName, typeof(DxStoreAccessRequest.GetProperty).FullName, typeof(DxStoreAccessRequest.GetPropertyNames).FullName, typeof(DxStoreAccessRequest.GetSubkeyNames).FullName, typeof(DxStoreAccessRequest.SetProperty).FullName, typeof(DxStoreAccessServerTransientException).FullName, typeof(DxStoreBatchCommand).FullName, typeof(DxStoreBatchCommand.CreateKey).FullName, typeof(DxStoreBatchCommand.DeleteKey).FullName, typeof(DxStoreBatchCommand.DeleteProperty).FullName, typeof(DxStoreBatchCommand.SetProperty).FullName, typeof(DxStoreBindingNotSupportedException).FullName, typeof(DxStoreClientException).FullName, typeof(DxStoreClientTransientException).FullName, typeof(DxStoreCommand).FullName, typeof(DxStoreCommand.ApplySnapshot).FullName, typeof(DxStoreCommand.CreateKey).FullName, typeof(DxStoreCommand.DeleteKey).FullName, typeof(DxStoreCommand.DeleteProperty).FullName, typeof(DxStoreCommand.DummyCommand).FullName, typeof(DxStoreCommand.ExecuteBatch).FullName, typeof(DxStoreCommand.PromoteToLeader).FullName, typeof(DxStoreCommand.SetProperty).FullName, typeof(DxStoreCommand.UpdateMembership).FullName, typeof(DxStoreCommand.VerifyStoreIntegrity).FullName, typeof(DxStoreCommand.VerifyStoreIntegrity2).FullName, typeof(DxStoreCommandConstraintFailedException).FullName, typeof(DxStoreInstanceClientException).FullName, typeof(DxStoreInstanceClientTransientException).FullName, typeof(DxStoreInstanceComponentNotInitializedException).FullName, typeof(DxStoreInstanceKeyNotFoundException).FullName, typeof(DxStoreInstanceNotReadyException).FullName, typeof(DxStoreInstanceServerException).FullName, typeof(DxStoreInstanceServerTransientException).FullName, typeof(DxStoreInstanceStaleStoreException).FullName, typeof(DxStoreManagerClientException).FullName, typeof(DxStoreManagerClientTransientException).FullName, typeof(DxStoreManagerGroupNotFoundException).FullName, typeof(DxStoreManagerServerException).FullName, typeof(DxStoreManagerServerTransientException).FullName, typeof(DxStoreReplyBase).FullName, typeof(DxStoreRequestBase).FullName, typeof(DxStoreSerializeException).FullName, typeof(DxStoreServerException).FullName, typeof(DxStoreServerFault).FullName, typeof(DxStoreServerTransientException).FullName, typeof(HttpReply).FullName, typeof(HttpReply.DxStoreReply).FullName, typeof(HttpReply.ExceptionReply).FullName, typeof(HttpReply.GetInstanceStatusReply).FullName, typeof(HttpRequest).FullName, typeof(HttpRequest.DxStoreRequest).FullName, typeof(HttpRequest.GetStatusRequest).FullName, typeof(HttpRequest.GetStatusRequest.Reply).FullName, typeof(HttpRequest.PaxosMessage).FullName, typeof(InstanceGroupConfig).FullName, typeof(InstanceGroupMemberConfig).FullName, typeof(InstanceGroupSettings).FullName, typeof(InstanceManagerConfig).FullName, typeof(InstanceSnapshotInfo).FullName, typeof(InstanceStatusInfo).FullName, typeof(LocDescriptionAttribute).FullName, typeof(PaxosBasicInfo).FullName, typeof(PaxosBasicInfo.GossipDictionary).FullName, typeof(ProcessBasicInfo).FullName, typeof(PropertyNameInfo).FullName, typeof(PropertyValue).FullName, typeof(ReadOptions).FullName, typeof(ReadResult).FullName, typeof(WcfTimeout).FullName, typeof(WriteOptions).FullName, typeof(WriteResult).FullName, typeof(WriteResult.ResponseInfo).FullName, typeof(GroupStatusInfo).FullName, typeof(GroupStatusInfo.NodeInstancePair).FullName, typeof(InstanceMigrationInfo).FullName, typeof(KeyContainer).FullName, typeof(DateTimeOffset).FullName }; private static readonly string[] allowedGenerics = new string[6] { "System.Collections.Generic.ObjectEqualityComparer`1", "System.Collections.Generic.EnumEqualityComparer`1", "System.Collections.Generic.EqualityComparer`1", "System.Collections.Generic.GenericEqualityComparer`1", "System.Collections.Generic.KeyValuePair`2", "System.Collections.Generic.List`1" }; public static void Serialize(MemoryStream ms, object obj) { ExchangeBinaryFormatterFactory.CreateSerializeOnlyFormatter().Serialize(ms, obj); } public static object DeserializeUnsafe(Stream s) { return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.HttpBinarySerialize).Deserialize(s); } public static object Deserialize(Stream s) { return DeserializeSafe(s); } public static object DeserializeSafe(Stream s) { return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.SwordFish_AirSync, strictMode: false, allowedTypes, allowedGenerics).Deserialize(s); } } * Added in `Microsoft.Exchange.DxStore.Common.IDxStoreDynamicConfig.cs` which has the following code: namespace Microsoft.Exchange.DxStore.Common; public interface IDxStoreDynamicConfig { bool IsRemovePublicKeyToken { get; } bool IsSerializerIncompatibleInitRemoved { get; } bool EnableResolverTypeCheck { get; } bool EnableResolverTypeCheckException { get; } } # Exploit Chain Lets start at the deserialization chain and work backwards. Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.DeserializeObject Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificateRpc.ExchangeCertificateRpc(ExchangeCertificateRpcVersion version, byte[] inputBlob, byte[] outputBlob) Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServerHelper.GetCertificate(int version, byte[] inputBlob) Microsoft.Exchange.Servicelets.ExchangeCertificate.ExchangeCertificateServer.GetCertificate(int version, byte[] inputBlob) We can then use the `Get-ExchangeCertificate` commandlet from <https://docs.microsoft.com/en-us/powershell/module/exchange/get-exchangecertificate?view=exchange-ps> and set a breakpoint inside `Microsoft.Exchange.ExchangeCertificateServicelet.dll` specifically within the `Microsoft.Exchange.Servicelets.ExchangeCertificate.GetCertificate` handler. Unfortunately it seems like the current way things work we are sending a `ExchangeCertificateRpcVersion rpcVersion` with a version of `Version2`. Exploited process is `Microsoft.Exchange.ServiceHost.exe` which runs as `NT AUTHORITY\SYSTEM`. Assessed Attacker Value: 3 Assessed Attacker Value: 3Assessed Attacker Value: 3


Related