Drupal 11.4.8 - Alta - Remota - Subida - La carga de imágenes de CKEditor 5 todavía omite FileIsImage
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-ckeditor-fileisimage
Análisis: https://abraxaslabs.tech/research/drupal-ckeditor-fileisimage
Clase: Carga sin restricciones / XSS almacenado Alcance: Remoto
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-009 añadió FileIsImage en los campos de imagen de JSON:API y REST. CKEditor5ImageController::getImageUploadValidators sigue devolviendo únicamente FileExtension, FileSizeLimit y FileImageDimensions. FileImageDimensions retorna sin infracción cuando el archivo no es una imagen válida. La extensión del nombre de archivo es la comprobación restante.
Un usuario de un formato de texto con carga de imágenes de CKEditor 5 puede enviar por POST bytes que no son imagen con el nombre x.png a /ckeditor5/upload-image/{editor} y obtener un 201 más una URL pública de archivos.
| ID | aún sin CVE |
| Clase | Carga sin restricciones / XSS almacenado si se detecta por sniffing |
| Alcance | Remoto (usuario autenticado de un formato) |
| CWE | CWE-434, CWE-79 |
| CVSS | Medio: 5.4 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| Producto | Drupal core |
| Afectado | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Autenticación | usuario autenticado de un formato de texto con carga de imágenes de CKEditor 5 (basic_html / full_html en Standard) |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Necesita una cuenta que pueda usar un formato cuyo editor tenga image_upload.status en true. En Standard eso es basic_html y full_html. Un completo desconocido sin cuenta no puede alcanzar esta ruta (_entity_access: editor.use más CSRF).
| Privilegio | Método / sink | Tipo | Demostrado en laboratorio o inferido |
|---|---|---|---|
Escribir bytes que no son imagen en un .png público bajo sites/default/files/inline-images/ | POST /ckeditor5/upload-image/{editor} multipart upload | integridad | Demostrado en laboratorio. x.png devolvió 201 y url=/sites/default/files/inline-images/x.png |
| Leer esos bytes de vuelta en la URL pública de archivos | GET de la url del JSON | reconocimiento | Demostrado en laboratorio. 200, 49 bytes, el cuerpo contenía DRUPAL-CKEDITOR-FILEISIMAGE-WITNESS |
| XSS almacenado en un visitante si el servidor web o el navegador detecta el archivo como HTML/JS por sniffing | Misma URL pública, Content-Type no image | integridad (origen del visitante) | Inferido. Este laboratorio no modificó el MIME de Apache. Drupal almacenó los bytes. |
| Subir una imagen real a través de la misma ruta | Control control.png PNG de 1x1 | previsto | Demostrado en laboratorio. 201 |
Descartado en este análisis: PHP/RCE mediante .php (demostrado en laboratorio 422 FileExtension); carga no autenticada; ImageItem de JSON:API/REST (esas rutas ya añaden FileIsImage); LPE; una shell en la lista de extensiones de imagen permitidas por defecto.
Fijé Drupal 11.4.8 tras la oleada de SA-CORE de 2026. JSON:API ya añade la restricción faltante:
if (\is_a($field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE) && !array_key_exists('FileIsImage', $validators)) {
$validators['FileIsImage'] = [];
}
REST FileUploadResource.php hace lo mismo. CKEditor 5 se dejó con nombre de archivo + tamaño + dimensiones:
return [
'FileExtension' => [
'extensions' => implode(' ', $allowed_extensions),
],
'FileSizeLimit' => [
'fileLimit' => $max_filesize,
],
'FileImageDimensions' => [
'maxDimensions' => $max_dimensions,
],
];
FileImageDimensionsConstraintValidator retorna sin una violación cuando el archivo no es una imagen válida. El hub drupal:11.4 flotó a 11.4.9; este pin es composer-exact drupal/recommended-project:11.4.8. 11.4.0 eliminó Article de Standard, por lo que la imagen aplica core/recipes/article_content_type después de la instalación para que /node/add/article cargue CKEditor 5.
Errores ya registrados: tratar FileImageDimensions como una comprobación de MIME (son dimensiones sobre una imagen válida, y una no-op en caso contrario); afirmar una shell PHP en la lista de permitidos por defecto (.php es 422); usar la etiqueta flotante del hub y ver aparecer 11.4.9. Teatro: una reverse shell. El oráculo es 201 en un x.png falso más 422 en los mismos bytes nombrados x.php.
Restos del mismo pin: SSRF de segundo salto en oEmbed.
python3 drupal-ckeditor-fileisimage-Abraxas-Labs.py
o
cd lab
./run.sh
Dirígete solo a http://127.0.0.1:19500. Drupal 11.4.8 Standard en vivo sobre 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
Añadir 'FileIsImage' => [] a CKEditor5ImageController::getImageUploadValidators. Mantener FileExtension para que .php siga devolviendo 422. Los tipos de medios de imagen en la Biblioteca de Medios deberían usar ImageItem en lugar de un FileItem genérico para que hereden la misma restricción.
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0. Ver LICENSE. Solo laboratorio de loopback. Sin garantía.