Drupal 11.4.8 - Élevé - À distance - Téléversement - Le téléversement d'images CKEditor 5 contourne toujours FileIsImage
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-ckeditor-fileisimage
Classe : Upload non restreint / XSS stocké Portée : Distante
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-009 a ajouté FileIsImage sur les champs image de JSON:API et REST. CKEditor5ImageController::getImageUploadValidators ne renvoie toujours que FileExtension, FileSizeLimit et FileImageDimensions. FileImageDimensions ne renvoie aucune violation lorsque le fichier n'est pas une image valide. L'extension du nom de fichier est la vérification restante.
Un utilisateur d'un format de texte avec upload d'image CKEditor 5 peut envoyer en POST des octets non-image nommés x.png vers /ckeditor5/upload-image/{editor} et obtenir un 201 ainsi qu'une URL publique de fichiers.
| ID | pas encore de CVE |
| Classe | Upload non restreint / XSS stocké si snifé |
| Portée | Distante (utilisateur authentifié du format) |
| CWE | CWE-434, CWE-79 |
| CVSS | Moyen : 5.4 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| Produit | Drupal core |
| Affecté | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | utilisateur authentifié d'un format de texte avec upload d'image CKEditor 5 (basic_html / full_html sur Standard) |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Nécessite un compte pouvant utiliser un format dont l'éditeur a image_upload.status à true. Sur Standard, il s'agit de basic_html et full_html. Un parfait inconnu sans compte ne peut pas atteindre cette route (_entity_access: editor.use plus CSRF).
| Privilège | Méthode / sink | Type | Prouvé en lab ou déduit |
|---|---|---|---|
Écrire des octets non-image dans un .png public sous sites/default/files/inline-images/ | POST /ckeditor5/upload-image/{editor} multipart upload | intégrité | Prouvé en lab. x.png a renvoyé 201 et url=/sites/default/files/inline-images/x.png |
| Relire ces octets à l'URL publique des fichiers | GET de l'url JSON | reconnaissance | Prouvé en lab. 200, 49 octets, le corps contenait DRUPAL-CKEDITOR-FILEISIMAGE-WITNESS |
| XSS stocké chez un visiteur si le serveur web ou le navigateur sniffe le fichier comme HTML/JS | Même URL publique, Content-Type non image | intégrité (origine du visiteur) | Déduit. Ce lab n'a pas modifié le MIME Apache. Drupal a stocké les octets. |
| Uploader une vraie image via la même route | Contrôle control.png PNG 1x1 | prévu | Prouvé en lab. 201 |
Fermé sur ce reliquat : PHP/RCE via .php (prouvé en lab 422 FileExtension) ; upload non authentifié ; ImageItem de JSON:API/REST (ces chemins ajoutent déjà FileIsImage) ; LPE ; un shell sur la liste d'extensions d'images autorisées par défaut.
J'ai épinglé Drupal 11.4.8 après la vague SA-CORE de 2026. JSON:API ajoute déjà la contrainte manquante :
if (\is_a($field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE) && !array_key_exists('FileIsImage', $validators)) {
$validators['FileIsImage'] = [];
}
REST FileUploadResource.php fait de même. CKEditor 5 a été laissé sur nom de fichier + taille + dimensions :
return [
'FileExtension' => [
'extensions' => implode(' ', $allowed_extensions),
],
'FileSizeLimit' => [
'fileLimit' => $max_filesize,
],
'FileImageDimensions' => [
'maxDimensions' => $max_dimensions,
],
];
FileImageDimensionsConstraintValidator ne renvoie aucune violation lorsque le fichier n'est pas une image valide. Le hub drupal:11.4 a dérivé vers 11.4.9 ; ce pin est exact au niveau composer drupal/recommended-project:11.4.8. 11.4.0 a retiré Article de Standard, donc l'image applique core/recipes/article_content_type après l'installation afin que /node/add/article charge CKEditor 5.
Fausses pistes déjà consignées : traiter FileImageDimensions comme une vérification MIME (c'est une vérification de dimensions sur une image valide, et un no-op sinon) ; prétendre à un shell PHP sur la liste d'autorisation par défaut (.php est 422) ; utiliser le tag flottant du hub et voir 11.4.9 apparaître. Théâtre : un reverse shell. L'oracle est 201 sur un faux x.png plus 422 sur les mêmes octets nommés x.php.
Reliquats du même pin : SSRF de second saut oEmbed.
python3 drupal-ckeditor-fileisimage-Abraxas-Labs.py
ou
cd lab
./run.sh
Cible uniquement http://127.0.0.1:19500. Drupal 11.4.8 Standard en direct sur php:8.4-apache-trixie, SQLite.
SUCCESS drupal-ckeditor-fileisimage png-http=201 php-http=422 control-png-http=201 editor=basic_html stored-http=200 stored-has-witness=yes url=/sites/default/files/inline-images/x.png DRUPAL-CKEDITOR-FILEISIMAGE-WITNESS
Ajouter 'FileIsImage' => [] à CKEditor5ImageController::getImageUploadValidators. Conserver FileExtension pour que .php reste 422. Les types de média image dans la Media Library devraient utiliser ImageItem plutôt qu'un FileItem générique afin d'hériter de la même contrainte.
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0. Voir LICENSE. Lab en boucle locale uniquement. Aucune garantie.