CVE-2026-0859

CamelCase Catastrophe: How a Typo in TYPO3 Enabled RCE

Alon Barad
Alon Barad
Software Engineer

Jan 14, 2026·5 min read

Executive Summary (TL;DR)

The TYPO3 developers attempted to secure the mail spooler by whitelisting allowed classes during deserialization. However, they used the configuration key 'allowedClasses' (camelCase) instead of the required 'allowed_classes' (snake_case). PHP silently ignored the invalid key, disabling the whitelist entirely. This allows an attacker with write access to the spool directory to execute arbitrary code via gadget chains.

A critical insecure deserialization vulnerability in TYPO3 CMS caused by a typographical error in the unserialize() options. A local attacker can escalate privileges to RCE by planting malicious serialized objects in the mail spool.

The Hook: The Mailman Always Rings Twice

In the world of enterprise CMS, sending emails synchronously is a performance killer. Nobody wants their checkout page to hang while the server negotiates a TLS handshake with an SMTP relay. Enter the FileSpool transport: a mechanism that serializes email objects to disk, saving them for a background worker to pick up later. It's the digital equivalent of tossing a letter into an outbox to be dealt with eventually.

But here's the catch: that outbox is a trust boundary. When the TYPO3 scheduler wakes up to process the queue (running mailer:spool:send), it has to read those files from disk and bring them back to life. It does this using PHP's native serialization format.

If you've been in security for more than a week, the word 'unserialize' should make your skin crawl. It's the Pandora's Box of PHP. If you open it without checking what's inside, you aren't just reading data; you're instantiating objects, triggering destructors, and waking up code paths that were never meant to be traversed. TYPO3 knew this risk. They tried to lock the door. But thanks to a simple typo, they left the key in the lock.

The Flaw: CamelCase Killed the Cat

The root cause of CVE-2026-0859 is almost tragic in its simplicity. It wasn't a complex logic error or a buffer overflow. It was a casing error. The developer attempted to restrict deserialization to a safe list of mail-related classes (RawMessage, Email, Envelope, etc.).

They passed an options array to PHP's unserialize() function looking like this: ['allowedClasses' => [...]].

Unfortunately, the PHP engine is strictly snake_case for this specific option. It expects allowed_classes. When PHP encounters an option key it doesn't recognize (like the camelCased version), it doesn't throw a warning or an error. It simply ignores it.

Because the security filter was ignored, the behavior defaulted to allowed_classes => true. This means any class available in the TYPO3 codebase (which includes the massive vendor directory containing Symfony, Guzzle, and Doctrine) could be instantiated. The security control was effectively a placebo.

The Code: The Smoking Gun

Let's look at the vulnerable code in FileSpool::flushQueue(). The intention was noble, but the execution was fatal.

// BEFORE (Vulnerable)
// The array key 'allowedClasses' is ignored by PHP.
$message = unserialize($content, [
    'allowedClasses' => [
        RawMessage::class,
        Message::class,
        // ... other safe classes
    ],
]);

The fix provided by the TYPO3 team was twofold. First, they fixed the typo. Second, recognizing that unserialize is a minefield, they implemented a defense-in-depth strategy called the PolymorphicDeserializer. This wrapper doesn't just trust PHP; it regex-scans the raw payload string before deserialization to validate class names.

// AFTER (Patched)
// 1. Regex validation of class names in the string payload
$classNames = $this->parseClassNames($payload);
foreach ($classNames as $className) {
    if (!$this->isInstanceOf($className, $allowedClasses)) {
        throw new Exception('Invalid class name found');
    }
}
 
// 2. The key is corrected to snake_case
return @unserialize($payload, ['allowed_classes' => $allowedClasses]);

[!NOTE] The patch also includes an isInsideString check to prevent attackers from hiding malicious class names inside string literals to bypass the regex scanner.

The Exploit: Building the Bomb

To exploit this, we need a standard PHP Property-Oriented Programming (POP) chain. TYPO3 is built on top of Symfony components and bundles massive libraries like Guzzle. This means gadget chains (like Symfony/RCE4 or generic Guzzle destructors) are almost certainly available.

The attack flow is straightforward but requires local access:

  1. Generate Payload: Use a tool like PHPGGC to generate a serialized payload that executes system('id') via a __destruct or __toString gadget.
  2. Planting: The attacker writes this payload to a file in the spool directory (e.g., var/spool/). The filename usually needs to match a specific pattern or extension .message, depending on the exact spool configuration, though often any file in the directory is processed.
  3. The Trigger: We wait. The TYPO3 scheduler runs periodically (usually every minute via cron). It executes mailer:spool:send.
  4. Detonation: The scheduler reads our file, passes it to the vulnerable unserialize(), and boom—the gadget chain executes in the context of the web server user.

While the CVSS score is suppressed to 5.2 due to the Local requirement, do not underestimate this. In shared hosting environments, or scenarios where an attacker has achieved a low-privilege foothold (e.g., via an unrestricted file upload that can't execute PHP but can write to var/), this is an instant privilege escalation to full RCE.

The Fix: Strict Typing & Regex

The mitigation is mandatory: Upgrade immediately. The fix is present in TYPO3 versions 10.4.55 LTS, 11.5.49 LTS, 12.4.41 LTS, 13.4.23 LTS, and 14.0.2.

If you cannot upgrade, you have two options:

  1. Change the Transport: Switch your mail transport from file to smtp or sendmail in LocalConfiguration.php. This bypasses the vulnerability entirely by avoiding the serialization-to-disk mechanism.
  2. Filesystem ACLs: Lock down the var/spool directory. Ensure that only the system user responsible for the web server and the cron job can write to it. If a compromised FTP account or a compromised plugin can write to this directory, you are vulnerable.

This vulnerability serves as a stark reminder: relying on dynamic languages' loose typing and silent failure modes is dangerous. Always verify your configuration keys against the official documentation, or better yet, use a linter that catches unknown array keys.

Fix Analysis (2)

Technical Appendix

CVSS Score
5.2/ 10
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:H/SI:H/SA:H
EPSS Probability
0.04%
Top 86% most exploited

Affected Systems

TYPO3 CMS 14.0.0 - 14.0.1TYPO3 CMS 13.0.0 - 13.4.22TYPO3 CMS 12.0.0 - 12.4.40TYPO3 CMS 11.0.0 - 11.5.48TYPO3 CMS 10.0.0 - 10.4.54

Affected Versions Detail

Product
Affected Versions
Fixed Version
TYPO3 CMS v14
TYPO3
14.0.0 - 14.0.114.0.2
TYPO3 CMS v13
TYPO3
13.0.0 - 13.4.2213.4.23
TYPO3 CMS v12
TYPO3
12.0.0 - 12.4.4012.4.41
AttributeDetail
CWE IDCWE-502
Attack VectorLocal (File Write)
CVSS5.2 (Medium)
ImpactRemote Code Execution (RCE)
Configurationtransport_spool_type = file
Exploit StatusPoC Available
CWE-502
Deserialization of Untrusted Data

The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.

Vulnerability Timeline

Vulnerability Disclosed & Patched
2026-01-13
CVE Assigned
2026-01-13

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.