Zero-Day
10 min read
March 17, 2026

A Recipe for Disaster. Unauthenticated PHP Object Injection in the Cooked Pro Plugin (CVE-2022-3900)

A Recipe for Disaster. Unauthenticated PHP Object Injection in the Cooked Pro Plugin (CVE-2022-3900)

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:

action=cooked_loadmore

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.

CWE-502
9.8Critical
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Exploitability Metrics
Attack Vector (AV)
Network (N)Adjacent (A)Local (L)Physical (P)
Attack Complexity (AC)
Low (L)High (H)
Privileges Required (PR)
None (N)Low (L)High (H)
User Interaction (UI)
None (N)Required (R)
Scope (S)
Unchanged (U)Changed (C)
Impact Metrics
Confidentiality Impact (C)
None (N)Low (L)High (H)
Integrity Impact (I)
None (N)Low (L)High (H)
Availability Impact (A)
None (N)Low (L)High (H)

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:

POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Connection: close action=cooked_loadmore &atts%5Bcategory%5D=false &atts%5Border%5D=false &recipe_args=<SERIALIZED_PHP_OBJECT> &page=1 &is_own_profile=

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)
Vulnerable code
Client (Frontend)recipe_args=O:8:"stdClass":...
POST admin-ajax.php
Backend PHPunserialize($recipe_args)
__wakeup() / __destruct()
ResultRemote Code Execution (RCE)
No authorization + no sanitization = RCE
POP Chain Gadgets

Depending on the classes available in the WordPress environment, the attacker can:

Delete any filee.g. wp-config.php
Read the databaseUser data, passwords
Reset the admin passwordFull takeover of the panel
Remote Code ExecutionFull control over the server
No login required: anyone can send a payload

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.
WordPress-based applications are a powerful ecosystem, and a flaw in a single niche module can bring down an entire infrastructure. Always keep your plugins updated, and if you build them yourself, remember: may JSON be with you!
Marcin Motwicki, CEO of PWNONE
Dangerous
unserialize()

Reconstructs full PHP objects from a string, including classes and magic methods.

// Vulnerable Cooked Pro code
$args = $_POST['recipe_args'];
$recipe = unserialize($args);
// The attacker injects:
O:8:"stdClass":1:{s:4:"exec";s:6:"whoami";}
Objects + magic methods = RCE
Secure
json_decode()

Carries only values and data structure. Ignores executable PHP objects and classes.

// Fixed code
$args = $_POST['recipe_args'];
$recipe = json_decode($args, true);
// JSON carries data only:
{"category":"false","order":"false"}
Data only, no objects &rarr; Safe!

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

1
CVE-2022-3900, NIST National Vulnerability Database

The official NIST NVD entry describing the PHP Object Injection vulnerability in the Cooked Pro plugin, rated CVSS 9.8 (Critical).

2
CWE-502: Deserialization of Untrusted Data

The official MITRE entry describing the class of vulnerabilities based on deserializing untrusted data — exactly the type of flaw exploited in CVE-2022-3900.

3
WPScan. Cooked Pro < 1.7.5.7, PHP Object Injection

The WPScan entry documenting the PHP Object Injection vulnerability in the Cooked Pro plugin, including technical details and the affected versions.

4
OWASP, Insecure Deserialization

The OWASP guide to testing for and preventing Insecure Deserialization attacks, one of the OWASP Top 10 categories.

5
PHP Manual, unserialize()

The official PHP documentation for the unserialize() function, with the security warning: "Do not pass untrusted user input to unserialize()".

Your WordPress plugins may be hiding a ticking bomb.

PHP Object Injection, SQL Injection, XSS — dangerous flaws lurk even in niche plugins. A PWNONE audit scans every layer of your application.

Order a WordPress security audit