---------------------------------------------------------------- v5 --- modzero Security Advisory: Multiple deserialization vulnerabilities in the .Net runtime [MZ-20-03] ----------------------------------------------------------------------- ----------------------------------------------------------------------- 1. Timeline ----------------------------------------------------------------------- * 2020-02-14: This advisory has been sent to the Microsoft security team (security@microsoft.com). * 2020-02-19: Microsoft requests that the three vunerabilities are resubmitted individually. * 2020-02-19: Vulnerabilities resubmitted individually. * 2020-02-29: Microsoft closes 4.2 as "By Design". * 2020-03-19: Microsoft accepts 4.1 as a security issue. * 2020-03-19: Microsoft closes 4.3 as "By Design". * 2020-04-07: Microsoft informs modzero of a planned patch release on June 9th. * 2020-06-02: Microsoft informs modzero that the vulnerability will be fixed with documentation only. * 2020-06-08: modzero replies with concerns regarding the proposed fix. * 2020-06-15: Microsoft replies that they will go through with the fix. * 2020-06-16: modzero publishes this disclosure. ----------------------------------------------------------------------- 2. Summary ----------------------------------------------------------------------- Vendor: Microsoft * 4.1 Deserialization vulnerability in IsolatedStorageFileEnumerator::MoveNext via crafted identity.dat file leading to arbitrary code execution modzero: CVSS:3.0/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H -> 8.2 * 4.2 Deserialization vulnerability in BinaryServerFormatterSink::ProcessMessage 4.2.1 When configured with TypeFilterLevel.Low Denial of Service modzero: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H -> 7.5 4.2.2 When configured with TypeFilterLevel.Full Remote code execution modzero: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H -> 9.0 * 4.3 Deserialization vulnerability in System.Messaging.Message::get_Body() using a BinaryMessageFormatter leading to remote code execution modzero: CVSS:3.0/AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H -> 9.0 ----------------------------------------------------------------------- 3.0 Introduction ----------------------------------------------------------------------- modzero identified several critical vulnerabilities in the .Net runtime which can lead to denial of service or remote code execution attacks against services using standard built-in .NET features. A potential for local privilege escalation or persistence using the IsolatedStorage vulnerability was also found. Any software using the vulnerable .Net components is potentially affected. Specifically: * Enumeration of IsolatedStorage spaces * .Net Remoting with binary serialization * .Net MSMQ with a BinaryMessageFormatter modzero identified and tested the vulnerabilities to be present in: .NET Framework 4.8 4.7.2 4.7.1 4.7 4.6.2 4.6.1 4.6 4.5.2 4.5.1 4.5 Earlier versions have not been tested, but are likely to at least be partially affected as well. ----------------------------------------------------------------------- 4. Details ----------------------------------------------------------------------- 4.1 Triggering Deserialization vulnerability in IsolatedStorageFileEnumerator::MoveNext via crafted identity.dat file leads to arbitrary code execution An attacker with write access to the identity.dat file can inject a deserialization payload, which will be executed when the built-in .NET method IsolatedStorageFileEnumerator::MoveNext is called. When creating an IsolatedStorage space in the Machine scope (IsolatedStorageScope.Machine) its identity.dat file has read and write permissions for the "Everyone" Windows-Group. An attacker with access to any account can create a Machine scope IsolatedStorage space and cause the vulnerability to trigger on the next enumeration. The enumeration itself does not have to be controlled or issued by the attacker and thus the execution takes place in the context where enumeration occurs. When using an IsolatedStorageFileEnumerator to enumerate IsolatedStorage spaces, the MoveNext method will read the contents of each space's identity.dat file and deserialize them without any security features enabled. The identity.dat files in the Machine scope have read/write permissions for the Everyone group and a low privileged user can craft an identity.dat file to execute a standard deserialization attack when another user enumerates storage spaces. This for example affects the storeadm.exe tool. The following code snippets demonstrate how the data from the identity.dat file is passed directly into a BinaryFormatter without further sanitization or any security measures. IsolatedStorageFileEnumerator::MoveNext calls this.GetIDStream(twoPaths.Path1, out stream) to retrieve the file contents of the associated identity.dat file of each IsolatedStorage space into a stream variable. public bool MoveNext() { while (this.m_fileEnum.MoveNext()) { [...] if (flag) { if (!this.GetIDStream(twoPaths.Path1, out stream) || !this.GetIDStream(twoPaths.Path1 + "\\" + twoPaths.Path2, out stream2)) [...] } else if (IsolatedStorageFile.NotAppFilesDir(twoPaths.Path2)) { if (!this.GetIDStream(twoPaths.Path1, out stream2)) [...] stream2.Position = 0L; } else { if (!this.GetIDStream(twoPaths.Path1, out stream3)) [...] stream3.Position = 0L; } The previously populated stream variable holding the possibliy malicious identity.dat file's content is passed to an overload of the InitStore method as documented in the followind code section. if (isolatedStorageFile.InitStore(scope, stream, stream2, stream3, domainName, assemName, appName) && isolatedStorageFile.InitExistingStore(scope)) { this.m_Current = isolatedStorageFile; return true; } The InitStore method then passes the MemoryStream of the file contents into a BinaryFormatter without enabling any security features on it. internal bool InitStore(IsolatedStorageScope scope, Stream domain, Stream assem, Stream app, string domainName, string assemName, string appName) { BinaryFormatter binaryFormatter = new BinaryFormatter(); [...] this.m_AppIdentity = binaryFormatter.Deserialize(app); [...] this.m_AssemIdentity = binaryFormatter.Deserialize(assem); [...] this.m_DomainIdentity = binaryFormatter.Deserialize(domain); This allows execution of arbitrary code by utilizing standard BinaryFormatter deserialization gadgets; payloads can for example be generated using the ysoserial.net tool. This can be used for privilege escalation, especially since enumeration of isolated storage spaces is typically only performed during administrative tasks. When creating an IsolatedStorage space scoped to a user with a roaming profile, the modified identity.dat file may be automatically transferred across an Active Directory network. In this case the vulnerability may spread across the network if an enumeration of storage spaces is regularly performed. A transferred payload can infect another computers Machine scope which can in turn infect other users and their roaming scope. 4.2 Deserialization vulnerability in BinaryServerFormatterSink::ProcessMessage leading to Denial of Service (DoS) By sending a crafted message to a .Net remoting channel a denial of service or remote code execution can be triggered if the channel uses a BinaryServerFormatterSink in its SinkChain, which it does in the default configuation. Wether or not the DoS or RCE will trigger depends on what the BinaryServerFormatterSink's TypeFilterLevel has been set to. The default is Low, in which case only the Denial of Service can be triggered. If it has been set to Full instead, remote code execution can be performed. When using Remoting in .Net the incoming and outgoing messages are processed by SinkChains, which are essentially a linked list of sinks. These sinks are passed the current data, perform some processing and pass the updated data on to the next chain for further processing. One of these sinks is the BinaryServerFormatterSink which processes incoming messages which have been serialized to a binary format. When an incoming message is received, ProcessMessage is called on the BinaryServerFormatterSink instance, the requestStream that is passed to it contains the serialized message that the sink is ment to decode. If the TypeFilterLevel property of the BinaryFormatterSink has been set to Low it will restrict the security context to only grant the SerializationFormatter permission. If the TypeFilterLevel is set to Full, the security context won't be restricted. (https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.typefilterlevel?view=netframework-4.8) Afterwards it will call CoreChannel.DeserializeBinaryRequestMessage with the requestStream it has been called with. if (this.TypeFilterLevel != TypeFilterLevel.Full) { permissionSet = new PermissionSet(PermissionState.None); permissionSet.SetPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter)); } try { if (permissionSet != null) { permissionSet.PermitOnly(); } requestMsg = CoreChannel.DeserializeBinaryRequestMessage(text5, requestStream, this._strictBinding, this.TypeFilterLevel); } finally { if (permissionSet != null) { CodeAccessPermission.RevertPermitOnly(); } } CoreChannel.DeserializeBinaryRequestMessage then initializes a BinaryFormatter and sets its FilterLevel according to the BinaryServerFormatterSink's. It then calls UnsafeDeserialize on the BinaryFormatter. internal static IMessage DeserializeBinaryRequestMessage(string objectUri, Stream inputStream, bool bStrictBinding, TypeFilterLevel securityLevel) { BinaryFormatter binaryFormatter = CoreChannel.CreateBinaryFormatter(false, bStrictBinding); binaryFormatter.FilterLevel = securityLevel; CoreChannel.UriHeaderHandler @object = new CoreChannel.UriHeaderHandler(objectUri); return (IMessage)binaryFormatter.UnsafeDeserialize(inputStream, new HeaderHandler(@object.HeaderHandler)); } The UnsafeDeserialize call then gets passed down to a Deserialize call which instantiates an ObjectReader with the corresponding TypeFilterLevel. It then calls ObjectReader::Deserialize on the new ObjectReader instance. internal object Deserialize(Stream serializationStream, HeaderHandler handler, bool fCheck, bool isCrossAppDomain, IMethodCallMessage methodCallMessage) { [...] internalFE.FEsecurityLevel = this.m_securityLevel; ObjectReader objectReader = new ObjectReader(serializationStream, this.m_surrogates, this.m_context, internalFE, this.m_binder); objectReader.crossAppDomainArray = this.m_crossAppDomainArray; return objectReader.Deserialize(handler, new __BinaryParser(serializationStream, objectReader), fCheck, isCrossAppDomain, methodCallMessage); } ObjectReader::Deserialize then performs the deserialization. Additional security checks are performed if IsRemoting is true. internal void CheckSecurity(ParseRecord pr) { Type prdtType = pr.PRdtType; if (prdtType != null && this.IsRemoting) { [...] FormatterServices.CheckTypeSecurity(prdtType, this.formatterEnums.FEsecurityLevel); } } IsRemoting is true if either bMethodCall or bMethodReturn is true. private bool IsRemoting { get { return this.bMethodCall || this.bMethodReturn; } } These two values (bMethodCall and bMethodReturn) are only set to true by the SetMethodCall and SetMethodReturn methods respectively. internal void SetMethodCall(BinaryMethodCall binaryMethodCall) { this.bMethodCall = true; this.binaryMethodCall = binaryMethodCall; } internal void SetMethodReturn(BinaryMethodReturn binaryMethodReturn) { this.bMethodReturn = true; this.binaryMethodReturn = binaryMethodReturn; } Those two methods are only called from__BinaryParser::ReadMethodObject, which is only called from __BinaryParser::Run case BinaryHeaderEnum.MethodCall: case BinaryHeaderEnum.MethodReturn: this.ReadMethodObject(binaryHeaderEnum); Therefore additional security checks are only performed if an IMessage is being deserialized. An attacker can bypass the additional security checks by submitting a crafted stream of data that contains no IMessage object and triggers execution of deserialization gadgets. Using the standard TypeConfuseDelegate gadget, a call to System.Diagnostics.Process::Start(string,string) can be performed. In the case of TypeFilterLevel being set to Low (4.2.1) the call to Process.Start then causes an uncaught SecurityException, since the security context is restricted. The exception occurs in System.Diagnostics.ShellExecuteHelper::ShellExecuteFunction in a separate thread spawned by System.Diagnostics.ShellExecuteHelper::ShellExecuteOnSTAThread. The uncaught exception then causes termination of the process leading to Denial of Service. In the case of TypeFilterLevel being set to Full (4.2.2) the call to Process.Start passes all security checks and a new process is started. In this case arbitrary code can be executed remotely as long as the channel is accessible. Because there are no restrictions imposed on the deserialization, when using an HTTP channel 4.2.2 can also be exploited by generating a payload file with the ysoserial.net tool and a curl request of the form: curl -X POST -H "Content-Type: application/octet-stream" --data-binary "@payload" http://serveraddress/Service 4.3 Deserialization vulnerability in System.Messaging.Message::get_Body() using a BinaryMessageFormatter When using Microsoft Message Queueing (MSMQ) with .Net, messages retrieved from the Queue are processed into a Message object. This object contains an IMessageFormatter property and in the case of a retrieved messageit contains a BodyStream that holds the serialized body of the message. This BodyStream is not parsed immediately, but will instead be deserialized only when the Body's getter is accessed. The getter then calls Formatter.Read on its IMessageFormatter instance to create the actual Body object. public object Body { get { if (this.filter.Body) { if (this.cachedBodyObject == null) { [...] this.cachedBodyObject = this.Formatter.Read(this); } return this.cachedBodyObject; } If the IMessageFormatter is a BinaryMessageFormatter, its Read method checks if the BodyType is compatible and then calls BinaryFormatter::Deserialize on a default BinaryFormatter instance with no additional security features enabled. public BinaryMessageFormatter() { this.formatter = new BinaryFormatter(); } public object Read(Message message) { [...] int bodyType = message.BodyType; if (bodyType == 768) { Stream bodyStream = message.BodyStream; return this.formatter.Deserialize(bodyStream); This allows the use of arbitrary deserialization gadgets and can be used to execute arbitrary code when somebody retrieves messages from the message queue. ----------------------------------------------------------------------- 5. Proof of Concept exploits ----------------------------------------------------------------------- PoC exploits are provided as separate git repos containing Visual Studio Solutions. The IsolatedStorageVulnerability solution demonstrates vulnerability 4.1. After executing the PoC, any vulnerable program enumerating the Machine scope will execute a program when deserializing the payload. Running "storeadm /List" in the Visual Studio Developer Console for example will trigger the vulnerability. The RemotingVulnerability solution demonstrates vulnerabilities 4.2. It contains two projects: * RemotingService - a bare bones Remoting server * RemotingExploit - the actual exploit When the server is running and configured with TypeFilterLevel.Low the exploit will crash the server process. When the server is running and configured with TypeFilterLevel.Full the exploit will trigger code execution in the server process. The MSMQ solution demonstrates vulnerability 4.3. It contains two projects: * MSMQ Reader - a small program polling messages from an MSMQ * MSMQ Exploit - the actual exploit When the reader is running the exploit will cause code execution to occur as soon as the getter of the Body property of the Message is accessed. All projects use the TypeConfuseDelegate gadget with SortedSet`1 to reach code execution from the deserialization vulnerability. Please find the PoC projects at GitHub: * https://github.com/modzero/MZ-20-03_PoC_IsolatedStorage * https://github.com/modzero/MZ-20-03_PoC_NetRemoting * https://github.com/modzero/MZ-20-03_PoC_MSMQ_BinaryMessageFormatter ----------------------------------------------------------------------- 6. Workarounds ----------------------------------------------------------------------- For 4.2, restricting access to the remoting channel will reduce the potential attack vectors. When using Tcp or Icp channels, enabling authentication can mitigate some risks as well. If possible setting TypeFilterLevel to Low will mitigate the RCE to a DoS but business cases might require using TypeFilterLevel.Full For 4.3, if possible, restrict access to any queue unless necessary. ----------------------------------------------------------------------- 7. Fix ----------------------------------------------------------------------- Currently, no fixes are available. ----------------------------------------------------------------------- 8. Credits ----------------------------------------------------------------------- * Nils Ole Timm ----------------------------------------------------------------------- 9. About modzero ----------------------------------------------------------------------- The independent Swiss-German company modzero assists clients with security analysis in the complex areas of computer technology. The focus lies on highly detailed technical analysis of concepts, software and hardware components as well as the development of individual solutions. Colleagues at modzero work exclusively in practical, highly technical computer-security areas and can draw on decades of experience in various platforms, system concepts, and designs. https://www.modzero.com contact@modzero.com modzero follows coordinated disclosure practices described here: https://www.modzero.com/static/modzero_Disclosure_Policy.pdf. This policy should have been sent to the vendor along with this security advisory. ----------------------------------------------------------------------- 10. Disclaimer ----------------------------------------------------------------------- The information in the advisory is believed to be accurate at the time of publishing based on currently available information. Use of the information constitutes acceptance for use in an AS IS condition. There are no warranties with regard to this information. Neither the author nor the publisher accepts any liability for any direct, indirect, or consequential loss or damage arising from use of, or reliance on, this information.