Have you ever wondered what can go wrong when you combine a recipe plugin with one of the most dangerous and outdated functions in PHP? You can cook up quite a mess.
During my security research on popular WordPress extensions, I came across a classic PHP Object Injection (Insecure Deserialization) flaw in the Cooked Pro plugin. My report led to the assignment of the official CVE-2022-3900 (it was catalogued by WPScan and the Wiz.io database, among others). On the NVD platform the flaw received an unforgiving score of 9.8 (Critical), though some databases rate it a solid 7.4 (High).
Let’s look at why blindly trusting user input and running it through unserialize() is the perfect recipe for handing your server over to an attacker. And all without needing to log in!
TL;DR - Key takeaways
- What Went Wrong? (Endpoints and Assumptions)
- Proof of Concept
- How to Avoid Such Slip-Ups? (Remediation)
What Went Wrong? (Endpoints and Assumptions)
The Cooked Pro plugin (vulnerable in versions older than 1.7.5.7) offers a very convenient feature: it lets you load additional recipes on the page asynchronously (often implemented with a "Load More" button).
The request from the frontend goes straight to the AJAX handler that is standard across the WordPress ecosystem: /wp-admin/admin-ajax.php, where the following action is ultimately invoked:
Among the submitted parameters was one very interesting element: recipe_args. The plugin’s developers had the rather unfortunate idea of sending the recipe arguments from the client side as a serialized (packed) PHP object.
The backend took the recipe_args parameter straight from the request and fed it directly into the native unserialize() function. No authorization? No input sanitization? Check. The flaw catalogued as CWE-502 (Deserialization of Untrusted Data) was wide open.
Why is unserialize() dangerous?
PHP’s unserialize() function reconstructs full PHP objects from a string, including their class and properties. If an attacker controls the input, they can create an arbitrary object from any class available in the application’s memory and trigger its magic methods (__wakeup, __destruct), leading to arbitrary code execution.
Proof of Concept
To exploit this flaw, we did not need any account in the WordPress system. The attacker simply had to send a suitably crafted POST request to the server:
The magic happens in the spot I labeled <SERIALIZED_PHP_OBJECT>. When PHP deserializes objects, it automatically tries to call their so-called "magic methods" (e.g. __wakeup() or __destruct()).
If an attacker places a malicious payload (a so-called POP chain — Property Oriented Programming) into recipe_args, containing classes available in the WordPress environment (or from other active plugins/themes), they can force the server to perform dangerous operations:
- Deleting any file on the server (e.g. wp-config.php)
- Reading the database and stealing user data
- Resetting the administrator password
- Ultimately: full Remote Code Execution (RCE)
recipe_args=O:8:"stdClass":...unserialize($recipe_args)Depending on the classes available in the WordPress environment, the attacker can:
How to Avoid Such Slip-Ups? (Remediation)
Insecure Deserialization flaws have long ranked high in the OWASP Top 10, and in old PHP applications and plugins they are practically a plague. The recipe for secure code has just two ingredients:
- Never trust unserialize(): The golden, inviolable rule of PHP programming. The unserialize() function should never accept data coming from a user, from cookies, or from GET/POST parameters.
- Switch to JSON: If you must transfer complex structures (such as multidimensional arrays or arguments, like the recipe_args above) between client and server, always use JSON. Using the native and safe json_encode() and json_decode() functions eliminates this risk entirely.
Reconstructs full PHP objects from a string, including classes and magic methods.
Carries only values and data structure. Ignores executable PHP objects and classes.
Summary
CVE-2022-3900 is a classic example of unauthenticated PHP Object Injection through the dangerous unserialize() function. The Cooked Pro plugin for WordPress allowed any internet user to inject malicious PHP objects via the recipe_args parameter in an AJAX request. In the worst case, an attacker could achieve full Remote Code Execution on the server. The fix is simple: replace unserialize() with json_decode() and validate the input.
Bibliography
The official NIST NVD entry describing the PHP Object Injection vulnerability in the Cooked Pro plugin, rated CVSS 9.8 (Critical).
The official MITRE entry describing the class of vulnerabilities based on deserializing untrusted data — exactly the type of flaw exploited in CVE-2022-3900.
The WPScan entry documenting the PHP Object Injection vulnerability in the Cooked Pro plugin, including technical details and the affected versions.
The OWASP guide to testing for and preventing Insecure Deserialization attacks, one of the OWASP Top 10 categories.
The official PHP documentation for the unserialize() function, with the security warning: "Do not pass untrusted user input to unserialize()".

