CodeIgniter4 has a validation bypass when uploading file extensions via `ext_in` rule
Description
CodeIgniter4 ext_in validation rule bypass allows arbitrary code execution via uploaded file with mismatched extension.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CodeIgniter4 ext_in validation rule bypass allows arbitrary code execution via uploaded file with mismatched extension.
Vulnerability
The ext_in validation rule in CodeIgniter4 versions prior to 4.7.3 only checks the MIME-derived guessed extension instead of the client-provided filename extension [1]. This allows a file named shell.php containing GIF-like content to pass validation rules such as ext_in[avatar,gif] because guessExtension() returns gif. The vulnerability is present in the ext_in rule implementation in system/Validation/StrictRules [4].
Exploitation
An attacker needs network access to an upload endpoint that uses ext_in for validation, saves files using the original client filename (e.g., $file->move($path)), stores uploads in a web-accessible directory, and allows execution of PHP (or other executable) files [2]. The attacker uploads a file with a dangerous extension (e.g., shell.php) but with content that triggers a safe MIME type (e.g., GIF). The ext_in rule passes because it only checks the guessed extension, and the file is saved with the original .php extension [3].
Impact
Successful exploitation leads to arbitrary code execution with the privileges of the web server. The attacker can execute arbitrary commands, access or modify sensitive data, and potentially gain further access to the server [3]. The default application does not expose such an upload endpoint, but custom implementations are vulnerable.
Mitigation
Upgrade to CodeIgniter4 version 4.7.3 or later, released on 2026-05-22, which fixes the ext_in rule to validate both the client extension and ensure it matches the guessed extension [3]. If upgrading is not immediately possible, workarounds include saving uploads outside the public web root, using $file->store() or $file->move($path, $file->getRandomName()) to avoid original filenames, disabling script execution in public upload directories, manually checking $file->getClientExtension(), and rejecting files where it does not match $file->guessExtension() [1][2].
AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: < 4.7.2
Patches
129299349e7d2Merge commit from fork
5 files changed · +119 −3
system/Validation/StrictRules/FileRules.php+8 −1 modified@@ -211,7 +211,14 @@ public function ext_in(?string $blank, string $params): bool return true; } - if (! in_array($file->guessExtension(), $params, true)) { + // Check the real filename extension, not only the guessed extension. + $clientExtension = strtolower($file->getClientExtension()); + + if ($clientExtension === '' || ! in_array($clientExtension, $params, true)) { + return false; + } + + if ($file->guessExtension() !== $clientExtension) { return false; } }
tests/system/Validation/StrictRules/FileRulesTest.php+82 −0 modified@@ -316,6 +316,20 @@ public function testExtensionOk(): void $this->assertTrue($this->validation->run([])); } + public function testExtensionOkWithMatchingClientExtensionAndMimeType(): void + { + $payload = $this->createGifPayload(); + + try { + $this->setUploadedAvatar($payload, 'my-avatar.gif'); + + $this->validation->setRules(['avatar' => 'ext_in[avatar,gif]']); + $this->assertTrue($this->validation->run([])); + } finally { + unlink($payload); + } + } + public function testExtensionNotOk(): void { $this->validation->setRules(['avatar' => 'ext_in[avatar,xls,doc,ppt]']); @@ -327,4 +341,72 @@ public function testExtensionImpossible(): void $this->validation->setRules(['avatar' => 'ext_in[unknown,xls,doc,ppt]']); $this->assertFalse($this->validation->run([])); } + + public function testExtensionFailsForMismatchedClientExtension(): void + { + $payload = $this->createGifPayload(); + + try { + $this->setUploadedAvatar($payload, 'shell.php'); + + $this->validation->setRules(['avatar' => 'ext_in[avatar,gif]']); + $this->assertFalse($this->validation->run([])); + } finally { + unlink($payload); + } + } + + public function testExtensionFailsForAllowedButMimeIncompatibleClientExtension(): void + { + $payload = $this->createGifPayload(); + + try { + $this->setUploadedAvatar($payload, 'my-avatar.jpg'); + + $this->validation->setRules(['avatar' => 'ext_in[avatar,jpg,gif]']); + $this->assertFalse($this->validation->run([])); + } finally { + unlink($payload); + } + } + + public function testExtensionFailsForExtensionlessClientFilename(): void + { + $payload = $this->createGifPayload(); + + try { + $this->setUploadedAvatar($payload, 'my-avatar'); + + $this->validation->setRules(['avatar' => 'ext_in[avatar,gif]']); + $this->assertFalse($this->validation->run([])); + } finally { + unlink($payload); + } + } + + private function createGifPayload(): string + { + $payload = tempnam(sys_get_temp_dir(), 'ci4-upload-poc-'); + $this->assertIsString($payload); + + $gif = base64_decode('R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', true); + $this->assertIsString($gif); + + file_put_contents($payload, $gif); + + return $payload; + } + + private function setUploadedAvatar(string $payload, string $name): void + { + service('superglobals')->setFilesArray([ + 'avatar' => [ + 'tmp_name' => $payload, + 'name' => $name, + 'size' => filesize($payload), + 'type' => 'image/gif', + 'error' => UPLOAD_ERR_OK, + ], + ]); + } }
user_guide_src/source/changelogs/v4.7.3.rst+11 −0 modified@@ -10,6 +10,17 @@ Release Date: Unreleased :local: :depth: 3 +******** +SECURITY +******** + +- **Validation:** The ``ext_in`` file upload validation rule now validates the + client filename extension and verifies that it matches the detected MIME type. + Previously, ``ext_in`` only checked the MIME-derived guessed extension, so a + file with a mismatched client extension could pass validation. + See the `Security advisory GHSA-2gr4-ppc7-7mhx <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-2gr4-ppc7-7mhx>`_ + for more information. + ******** BREAKING ********
user_guide_src/source/installation/upgrade_473.rst+14 −0 modified@@ -30,6 +30,20 @@ upgrading. The easiest way is to re-run the install command: Breaking Changes **************** +File Validation +=============== + +The ``ext_in`` file upload validation rule now checks the client filename +extension and verifies that the detected MIME type is associated with that +extension. Previously, ``ext_in`` only checked the MIME-derived guessed +extension. + +This means files with no client filename extension, or files whose client +filename extension does not match the detected MIME type, now fail ``ext_in`` +validation. If your application intentionally accepts such files, remove +``ext_in`` from those validation rules and use a custom validation rule that +matches your application's requirements. + ********************* Breaking Enhancements *********************
user_guide_src/source/libraries/validation.rst+4 −2 modified@@ -1108,8 +1108,10 @@ min_dims Yes Fails if the minimum width and height of an v4.6.0.) mime_in Yes Fails if the file's mime type is not one ``mime_in[field_name,image/png,image/jpeg]`` listed in the parameters. -ext_in Yes Fails if the file's extension is not one ``ext_in[field_name,png,jpg,gif]`` - listed in the parameters. +ext_in Yes Fails if the client filename's extension is ``ext_in[field_name,png,jpg,gif]`` + not one listed in the parameters, or if the + detected MIME type does not match the MIME + types associated with the extension. is_image Yes Fails if the file cannot be determined to be ``is_image[field_name]`` an image based on the mime type. ======================= ========== ============================================= ===================================================
Vulnerability mechanics
Root cause
"The `ext_in` upload validation rule checked the MIME-derived guessed extension instead of the client-provided filename extension, allowing a file with a dangerous extension to pass validation."
Attack vector
An attacker uploads a file with a dangerous extension (e.g., `shell.php`) whose content matches an allowed MIME type (e.g., `image/gif`). The `ext_in` rule previously checked only the MIME-derived guessed extension (`gif`), so the file passed validation. If the application saves the file using the original client filename (`$file->move($path)`) in a web-accessible directory where PHP execution is enabled, the attacker can access the uploaded file and achieve arbitrary code execution [ref_id=1]. The advisory explicitly states this scenario leads to "arbitrary code execution" [ref_id=1].
Affected code
The `ext_in` method in `system/Validation/StrictRules/FileRules.php` (and the equivalent in `system/Validation/FileRules.php`) is at fault. Previously, the method only checked `$file->guessExtension()` (the MIME-derived extension) and ignored the client-provided filename extension entirely [patch_id=5618905].
What the fix does
The patch adds two checks in `ext_in()`: first, it retrieves the client filename extension via `$file->getClientExtension()` and rejects the file if that extension is empty or not in the allowed list; second, it verifies that `$file->guessExtension()` matches the client extension [patch_id=5618905]. This closes the gap because a file named `shell.php` with GIF content will have a client extension of `php` (not in the allowed list) and a guessed extension of `gif` (which does not match `php`), so validation fails. The upgrade notes and changelog confirm this behavioral change [patch_id=5618905].
Preconditions
- configApplication must accept user-controlled file uploads
- configApplication must use ext_in validation rule to validate the uploaded filename extension
- configApplication must save uploaded files using the original client filename via $file->move($path)
- configUpload directory must be web-accessible and allow execution of PHP (or other executable) files
Generated on Jun 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-2gr4-ppc7-7mhxghsaADVISORY
- codeigniter.com/user_guide/libraries/uploaded_files.htmlghsa
- codeigniter.com/user_guide/libraries/validation.htmlghsa
- github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.mdghsa
- github.com/codeigniter4/CodeIgniter4/commit/29299349e7d232e9532767c7cefaed30957309beghsa
- github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-2gr4-ppc7-7mhxghsa
News mentions
0No linked articles in our index yet.