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:
- 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.
- 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).
- The server creates an instance of ObjectDataProvider and then populates it with the rest of the JSON data we provided.
- 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.
- 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!
- 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.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: