Sep 15, 2026·6 min read·57 visits
October CMS unserialized widget session states without class restrictions, enabling PHP Object Injection and sandbox escapes in sandboxed safe-mode deployments.
A security vulnerability in October Content Management System (CMS) involves the deserialization of untrusted data (CWE-502) within the backend SessionMaker trait. Prior to the patched versions, October CMS stored widget session states as base64-encoded serialized PHP objects. When loading these states, the application consumed them using unserialize() without enforcing class restrictions (allowed_classes). In configurations where cms.safe_mode is enabled to sandbox users with markup editor privileges, an attacker can exploit this behavior to instantiate arbitrary PHP classes and execute arbitrary code via accessible gadget chains.
The October CMS platform uses a core trait named Backend\Traits\SessionMaker to manage the state of backend widgets. This trait persists user interface configurations, such as active search parameters, sort orientations, selected record identifiers, and filter state values, across distinct HTTP requests. In standard production environments, backend access is restricted to highly trusted administrators who natively possess privileges to execute arbitrary PHP code through the CMS markup editor.
However, in environments where the CMS is configured for multi-tenancy, shared editing, or public demonstration platforms, administrators frequently enable the cms.safe_mode configuration. This safety boundary is specifically designed to isolate and sandbox backend administrative actions, preventing users of the CMS markup editor from executing arbitrary PHP code on the hosting server. Under safe mode, the markup editor is strictly constrained, establishing a security boundary between CMS layout customizers and the lower-level system runtime.
The inclusion of an unrestricted deserialization sink inside the SessionMaker trait creates a path for bypassing this sandbox boundary. Because administrative users with markup editing privileges can interact with and influence session storage paths, they can introduce serialized objects into the backend. When the application automatically processes these objects, the safety sandbox is bypassed, leading to execution of code at the level of the underlying operating system process.
The fundamental flaw resides in the use of PHP native unserialize() without specifying safety boundaries. In PHP applications, the unserialize() function processes a string representation of a serialized variable and converts it back into a PHP value. If the string represents an object, PHP attempts to instantiate that specific class and execute associated lifecycle hooks, commonly referred to as magic methods.
Prior to the patch, the unsession read operations within Backend\Traits\SessionMaker executed unserialize(base64_decode($value)) directly on data extracted from the application session cache. Because the second parameter of unserialize() was omitted, PHP defaulted to allowing all classes ('allowed_classes' => true). This default behavior permits the creation of any object currently declared within the execution scope of the PHP interpreter, including classes loaded via Composer dependencies.
To exploit this behavior, an attacker must construct a PHP gadget chain. A gadget chain is an ordered sequence of method calls across existing codebase classes where the execution of one magic method (such as __wakeup(), __destruct(), __toString(), or __call()) flows into other methods, ultimately terminating in a dangerous sink such as eval(), system(), or file write operations. Because October CMS is built on top of the Laravel framework and incorporates various third-party packages, numerous potential gadget chains exist within the application classpath.
Analyzing the implementation of Backend\Traits\SessionMaker reveals the technical difference between the vulnerable and patched states. The vulnerable version uses serialize() and unserialize() without restriction.
// Vulnerable Implementation
trait SessionMaker
{
public function putSession($key, $value)
{
// Objects are serialized and base64 encoded into session storage
$serializedValue = base64_encode(serialize($value));
Session::put($this->getSessionIndex() . '.' . $key, $serializedValue);
}
public function getSession($key, $default = null)
{
$sessionKey = $this->getSessionIndex() . '.' . $key;
if (!Session::has($sessionKey)) {
return $default;
}
$value = Session::get($sessionKey);
// Unsafe deserialization without limiting allowed classes
return unserialize(base64_decode($value));
}
}The remediation eliminates PHP object injection by migrating the underlying persistence format to JSON. Since JSON parsing does not instantiate classes or trigger PHP magic methods, the object injection sink is closed. For backwards compatibility during the upgrade cycle, a fallback reader exists but restricts class instantiation by setting the allowed_classes option to false.
// Patched Implementation
trait SessionMaker
{
public function putSession($key, $value)
{
// Safe serialization using JSON instead of PHP serialize
$jsonValue = json_encode($value);
Session::put($this->getSessionIndex() . '.' . $key, $jsonValue);
}
public function getSession($key, $default = null)
{
$sessionKey = $this->getSessionIndex() . '.' . $key;
if (!Session::has($sessionKey)) {
return $default;
}
$value = Session::get($sessionKey);
// Attempt to decode using the safe JSON format
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
// Secure fallback for legacy base64-serialized data
$decodedBase64 = base64_decode($value, true);
if ($decodedBase64 !== false) {
// Hardened: class instantiation is strictly disabled
return @unserialize($decodedBase64, ['allowed_classes' => false]);
}
return $default;
}
}To successfully exploit CVE-2026-49400, an attacker must overcome major operational hurdles and satisfy specific architectural constraints. First, the application must run with cms.safe_mode enabled. If safe mode is disabled, the vulnerability is functionally redundant because an authenticated backend administrator can already execute arbitrary PHP code directly via the template markup editor.
Second, the attacker must find a code path that allows writing arbitrary, untransformed data into a session key prefixed with widget.. Most standard October CMS widget implementations wrapper user inputs (such as search terms or pagination indexes) inside structured PHP array definitions before persistence. For example, if user input is stored as ['search' => $userInput], the outer structure remains an array rather than an arbitrary object, rendering direct object injection ineffective.
Consequently, exploitation requires identifying a specific component or API endpoint that accepts raw, non-wrapped input strings and writes them directly to the widget session storage. Once such a write vector is found, the attacker generates a target payload using a gadget generator tool, encodes it to base64, and injects it into the target session variable. The payload triggers when the widget state is read during the subsequent page load.
The impact of this vulnerability depends heavily on system configuration. Under default installations where cms.safe_mode is disabled, the risk is minimal. Administrative users already possess system-level privileges within the design editor, meaning the vulnerability does not grant any privileges beyond what is already authorized.
In sandboxed or multi-tenant deployments where safe mode is active, the impact is significant. It represents a complete sandbox escape, elevating a constrained backend administrator to an unconstrained system user capable of executing arbitrary operating system commands. This breaks the tenant isolation guarantees of the application.
The CVSS v3.1 base score is calculated as 3.3 (Low Severity) with the vector CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N. The low severity score reflects the high attack complexity, the requirement for administrative privileges, and the narrow scope of typical safe-mode configurations.
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
October CMS October CMS | < 3.7.17 | 3.7.17 |
October CMS October CMS | >= 4.0.0, < 4.2.23 | 4.2.23 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502: Deserialization of Untrusted Data |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 3.3 |
| Exploit Status | none |
| KEV Status | Not Listed |
| Impact | Sandbox Escape / Remote Code Execution |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid, or without restricting the types of objects that can be created.
CVE-2026-62290 is an incorrect authorization vulnerability (CWE-863) in cert-manager that allows low-privileged Kubernetes namespace tenants to bypass ClusterIssuer DNS01 solver policies and exfiltrate cluster-scoped DNS credentials. By directly creating standalone ACME Challenge custom resources rather than going through the standard Certificate and Order workflow, an attacker can specify arbitrary solver provider configurations while referencing a privileged ClusterIssuer. The cert-manager ACME challenge controller reconciles these directly instantiated Challenge resources without verifying their parentage or validating the user-supplied solver parameters against the referenced ClusterIssuer's selector policies.
CVE-2026-118662 (also tracked as GHSA-8g5g-mxqh-grgj) is an improper access control and path canonicalization vulnerability in the Vite frontend build and development tool. In version 6.4.4, the Vite development server fails to strip transform and loader query parameters before evaluating file paths against the `server.fs.deny` configuration directive. When the development server is exposed to a network and sensitive files located within `server.fs.allow` match patterns in `server.fs.deny`, an unauthenticated remote attacker can bypass file access restrictions by appending specific query strings such as `?raw` or `?inline` to HTTP requests. This allows arbitrary retrieval of restricted files, including environment configuration files and private cryptographic keys.
A vulnerability in the Rust `zip` crate (versions 1.3.0 prior to 9.0.0) allows an attacker to trigger an uncatchable process abort via a crafted ZIP archive. When extracting symbolic link entries on Unix or Windows targets, `ZipArchive::extract` passes the untrusted, attacker-controlled `uncompressed_size` header value directly to `Vec::with_capacity` before reading or validating entry payload data. By supplying a 112-byte archive with a ZIP64 extended information field declaring an excessive uncompressed size, an attacker forces the memory allocator to fail and invoke `handle_alloc_error()`, immediately terminating the entire process with `SIGABRT`.
CVE-2026-115906 is a cryptographic authentication bypass vulnerability in the Rust zip library (zip-rs/zip2) affecting versions from 0.6.0 prior to 9.0.0. In the WinZip AE-x AES decryption reader implementation within src/aes.rs, the HMAC-SHA1-80 authentication tag is only validated when the internal byte counter (data_remaining) decrements to exactly zero. Because this counter is initialized from the unauthenticated compressed_size header field, an attacker can craft a ZIP archive where the entry stream hits premature physical end-of-file (EOF) before data_remaining reaches zero, causing the reader to return Ok(0) and skip HMAC verification entirely.
CVE-2026-115908 (GHSA-hjqx-j4rw-g7xv) is a high-severity path traversal and link-following vulnerability in the Rust zip library (zip-rs/zip2) affecting versions >= 7.2.0 and < 9.0.0. Because ZipArchive::extract and ZipArchive::extract_unwrapped_root_dir validate symbolic link targets solely via lexical prefix comparisons rather than resolving intermediate filesystem links, an attacker who knows or can guess the absolute extraction path can construct multi-hop symlink chains that escape the destination directory, enabling arbitrary file creation and overwrite with the privileges of the extracting process.
CVE-2026-115907 is an uncontrolled resource consumption vulnerability in the Rust zip crate (versions 5.1.0 through 8.6.0, and 9.0.0 pre-releases) when compiled with the optional legacy-zip feature flag. The vulnerability resides in the legacy decompression decoders—Shrink, Reduce, and Implode—which directly pass an archive-declared 64-bit uncompressed size from a ZIP64 extended information extra field to Vec::reserve() before validating the compressed data stream. By supplying a malformed ZIP archive with a declared uncompressed size exceeding isize::MAX (such as u64::MAX), an unauthenticated attacker can trigger an unhandled capacity overflow panic in the Rust standard library allocator, terminating the calling thread or aborting the entire process.