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
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 violació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 bytes que no son imagen con el nombre x.png a /ckeditor5/upload-image/{editor} y obtener 201 más una URL pública de archivos.
| ID | sin CVE todavía |
| Clase | Carga sin restricciones / XSS almacenado si se detecta |
| Alcance | Remoto (usuario autenticado del 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) |
| Auth | 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 | Misma URL pública, Content-Type no image | integridad (origen del visitante) | Inferido. Este laboratorio no cambió el MIME de Apache. Drupal almacenó los bytes. |
| Subir una imagen real a través de la misma ruta | Control control.png PNG 1x1 | previsto | Demostrado en laboratorio. 201 |
Cerrado en este remanente: PHP/RCE vía .php (demostrado en laboratorio 422 FileExtension); carga no autenticada; JSON:API/REST ImageItem (esas rutas ya añaden FileIsImage); LPE; un shell en la lista de extensiones de imagen permitidas por defecto.
Fijé Drupal 11.4.8 después de 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 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, así que la imagen aplica core/recipes/article_content_type después de la instalación para que /node/add/article cargue CKEditor 5.
Desvíos ya registrados: tratar FileImageDimensions como una comprobación de MIME (son dimensiones sobre una imagen válida, y un no-op en caso contrario); afirmar un 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: un reverse shell. El oráculo es 201 en el falso x.png más 422 en los mismos bytes con el nombre x.php.
Remanentes del mismo pin: SSRF de segundo salto de oEmbed.
python3 drupal-ckeditor-fileisimage-Abraxas-Labs.py
o
cd lab
./run.sh
Objetivo solo 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 siendo 422. Los tipos de medios de imagen en Media Library 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 en loopback. Sin garantía.