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
Write-up : https://abraxaslabs.tech/research/drupal-ckeditor-fileisimage
Classe : Téléversement 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 renvoie toujours uniquement FileExtension, FileSizeLimit et FileImageDimensions. FileImageDimensions renvoie sans 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 téléversement d'images 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 | Téléversement non restreint / XSS stocké si reniflé |
| Portée | Distante (utilisateur authentifié d'un 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 téléversement d'images 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 renifle 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 d'Apache. Drupal a stocké les octets. |
| Téléverser une vraie image via la même route | Contrôle control.png PNG 1x1 | prévu | Prouvé en lab. 201 |
Écarté sur ce point restant : PHP/RCE via .php (prouvé en lab 422 FileExtension) ; téléversement non authentifié ; ImageItem 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 retourne sans violation lorsque le fichier n'est pas une image valide. Le hub drupal:11.4 a été flotté vers 11.4.9 ; cette épingle est composer-exact 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 (il s'agit des dimensions sur une image valide, et sans effet sinon) ; prétendre à un shell PHP sur la liste d'autorisation par défaut (.php est 422) ; utiliser le tag de hub flottant 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.
Restes de la même épingle : SSRF au second saut oEmbed.
python3 drupal-ckeditor-fileisimage-Abraxas-Labs.py
ou
cd lab
./run.sh
Ciblez uniquement http://127.0.0.1:19500. Drupal 11.4.8 Standard en production 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
Ajoutez 'FileIsImage' => [] à CKEditor5ImageController::getImageUploadValidators. Conservez FileExtension afin que .php reste en 422. Les types de médias image dans la Media Library devraient utiliser ImageItem plutôt qu'un FileItem générique afin qu'ils héritent de la même contrainte.
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0. Voir LICENSE. Laboratoire en boucle locale uniquement. Aucune garantie.