Very High
CVE-2021-26857
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
CVE-2021-26857
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
Microsoft Exchange Server Remote Code Execution Vulnerability
Add Assessment
Ratings
-
Attacker ValueVery High
-
ExploitabilityMedium
Technical Analysis
As per Microsoft’s blog post on Exchange Server 0day use by the HAFNIUM actors, CVE-2021-26857 is a deserialization vulnerability in Exchange Server’s Unified Messaging (voicemail) service. Exploiting the vulnerability reportedly requires admin access or chaining with another vuln (likely CVE-2021-26855), but successful exploitation results in RCE as the SYSTEM
account. This vulnerability would ideally be combined with an auth bypass, which CVE-2021-26855 may very well provide.
I took a look at CVE-2021-26857 last night and came up with the following patch diff:
--- exchange.unpatched/Microsoft.Exchange.UM.UMCore/UMCore/PipelineContext.cs 2021-03-02 19:54:18.000000000 -0600 +++ exchange.patched/Microsoft.Exchange.UM.UMCore/UMCore/PipelineContext.cs 2021-03-02 19:55:19.000000000 -0600 @@ -1,742 +1,886 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Runtime.Serialization; +using Microsoft.Exchange.Compliance.Serialization.Formatters; +using Microsoft.Exchange.Data; +using Microsoft.Exchange.Data.Common; using Microsoft.Exchange.Data.Directory; using Microsoft.Exchange.Data.Directory.Recipient; using Microsoft.Exchange.Data.Directory.SystemConfiguration; using Microsoft.Exchange.Data.Storage; using Microsoft.Exchange.Diagnostics; using Microsoft.Exchange.Diagnostics.Components.UnifiedMessaging; using Microsoft.Exchange.ExchangeSystem; using Microsoft.Exchange.TextProcessing.Boomerang; using Microsoft.Exchange.UM.UMCommon; +using Microsoft.Mapi; namespace Microsoft.Exchange.UM.UMCore { internal abstract class PipelineContext : DisposableBase, IUMCreateMessage { internal PipelineContext() { } internal PipelineContext(SubmissionHelper helper) { bool flag = false; try { this.helper = helper; this.cultureInfo = new CultureInfo(helper.CultureInfo); flag = true; } finally { if (!flag) { this.Dispose(); } } } public MessageItem MessageToSubmit { get { return this.messageToSubmit; } protected set { this.messageToSubmit = value; } } public string MessageID { get { return this.messageID; } protected set { this.messageID = value; } } internal abstract Pipeline Pipeline { get; } internal Microsoft.Exchange.UM.UMCommon.PhoneNumber CallerId { get { return this.helper.CallerId; } } internal Guid TenantGuid { get { return this.helper.TenantGuid; } } internal int ProcessedCount { get { return this.processedCount; } } internal ExDateTime SentTime { get { return this.sentTime; } set { this.sentTime = value; } } internal CultureInfo CultureInfo { get { return this.cultureInfo; } } protected internal string HeaderFileName { get { if (string.IsNullOrEmpty(this.headerFileName)) { Guid guid = Guid.NewGuid(); this.headerFileName = Path.Combine(Utils.VoiceMailFilePath, guid.ToString() + ".txt"); } return this.headerFileName; } protected set { this.headerFileName = value; } } protected internal string CallerAddress { get { return this.helper.CallerAddress; } protected set { this.helper.CallerAddress = value; } } protected internal string CallerIdDisplayName { get { return this.helper.CallerIdDisplayName; } protected set { this.helper.CallerIdDisplayName = value; } } protected internal string MessageType { internal get { return this.messageType; } set { this.messageType = value; } } public virtual void PrepareUnProtectedMessage() { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, this.GetHashCode(), "PipelineContext:PrepareUnProtectedMessage.", Array.Empty<object>()); using (DisposeGuard disposeGuard = default(DisposeGuard)) { this.messageToSubmit = MessageItem.CreateInMemory(StoreObjectSchema.ContentConversionProperties); disposeGuard.Add<MessageItem>(this.messageToSubmit); this.SetMessageProperties(); disposeGuard.Success(); } } public virtual void PrepareProtectedMessage() { throw new InvalidOperationException(); } public virtual void PrepareNDRForFailureToGenerateProtectedMessage() { throw new InvalidOperationException(); } public virtual PipelineDispatcher.WIThrottleData GetThrottlingData() { return new PipelineDispatcher.WIThrottleData { Key = this.GetMailboxServerId(), RecipientId = this.GetRecipientIdForThrottling(), WorkItemType = PipelineDispatcher.ThrottledWorkItemType.NonCDRWorkItem }; } public virtual void PostCompletion() { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, 0, "PipelineContext - Deleting header file '{0}'", new object[] { this.headerFileName }); Util.TryDeleteFile(this.headerFileName); } internal static PipelineContext FromHeaderFile(string headerFile) { PipelineContext pipelineContext = null; PipelineContext result; try { ContactInfo contactInfo = null; string text = null; int num = 0; ExDateTime exDateTime = default(ExDateTime); string text2 = null; SubmissionHelper submissionHelper = new SubmissionHelper(); uint num2; using (StreamReader streamReader = File.OpenText(headerFile)) { string text3; while ((text3 = streamReader.ReadLine()) != null) { string[] array = text3.Split(" : ".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries); if (array != null && array.Length == 2) { string text4 = array[0]; num2 = <PrivateImplementationDetails>.ComputeStringHash(text4); if (num2 <= 872212143U) { if (num2 <= 134404218U) { if (num2 != 77294025U) { if (num2 != 111122938U) { - if (num2 == 134404218U) + if (num2 != 134404218U) { - if (text4 == "ProcessedCount") - { - num = Convert.ToInt32(array[1], CultureInfo.InvariantCulture) + 1; - continue; - } + goto IL_409; + } + if (!(text4 == "ProcessedCount")) + { + goto IL_409; } + num = Convert.ToInt32(array[1], CultureInfo.InvariantCulture) + 1; + continue; } - else if (text4 == "RecipientObjectGuid") + else { + if (!(text4 == "RecipientObjectGuid")) + { + goto IL_409; + } submissionHelper.RecipientObjectGuid = new Guid(array[1]); continue; } } - else if (text4 == "CallerNAme") + else { + if (!(text4 == "CallerNAme")) + { + goto IL_409; + } submissionHelper.CallerName = array[1]; continue; } } else if (num2 <= 507978139U) { if (num2 != 152414519U) { - if (num2 == 507978139U) + if (num2 != 507978139U) { - if (text4 == "RecipientName") - { - submissionHelper.RecipientName = array[1]; - continue; - } + goto IL_409; } + if (!(text4 == "RecipientName")) + { + goto IL_409; + } + submissionHelper.RecipientName = array[1]; + continue; } - else if (text4 == "ContactInfo") + else { - contactInfo = (CommonUtil.Base64Deserialize(array[1]) as ContactInfo); - continue; + if (!(text4 == "ContactInfo")) + { + goto IL_409; + } + Exception ex = null; + try + { + try + { + using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(array[1]))) + { + contactInfo = (ContactInfo)TypedBinaryFormatter.DeserializeObject(memoryStream, PipelineContext.contactInfoDeserializationAllowList, null, true); + } + } + catch (ArgumentNullException ex) + { + } + catch (SerializationException ex) + { + } + catch (Exception ex) + { + } + continue; + } + finally + { + if (ex != null) + { + CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, 0, "Failed to get contactInfo from header file {0} with Error={1}", new object[] + { + headerFile, + ex + }); + } + } } } else if (num2 != 707084238U) { - if (num2 == 872212143U) + if (num2 != 872212143U) { - if (text4 == "CallerId") - { - submissionHelper.CallerId = Microsoft.Exchange.UM.UMCommon.PhoneNumber.Parse(array[1]); - continue; - } + goto IL_409; } + if (!(text4 == "CallerId")) + { + goto IL_409; + } + submissionHelper.CallerId = Microsoft.Exchange.UM.UMCommon.PhoneNumber.Parse(array[1]); + continue; } - else if (text4 == "SentTime") + else { + if (!(text4 == "SentTime")) + { + goto IL_409; + } DateTime dateTime = Convert.ToDateTime(array[1], CultureInfo.InvariantCulture); exDateTime = new ExDateTime(ExTimeZone.CurrentTimeZone, dateTime); continue; } } else if (num2 <= 2593661420U) { if (num2 <= 1526417836U) { if (num2 != 978885386U) { - if (num2 == 1526417836U) + if (num2 != 1526417836U) { - if (text4 == "MessageType") - { - text = array[1]; - continue; - } + goto IL_409; + } + if (!(text4 == "MessageType")) + { + goto IL_409; } + text = array[1]; + continue; } - else if (text4 == "CallerAddress") + else { + if (!(text4 == "CallerAddress")) + { + goto IL_409; + } submissionHelper.CallerAddress = array[1]; continue; } } else if (num2 != 1850847732U) { - if (num2 == 2593661420U) + if (num2 != 2593661420U) { - if (text4 == "CallId") - { - submissionHelper.CallId = array[1]; - continue; - } + goto IL_409; } + if (!(text4 == "CallId")) + { + goto IL_409; + } + submissionHelper.CallId = array[1]; + continue; } - else if (text4 == "CallerIdDisplayName") + else { + if (!(text4 == "CallerIdDisplayName")) + { + goto IL_409; + } submissionHelper.CallerIdDisplayName = array[1]; continue; } } else if (num2 <= 3342616108U) { if (num2 != 2975106116U) { - if (num2 == 3342616108U) + if (num2 != 3342616108U) { - if (text4 == "TenantGuid") - { - submissionHelper.TenantGuid = new Guid(array[1]); - continue; - } + goto IL_409; } + if (!(text4 == "TenantGuid")) + { + goto IL_409; + } + submissionHelper.TenantGuid = new Guid(array[1]); + continue; } - else if (text4 == "SenderAddress") + else { + if (!(text4 == "SenderAddress")) + { + goto IL_409; + } string text5 = array[1]; continue; } } else if (num2 != 3581765001U) { - if (num2 == 4186841001U) + if (num2 != 4186841001U) { - if (text4 == "CultureInfo") - { - submissionHelper.CultureInfo = array[1]; - continue; - } + goto IL_409; + } + if (!(text4 == "CultureInfo")) + { + goto IL_409; } + submissionHelper.CultureInfo = array[1]; + continue; } - else if (text4 == "MessageID") + else if (!(text4 == "MessageID")) { - text2 = array[1]; - continue; + goto IL_409; } + text2 = array[1]; + continue; + IL_409: submissionHelper.CustomHeaders[array[0]] = array[1]; } } } num2 = <PrivateImplementationDetails>.ComputeStringHash(text); if (num2 <= 894870128U) { if (num2 <= 360985808U) { if (num2 != 356120169U) { if (num2 == 360985808U) { if (text == "Fax") { pipelineContext = new FaxPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } } else if (text == "IncomingCallLog") { pipelineContext = new IncomingCallLogPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } else if (num2 != 438908515U) { if (num2 != 466919760U) { if (num2 == 894870128U) { if (text == "CDR") { pipelineContext = CDRPipelineContext.Deserialize((string)submissionHelper.CustomHeaders["CDRData"]); - goto IL_62E; + goto IL_694; } } } else if (text == "MissedCall") { pipelineContext = new MissedCallPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } else if (text == "OCSNotification") { pipelineContext = OCSPipelineContext.Deserialize((string)submissionHelper.CustomHeaders["OCSNotificationData"]); text2 = pipelineContext.messageID; exDateTime = pipelineContext.sentTime; - goto IL_62E; + goto IL_694; } } else if (num2 <= 1086454342U) { if (num2 != 995233564U) { if (num2 == 1086454342U) { if (text == "XSOVoiceMail") { pipelineContext = new XSOVoiceMessagePipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } } else if (text == "PartnerTranscriptionRequest") { pipelineContext = new PartnerTranscriptionRequestPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } else if (num2 != 1356218075U) { if (num2 != 2525024257U) { if (num2 == 3974407582U) { if (text == "SMTPVoiceMail") { if (num < PipelineWorkItem.ProcessedCountMax - 1) { pipelineContext = new VoiceMessagePipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } pipelineContext = new MissedCallPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } } } else if (text == "HealthCheck") { pipelineContext = new HealthCheckPipelineContext(Path.GetFileNameWithoutExtension(headerFile)); - goto IL_62E; + goto IL_694; } } else if (text == "OutgoingCallLog") { pipelineContext = new OutgoingCallLogPipelineContext(submissionHelper); - goto IL_62E; + goto IL_694; } throw new HeaderFileArgumentInvalidException(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", "MessageType", text)); - IL_62E: + IL_694: if (text2 == null) { text2 = Guid.NewGuid().ToString(); exDateTime = ExDateTime.Now; } pipelineContext.HeaderFileName = headerFile; pipelineContext.processedCount = num; if (contactInfo != null) { IUMResolveCaller iumresolveCaller = pipelineContext as IUMResolveCaller; if (iumresolveCaller != null) { iumresolveCaller.ContactInfo = contactInfo; } } pipelineContext.sentTime = exDateTime; pipelineContext.messageID = text2; pipelineContext.WriteHeaderFile(headerFile); result = pipelineContext; } - catch (IOException ex) + catch (IOException ex2) { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, 0, "Failed to parse the header file {0} because its not closed by thread creating the file. Error={1}", new object[] { headerFile, - ex + ex2 }); if (pipelineContext != null) { pipelineContext.Dispose(); pipelineContext = null; } result = null; } - catch (InvalidObjectGuidException ex2) + catch (InvalidObjectGuidException ex3) { CallIdTracer.TraceWarning(ExTraceGlobals.VoiceMailTracer, 0, "Couldn't find the recipient for this message. Error={0}", new object[] { - ex2 + ex3 }); if (pipelineContext != null) { pipelineContext.Dispose(); pipelineContext = null; } throw; } - catch (InvalidTenantGuidException ex3) + catch (InvalidTenantGuidException ex4) { CallIdTracer.TraceWarning(ExTraceGlobals.VoiceMailTracer, 0, "Couldn't find the tenant for this message. Error={0}", new object[] { - ex3 + ex4 }); if (pipelineContext != null) { pipelineContext.Dispose(); pipelineContext = null; } throw; } - catch (NonUniqueRecipientException ex4) + catch (NonUniqueRecipientException ex5) { CallIdTracer.TraceWarning(ExTraceGlobals.VoiceMailTracer, 0, "Multiple objects found for the recipient. Error={0}", new object[] { - ex4 + ex5 }); if (pipelineContext != null) { pipelineContext.Dispose(); pipelineContext = null; } throw; } return result; } internal abstract void WriteCustomHeaderFields(StreamWriter headerStream); public abstract string GetMailboxServerId(); public abstract string GetRecipientIdForThrottling(); internal virtual void SaveMessage() { this.WriteHeaderFile(this.HeaderFileName); } protected override void InternalDispose(bool disposing) { if (disposing) { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, this.GetHashCode(), "PipelineContext.Dispose() called", Array.Empty<object>()); } } protected override DisposeTracker InternalGetDisposeTracker() { return DisposeTracker.Get<PipelineContext>(this); } protected virtual void SetMessageProperties() { IUMResolveCaller iumresolveCaller = this as IUMResolveCaller; if (iumresolveCaller != null) { ExAssert.RetailAssert(iumresolveCaller.ContactInfo != null, "ResolveCallerStage should always set the ContactInfo."); UMSubscriber umsubscriber = ((IUMCAMessage)this).CAMessageRecipient as UMSubscriber; UMDialPlan dialPlan = (umsubscriber != null) ? umsubscriber.DialPlan : null; Microsoft.Exchange.UM.UMCommon.PhoneNumber pstnCallbackTelephoneNumber = this.CallerId.GetPstnCallbackTelephoneNumber(iumresolveCaller.ContactInfo, dialPlan); this.messageToSubmit.From = iumresolveCaller.ContactInfo.CreateParticipant(pstnCallbackTelephoneNumber, this.CultureInfo); XsoUtil.SetVoiceMessageSenderProperties(this.messageToSubmit, iumresolveCaller.ContactInfo, dialPlan, this.CallerId); this.messageToSubmit.InternetMessageId = BoomerangHelper.FormatInternetMessageId(this.MessageID, Utils.GetHostFqdn()); this.messageToSubmit[ItemSchema.SentTime] = this.SentTime; } this.messageToSubmit.AutoResponseSuppress = AutoResponseSuppress.All; this.messageToSubmit[MessageItemSchema.CallId] = this.helper.CallId; IUMCAMessage iumcamessage = this as IUMCAMessage; if (iumcamessage != null) { this.MessageToSubmit.Recipients.Add(new Participant(iumcamessage.CAMessageRecipient.ADRecipient)); IADSystemConfigurationLookup iadsystemConfigurationLookup = ADSystemConfigurationLookupFactory.CreateFromOrganizationId(iumcamessage.CAMessageRecipient.ADRecipient.OrganizationId); this.MessageToSubmit.Sender = new Participant(iadsystemConfigurationLookup.GetMicrosoftExchangeRecipient()); } } protected void WriteHeaderFile(string headerFileName) { using (FileStream fileStream = File.Open(headerFileName, FileMode.Create, FileAccess.Write, FileShare.None)) { using (StreamWriter streamWriter = new StreamWriter(fileStream)) { if (this.MessageType != null) { streamWriter.WriteLine("MessageType : " + this.MessageType); } streamWriter.WriteLine("ProcessedCount : " + this.processedCount.ToString(CultureInfo.InvariantCulture)); if (this.messageID != null) { streamWriter.WriteLine("MessageID : " + this.messageID); } if (this.sentTime.Year != 1) { streamWriter.WriteLine("SentTime : " + this.sentTime.ToString(CultureInfo.InvariantCulture)); } this.WriteCommonHeaderFields(streamWriter); this.WriteCustomHeaderFields(streamWriter); } } } protected virtual void WriteCommonHeaderFields(StreamWriter headerStream) { if (!this.CallerId.IsEmpty) { headerStream.WriteLine("CallerId : " + this.CallerId.ToDial); } if (this.helper.RecipientName != null) { headerStream.WriteLine("RecipientName : " + this.helper.RecipientName); } if (this.helper.RecipientObjectGuid != Guid.Empty) { headerStream.WriteLine("RecipientObjectGuid : " + this.helper.RecipientObjectGuid.ToString()); } if (this.helper.CallerName != null) { headerStream.WriteLine("CallerNAme : " + this.helper.CallerName); } if (!string.IsNullOrEmpty(this.helper.CallerIdDisplayName)) { headerStream.WriteLine("CallerIdDisplayName : " + this.helper.CallerIdDisplayName); } if (this.CallerAddress != null) { headerStream.WriteLine("CallerAddress : " + this.CallerAddress); } if (this.helper.CultureInfo != null) { headerStream.WriteLine("CultureInfo : " + this.helper.CultureInfo); } if (this.helper.CallId != null) { headerStream.WriteLine("CallId : " + this.helper.CallId); } IUMResolveCaller iumresolveCaller = this as IUMResolveCaller; if (iumresolveCaller != null && iumresolveCaller.ContactInfo != null) { headerStream.WriteLine("ContactInfo : " + CommonUtil.Base64Serialize(iumresolveCaller.ContactInfo)); } headerStream.WriteLine("TenantGuid : " + this.helper.TenantGuid.ToString()); } protected UMRecipient CreateRecipientFromObjectGuid(Guid objectGuid, Guid tenantGuid) { return UMRecipient.Factory.FromADRecipient<UMRecipient>(this.CreateADRecipientFromObjectGuid(objectGuid, tenantGuid)); } protected ADRecipient CreateADRecipientFromObjectGuid(Guid objectGuid, Guid tenantGuid) { if (objectGuid == Guid.Empty) { throw new HeaderFileArgumentInvalidException("ObjectGuid is empty"); } ADRecipient adrecipient = ADRecipientLookupFactory.CreateFromTenantGuid(tenantGuid).LookupByObjectId(new ADObjectId(objectGuid)); if (adrecipient == null) { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, 0, "Could not find recipient {0}", new object[] { objectGuid.ToString() }); throw new InvalidObjectGuidException(objectGuid.ToString()); } return adrecipient; } protected UMDialPlan InitializeCallerIdAndTryGetDialPlan(UMRecipient recipient) { UMDialPlan umdialPlan = null; if (this.CallerId.UriType == UMUriType.E164 && recipient.ADRecipient.UMRecipientDialPlanId != null) { umdialPlan = ADSystemConfigurationLookupFactory.CreateFromADRecipient(recipient.ADRecipient).GetDialPlanFromId(recipient.ADRecipient.UMRecipientDialPlanId); if (umdialPlan != null && umdialPlan.CountryOrRegionCode != null) { this.helper.CallerId = this.helper.CallerId.Clone(umdialPlan); } } return umdialPlan; } protected string GetMailboxServerIdHelper() { IUMCAMessage iumcamessage = this as IUMCAMessage; if (iumcamessage != null) { UMMailboxRecipient ummailboxRecipient = iumcamessage.CAMessageRecipient as UMMailboxRecipient; if (ummailboxRecipient != null) { return ummailboxRecipient.ADUser.ServerLegacyDN; } } return "af360a7e-e6d4-494a-ac69-6ae14896d16b"; } protected string GetRecipientIdHelper() { IUMCAMessage iumcamessage = this as IUMCAMessage; if (iumcamessage != null) { UMMailboxRecipient ummailboxRecipient = iumcamessage.CAMessageRecipient as UMMailboxRecipient; if (ummailboxRecipient != null) { return ummailboxRecipient.ADUser.DistinguishedName; } } return "455e5330-ce1f-48d1-b6b1-2e318d2ff2c4"; } private MessageItem messageToSubmit; private SubmissionHelper helper; private string messageType; private CultureInfo cultureInfo; private string headerFileName; private int processedCount; private string messageID; private ExDateTime sentTime; + + private static Type[] contactInfoDeserializationAllowList = new Type[] + { + typeof(Version), + typeof(Guid), + typeof(PropTag), + typeof(ContactInfo), + typeof(ADContactInfo), + typeof(FoundByType), + typeof(ADUser), + typeof(ADPropertyBag), + typeof(ValidationError), + typeof(ADPropertyDefinition), + typeof(ADObjectId), + typeof(ExchangeObjectVersion), + typeof(ExchangeBuild), + typeof(MultiValuedProperty<string>), + typeof(LocalizedString), + typeof(ProxyAddressCollection), + typeof(SmtpAddress), + typeof(RecipientDisplayType), + typeof(RecipientTypeDetails), + typeof(ElcMailboxFlags), + typeof(UserAccountControlFlags), + typeof(ObjectState), + typeof(DirectoryBackendType), + typeof(MServPropertyDefinition), + typeof(MbxPropertyDefinition), + typeof(MbxPropertyDefinitionFlags), + typeof(OrganizationId), + typeof(PartitionId), + typeof(SmtpProxyAddress), + typeof(SmtpProxyAddressPrefix), + typeof(ByteQuantifiedSize), + typeof(Unlimited<ByteQuantifiedSize>), + typeof(List<ValidationError>), + typeof(ADMultiValuedProperty<TextMessagingStateBase>), + typeof(ADMultiValuedProperty<ADObjectId>), + typeof(StoreObjectId), + typeof(StoreObjectType), + typeof(EntryIdProvider), + typeof(SimpleContactInfoBase), + typeof(MultipleResolvedContactInfo), + typeof(CallerNameDisplayContactInfo), + typeof(PersonalContactInfo), + typeof(DefaultContactInfo), + typeof(UMDialPlan), + typeof(UMEnabledFlags), + Type.GetType("Microsoft.Exchange.Data.ByteQuantifiedSize+QuantifierProvider, Microsoft.Exchange.Data"), + Type.GetType("System.UnitySerializationHolder, mscorlib"), + Type.GetType("Microsoft.Exchange.Data.ByteQuantifiedSize+Quantifier,Microsoft.Exchange.Data"), + Type.GetType("Microsoft.Exchange.Data.PropertyBag+ValuePair, Microsoft.Exchange.Data"), + Type.GetType("System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"), + typeof(DialByNamePrimaryEnum), + typeof(DialByNameSecondaryEnum), + typeof(AudioCodecEnum), + typeof(UMUriType), + typeof(UMSubscriberType), + typeof(UMGlobalCallRoutingScheme), + typeof(UMVoIPSecurityType), + typeof(SystemFlagsEnum), + typeof(EumProxyAddress), + typeof(EumProxyAddressPrefix) + }; } }
The patch appears to add and use a typed allowlist for deserialization of a voicemail’s contact info, which is found in a header file alongside the voicemail itself. Other seemingly unprotected deserializations can be seen in the same class. (I think it’s just XML parsing.) My suspicion is that CVE-2021-26858 or CVE-2021-27065 could be used to write a malicious header file to C:\Program Files\Microsoft\Exchange Server\V15\UnifiedMessaging\voicemail
, but it’s entirely possible a crafted voicemail could be sent instead. While I haven’t developed a PoC yet, I do have a good idea how to, assuming the patch analysis is correct. Better-resourced attackers should be able to exploit this issue in considerably less time.
The specifically patched code can be seen below:
[snip] else { if (!(text4 == "ContactInfo")) { goto IL_409; } Exception ex = null; try { try { using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(array[1]))) { contactInfo = (ContactInfo)TypedBinaryFormatter.DeserializeObject(memoryStream, PipelineContext.contactInfoDeserializationAllowList, null, true); } } catch (ArgumentNullException ex) { } catch (SerializationException ex) { } catch (Exception ex) { } continue; } finally { if (ex != null) { CallIdTracer.TraceDebug(ExTraceGlobals.VoiceMailTracer, 0, "Failed to get contactInfo from header file {0} with Error={1}", new object[] { headerFile, ex }); } } } [snip]
[snip] private static Type[] contactInfoDeserializationAllowList = new Type[] { typeof(Version), typeof(Guid), typeof(PropTag), typeof(ContactInfo), typeof(ADContactInfo), typeof(FoundByType), typeof(ADUser), typeof(ADPropertyBag), typeof(ValidationError), typeof(ADPropertyDefinition), typeof(ADObjectId), typeof(ExchangeObjectVersion), typeof(ExchangeBuild), typeof(MultiValuedProperty<string>), typeof(LocalizedString), typeof(ProxyAddressCollection), typeof(SmtpAddress), typeof(RecipientDisplayType), typeof(RecipientTypeDetails), typeof(ElcMailboxFlags), typeof(UserAccountControlFlags), typeof(ObjectState), typeof(DirectoryBackendType), typeof(MServPropertyDefinition), typeof(MbxPropertyDefinition), typeof(MbxPropertyDefinitionFlags), typeof(OrganizationId), typeof(PartitionId), typeof(SmtpProxyAddress), typeof(SmtpProxyAddressPrefix), typeof(ByteQuantifiedSize), typeof(Unlimited<ByteQuantifiedSize>), typeof(List<ValidationError>), typeof(ADMultiValuedProperty<TextMessagingStateBase>), typeof(ADMultiValuedProperty<ADObjectId>), typeof(StoreObjectId), typeof(StoreObjectType), typeof(EntryIdProvider), typeof(SimpleContactInfoBase), typeof(MultipleResolvedContactInfo), typeof(CallerNameDisplayContactInfo), typeof(PersonalContactInfo), typeof(DefaultContactInfo), typeof(UMDialPlan), typeof(UMEnabledFlags), Type.GetType("Microsoft.Exchange.Data.ByteQuantifiedSize+QuantifierProvider, Microsoft.Exchange.Data"), Type.GetType("System.UnitySerializationHolder, mscorlib"), Type.GetType("Microsoft.Exchange.Data.ByteQuantifiedSize+Quantifier,Microsoft.Exchange.Data"), Type.GetType("Microsoft.Exchange.Data.PropertyBag+ValuePair, Microsoft.Exchange.Data"), Type.GetType("System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"), typeof(DialByNamePrimaryEnum), typeof(DialByNameSecondaryEnum), typeof(AudioCodecEnum), typeof(UMUriType), typeof(UMSubscriberType), typeof(UMGlobalCallRoutingScheme), typeof(UMVoIPSecurityType), typeof(SystemFlagsEnum), typeof(EumProxyAddress), typeof(EumProxyAddressPrefix) }; [snip]
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- microsoft
Products
- exchange server 2010,
- exchange server 2013,
- exchange server 2016,
- exchange server 2019
Exploited in the Wild
Would you like to delete this Exploited in the Wild Report?
Yes, delete this report- Government or Industry Alert (https://us-cert.cisa.gov/ncas/alerts/aa21-209a)
- Other: 2021 Commonly Exploited Vulnerabilities (https://www.ic3.gov/Media/News/2021/210728.pdf)
Would you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportReferences
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
I’m puzzled by this vuln. What is the nature of the bug? I mean is it a memory bug like a buffer overflow or use after free that happens during deserialization? I don’t understand why parsing an XML header would result in an RCE without some sort of memory bug.
And the code you pasted looks like C#, so that’s even more puzzling. The usual memory bugs normally depend on manual memory managed C/C++ code. How is this happening in C#, unless we’re talking about the .NET JIT written in C++ or something? And is this code open source? Is it trivial to decompile and unobfuscate Microsoft’s closed source C# applications?
@JoeUX: Those are great questions! Deserialization itself can be the vulnerability. Moreover, much of Exchange is written in .NET, which can be easily decompiled. Hope this helps.
@JoeUX Continuing on @wvu-r7’s answer though, take a look at XML external entity injection attacks (XXE) as described at https://portswigger.net/web-security/xxe for some more examples. Basically any parser can have RCE attacks in it depending on what features that parser has, and those can be abused at times to grant additional privileges. In the example of an XML parser you might be able to get it to include an external entity that makes a HTTP GET request to a certain page to reset a device back to its default settings, thereby allowing RCE by logging in with default credentials. Another way might be achieving RCE by exfiltrating a file containing the SSH login password hash such as
/etc/passwd
and then cracking that password to get the login information.As for deserialization attacks, keep in mind that its often kind of hard to determine the true impact of deserialization bugs without understanding the code around the deserialization itself. In some cases it may be possible to get RCE by abusing the deserialization to set the application state in such a way that when a certain code block is executed, it results in arbitrary code execution. In other cases, it may only be possible to read arbitrary files on the system. This is why determining the impact of a deserialization bug can sometimes be difficult without additional context.
In the case of this vulnerability, it seems there was some chain of code that could be triggered which utilized the adjusted object state (which is caused by the deserialization vulnerability) in such a way that it allowed for remote code execution to occur.
As for C# vs C++ vs other languages and deserialization itself, keep in mind many languages support deserialization. This bug is not unique to C++ or C# or really any programming language in particular and RCE could occur due to deserialization languages in most common languages that you can think of, including PHP, Ruby, Perl, C++, .NET, Python, etc.
Exchange is not open source in and of itself, however as @wvu-r7 mentioned it is trivial to decompile .NET code using tools such as dnspy or dotNetPeek, both of which are freely available tools that can be downloaded from the internet to decompile .NET applications, and which can be used along with manual code review skills to find bugs such as this one. Keep in mind though that decompiling it is one thing, making sense of the code however is another as Exchange is a large application with a complex code base which requires time and patience to understand.