diff --git a/src/pentesting-web/deserialization/README.md b/src/pentesting-web/deserialization/README.md index d072dffd3f9..9cf3f8d081b 100644 --- a/src/pentesting-web/deserialization/README.md +++ b/src/pentesting-web/deserialization/README.md @@ -119,6 +119,64 @@ $o->param = "PARAM"; $ser=serialize($o); ``` +### Preventing PHP Object Injection with `allowed_classes` + +> [!INFO] +> Support for the **second argument** of `unserialize()` (the `$options` array) was added in **PHP 7.0**. On older versions the function only accepts the serialized string, making it impossible to restrict which classes may be instantiated. + +`unserialize()` will **instantiate every class** it finds inside the serialized stream unless told otherwise. Since PHP 7 the behaviour can be restricted with the [`allowed_classes`](https://www.php.net/manual/en/function.unserialize.php) option: + +```php +// NEVER DO THIS – full object instantiation +$object = unserialize($userControlledData); + +// SAFER – disable object instantiation completely +$object = unserialize($userControlledData, [ + 'allowed_classes' => false // no classes may be created +]); + +// Granular – only allow a strict white-list of models +$object = unserialize($userControlledData, [ + 'allowed_classes' => [MyModel::class, DateTime::class] +]); +``` + +If **`allowed_classes` is omitted _or_ the code runs on PHP < 7.0**, the call becomes **dangerous** as an attacker can craft a payload that abuses magic methods such as `__wakeup()` or `__destruct()` to achieve Remote Code Execution (RCE). + +#### Real-world example: Everest Forms (WordPress) CVE-2025-52709 + +The WordPress plugin **Everest Forms ≤ 3.2.2** tried to be defensive with a helper wrapper but forgot about legacy PHP versions: + +```php +function evf_maybe_unserialize($data, $options = array()) { + if (is_serialized($data)) { + if (version_compare(PHP_VERSION, '7.1.0', '>=')) { + // SAFE branch (PHP ≥ 7.1) + $options = wp_parse_args($options, array('allowed_classes' => false)); + return @unserialize(trim($data), $options); + } + // DANGEROUS branch (PHP < 7.1) + return @unserialize(trim($data)); + } + return $data; +} +``` + +On servers that still ran **PHP ≤ 7.0** this second branch led to a classic **PHP Object Injection** when an administrator opened a malicious form submission. A minimal exploit payload could look like: + +``` +O:8:"SomeClass":1:{s:8:"property";s:28:"";} +``` + +As soon as the admin viewed the entry, the object was instantiated and `SomeClass::__destruct()` got executed, resulting in arbitrary code execution. + +**Take-aways** +1. Always pass `['allowed_classes' => false]` (or a strict white-list) when calling `unserialize()`. +2. Audit defensive wrappers – they often forget about the legacy PHP branches. +3. Upgrading to **PHP ≥ 7.x** alone is *not* sufficient: the option still needs to be supplied explicitly. + +--- + ### PHPGGC (ysoserial for PHP) [**PHPGGC**](https://github.com/ambionics/phpggc) can help you generating payloads to abuse PHP deserializations.\ @@ -663,6 +721,8 @@ The tool [JMET](https://github.com/matthiaskaiser/jmet) was created to **connect ### References +- [Patchstack advisory – Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)](https://patchstack.com/articles/critical-vulnerability-impacting-over-100k-sites-patched-in-everest-forms-plugin/) + - JMET talk: [https://www.youtube.com/watch?v=0h8DWiOWGGA](https://www.youtube.com/watch?v=0h8DWiOWGGA) - Slides: [https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf](https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf)