PHPGGC: Exploiting PHP Deserialization Without Writing Your Own Gadget Chains
Hook
When you discover an unserialize() vulnerability in a Laravel application, you face a choice: spend hours hunting for exploitable class chains in vendor code, or generate a working RCE payload in under 30 seconds. PHPGGC makes that choice obvious.
Context
PHP’s unserialize() function has been a security nightmare since developers realized that deserializing untrusted data allows attackers to instantiate arbitrary objects. The real exploitation happens through gadget chains—sequences of existing framework classes whose magic methods (__destruct, __toString, etc.) can be chained together to achieve code execution or file manipulation. Before PHPGGC, exploiting deserialization vulnerabilities required deep knowledge of framework internals: you’d grep through vendor directories looking for dangerous __destruct implementations, trace property access chains, and manually construct serialized payloads. This research could take days per framework.
Created by Ambionics Security, PHPGGC is the PHP equivalent of ysoserial for Java—a pre-built library of deserialization gadget chains across major frameworks including CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii and ZendFramework. It’s both a command-line tool for penetration testers and supports programmatic use for security researchers. The project recognizes a fundamental truth: the same framework classes that power millions of websites can be weaponized through deserialization, and those weapons shouldn’t need to be forged from scratch every engagement.
Technical Insight
PHPGGC’s architecture centers on pre-built gadget chains organized by framework. When you run ./phpggc monolog/rce1 assert 'phpinfo()', the tool generates the appropriate gadget chain with your parameters and returns a serialized payload ready for injection.
The gadget chains are version-aware, type-specific, and vector-aware. For instance, Laravel has ten different RCE chains (RCE1 through RCE10), each targeting different versions and exploiting different code paths. Some trigger on __destruct, others on __toString. Some execute shell commands directly, others inject PHP code, and some call arbitrary functions. This granularity matters when you’re dealing with specific application contexts.
Consider a practical example. The Symfony/RCE4 chain is classified as “RCE (Function call)”, meaning you specify both the function and its argument:
./phpggc symfony/rce4 system 'cat /etc/passwd'
This generates a serialized object that, when unserialized, will call system('cat /etc/passwd'). Behind the scenes, PHPGGC is chaining Symfony’s components, exploiting how destructors fire and properties get accessed. The beauty is you don’t need to understand the internals—the gadget chain author already did that work.
For file operations, the syntax adapts to the operation type. A file write chain like SwiftMailer/FW1 takes a destination path and source file:
echo '<?php system($_GET["c"]); ?>' > /tmp/shell.php
./phpggc swiftmailer/fw1 /var/www/html/backdoor.php /tmp/shell.php
This constructs a payload that writes your shell to the target location when deserialized. The chain leverages SwiftMailer’s file handling classes in unexpected ways, turning email functionality into a file write primitive.
PHPGGC also supports PHAR-based exploitation, a lesser-known attack vector. PHP’s PHAR archives store serialized metadata in their headers, and various file operations (file_exists, fopen, etc.) trigger deserialization when passed PHAR URIs. PHPGGC can generate PHAR files in three formats (PHAR, TAR, and ZIP), and even polyglot JPEG/PHAR files that pass image validation while containing malicious serialized data:
./phpggc -p phar -o exploit.phar laravel/rce9 system id
./phpggc -pj /tmp/dummy.jpg -o /tmp/z.zip.phar monolog/rce1 system id
The wrapper system adds another layer of sophistication. When you need to adapt payloads to specific application quirks—maybe the vulnerable code base64-encodes before storing, or expects objects wrapped in an array—you can define processing hooks:
// wrapper.php
function process_parameters($parameters) {
// Modify parameters before chain generation
return $parameters;
}
function process_object($object) {
// Wrap the generated object before serialization
return ['data' => $object];
}
Then invoke: ./phpggc -w wrapper.php symfony/rce4 system id. This flexibility transforms PHPGGC from a simple payload generator into a framework for building complex exploits.
What’s particularly useful is the informational flag system. Gadget chains marked with an asterisk (*) in the listings appear to indicate chains with specific requirements or constraints. Running ./phpggc -i on these reveals details that can prevent wasted time testing incompatible payloads against targets.
Gotcha
PHPGGC’s effectiveness hinges entirely on the target using a supported framework in a vulnerable version with the necessary classes loaded. If you’re testing a custom PHP application that doesn’t use Laravel, Symfony, or any of the included frameworks, PHPGGC offers zero value—you’ll need to research and build gadget chains manually. Even when frameworks are present, stripped-down installations missing certain components can break chains. A Laravel app with minimal dependencies might not have the specific classes a gadget requires.
PHP serialization payloads contain NULL bytes, which creates practical exploitation challenges. Many injection points (URL parameters, JSON fields, database columns) either reject or mangle NULL bytes. While PHPGGC supports various encodings (base64, URL encoding with -u, or soft URL encoding with -s), you’re still constrained by how the application handles input before deserialization. The README acknowledges this by offering output format options and warning that “Payloads often contain NULL bytes and cannot be copy/pasted as-is”, but it doesn’t solve the fundamental issue: some vulnerable code paths are simply unreachable with serialized payloads.
Version targeting can be frustratingly imprecise. A chain listed as “5.4.0 <= 8.6.9+” suggests it works across a massive Laravel version range, but the ”+” indicates uncertainty. Framework updates constantly introduce breaking changes in internal classes, and PHPGGC’s gadget chains can silently fail against newer versions. There’s no automated version detection—you need to know what you’re targeting, and testing might reveal your payload is dead on arrival. The tool also requires PHP >= 5.6 to run, which occasionally causes issues when your attack machine has a different PHP version than your target, leading to serialization compatibility problems.
Verdict
Use PHPGGC if you’re conducting penetration tests or bug bounty research on PHP applications built with popular frameworks, you’ve identified an unserialize() vulnerability, and you want to skip the tedious work of gadget chain research. It’s indispensable for security professionals who need to quickly prove impact during time-constrained assessments. Also use it if you’re building automated exploitation frameworks or teaching deserialization attacks—having a library of working examples across frameworks is invaluable for education and tool development. Skip it if you’re dealing with custom PHP applications without standard framework dependencies, need to generate novel gadget chains for research purposes, or are working in environments where pre-built exploitation tools raise compliance concerns. Also skip it for defensive work—if you’re trying to detect deserialization vulnerabilities through static analysis, you need different tooling entirely. PHPGGC is unashamedly an offensive security tool: brilliant at what it does, useless everywhere else.