Feb 20, 2026·6 min read·75 visits
TYPO3 developers made a typo in the `unserialize()` options, writing `allowedClasses` (camelCase) instead of `allowed_classes` (snake_case). PHP silently ignored the restriction, turning a hardened deserialization call into an open door for RCE. Attackers with local file write access to the mail spool can execute arbitrary code.
A critical insecure deserialization vulnerability in TYPO3 CMS caused by a simple syntax error. Developers attempted to secure PHP's `unserialize` function using an allow-list, but used the wrong configuration key ('allowedClasses' instead of 'allowed_classes'). This typo caused PHP to silently ignore the security restriction, allowing full object instantiation and Remote Code Execution (RCE) via the Mailer component.
In the world of secure coding, intent means nothing. Implementation is everything. The developers behind TYPO3, one of the enterprise world's favorite Content Management Systems, intended to write secure code. They knew that PHP's unserialize() function is a loaded gun pointed at the foot of any application that uses it. They tried to use the safety mechanism introduced way back in PHP 7.0: the allowed_classes filter.
But they made one fatal mistake: they respected the variable naming conventions of their own codebase (camelCase) rather than the snake_case requirements of the PHP interpreter. They wrote 'allowedClasses' instead of 'allowed_classes'.
This is the story of CVE-2026-0859, a vulnerability that proves that in PHP, a single capital letter can be the difference between a rejected packet and a root shell. It highlights a terrifying behavior in PHP: when you pass an invalid option to a security function, it often doesn't throw an error—it just silently ignores you and proceeds as if you hadn't tried to secure it at all.
To understand this vulnerability, you have to appreciate the awkward evolution of PHP object injection. For years, unserialize() was simply unsafe. Then, PHP 7 introduced a chaotic-good feature: the options array. This allows developers to pass a whitelist of classes that are allowed to be instantiated. If an attacker tries to inject a GuzzleHttp\Client object to trigger a destructor gadget, but the whitelist only allows stdClass, the exploit fails.
Here is where the logic flaw occurred. The TYPO3 FileSpool class manages queued emails by saving them to disk and reading them back later. To read them back, it uses unserialize. The developers passed an array of safe mail-related classes (like RawMessage, Email, etc.) to the function.
However, because they used the key allowedClasses (notice the capital 'C'), PHP's internal engine looked at the options array, didn't see the expected allowed_classes key, and shrugged. It defaulted to allowed_classes => true, which means "allow everything." The filter was effectively nonexistent. The code looked secure during code review, but it was functionally wide open.
Let's look at the diff. It is rare to see a vulnerability so starkly defined by a syntax error. This isn't a complex logic race condition; it's a typo.
The Vulnerable Code (Simplified):
// TYPO3/CMS/Core/Mail/FileSpool.php
$message = unserialize((string)file_get_contents($file), [
// The fatal mistake: CamelCase key is ignored by PHP
'allowedClasses' => [
RawMessage::class,
Message::class,
Email::class,
// ... list of safe classes
],
]);The Patch (Commit 3225d705...):
In the fix, the developers didn't just correct the typo; they completely overhauled the deserialization logic. They realized that trusting unserialize with a simple list might be fragile given the complexity of Symfony Mailer objects. They introduced a PolymorphicDeserializer that pre-scans the string.
// The Fix: Using a wrapper and correct keys
$message = $this->deserializer->deserialize(
(string)file_get_contents($file),
[
SentMessage::class,
RawMessage::class,
// ...
]
);Internally, that deserializer ensures the payload doesn't contain dangerous object markers before even letting PHP touch it. They went from "oops, typo" to "defense-in-depth."
So, we have an insecure deserialization sink. How do we reach it? This vulnerability is classified as AV:L (Local) because the input vector comes from a file on disk: the mail spool.
Step 1: The Gadget Chain
TYPO3 is built on top of Symfony components and includes a massive amount of dependencies (Guzzle, Doctrine, etc.). This is a playground for tools like PHPGGC. An attacker would generate a payload using a standard chain, for example, Monolog/RCE1 or a generic Symfony/RCE chain, wrapping a command like id or a reverse shell.
Step 2: The Delivery
The attacker needs to write this payload to the configured transport_spool_filepath. If the attacker has compromised a low-privileged account (e.g., via an unrestricted file upload or a separate LFI), they can drop a file named payload.sending into the spool directory.
Step 3: The Trigger
The beauty (and horror) of this exploit is that the attacker doesn't need to trigger it manually. TYPO3 relies on a scheduler (cron job) to flush the mail queue. The attacker plants the bomb and walks away. When the system administrator's cron job runs typo3 mailer:spool:send, the script iterates over the directory, reads payload.sending, and deserializes it. Boom—code execution running as the user invoking the scheduler (often the web user or even root/cron user).
While the requirement for local file write lowers the CVSS score to a 7.8, the impact is undeniably critical. This acts as a privilege escalation or persistence mechanism. If an attacker can write to the spool but cannot execute PHP directly (perhaps due to disable_functions or WAF rules blocking direct execution), this vulnerability bypasses those restrictions.
The deserialization happens deep within the core framework logic, often outside the context of the web request, meaning it might bypass web-based intrusion detection systems (WAFs) entirely. Furthermore, because it executes during the scheduler run, it could potentially escalate privileges if the scheduler is run by a user with higher permissions than the web server itself.
The remediation is straightforward: Update. The patch versions (10.4.55, 11.5.49, 12.4.41, 13.4.23, 14.0.2) contain the fix. The fix is robust; it moves away from the fragile unserialize allow-list and implements a rigorous check on the serialized string content before processing.
Defensive Strategy:
transport_spool_type = memory) or use a proper SMTP server without local spooling. This removes the attack surface entirely.var/spool or fileadmin directories are not writable by untrusted users. If an attacker can't write the file, they can't trigger the deserialization.CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
TYPO3 CMS TYPO3 | >= 10.0.0, <= 10.4.54 | 10.4.55 ELTS |
TYPO3 CMS TYPO3 | >= 11.0.0, <= 11.5.48 | 11.5.49 ELTS |
TYPO3 CMS TYPO3 | >= 12.0.0, <= 12.4.40 | 12.4.41 LTS |
TYPO3 CMS TYPO3 | >= 13.0.0, <= 13.4.22 | 13.4.23 LTS |
TYPO3 CMS TYPO3 | >= 14.0.0, <= 14.0.1 | 14.0.2 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Insecure Deserialization (CWE-502) |
| CVSS v3.1 | 7.8 (High) |
| Attack Vector | Local (File Write required) |
| Root Cause | Typo in API option key ('allowedClasses' vs 'allowed_classes') |
| EPSS Score | 0.03% (Low probability of mass exploitation) |
| Patch Status | Released (2026-01-13) |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.