Drupal 11.4.8 - 高危 - 远程 - 上传 - CKEditor 5 图片上传仍然跳过 FileIsImage
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-ckeditor-fileisimage
类别: 不受限制的文件上传 / 存储型 XSS 影响范围: 远程
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-009 在 JSON:API 和 REST 图片字段上添加了 FileIsImage。CKEditor5ImageController::getImageUploadValidators 仍然只返回 FileExtension、FileSizeLimit 和 FileImageDimensions。当文件不是有效图片时,FileImageDimensions 返回时不会产生违规。文件名扩展名是剩余的检查。
使用带有 CKEditor 5 图片上传功能的文本格式的用户,可以将名为 x.png 的非图片字节 POST 到 /ckeditor5/upload-image/{editor},并获得 201 以及一个公开文件 URL。
| ID | 暂无 CVE |
| 类别 | 不受限制的文件上传 / 若被嗅探则为存储型 XSS |
| 影响范围 | 远程(已认证的格式用户) |
| CWE | CWE-434、CWE-79 |
| CVSS | 中危:5.4 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| 产品 | Drupal core |
| 受影响版本 | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| 认证 | 使用带有 CKEditor 5 图片上传功能的文本格式的已认证用户(Standard 上的 basic_html / full_html) |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
需要一个能够使用编辑器具有 image_upload.status 为 true 的格式的账户。在 Standard 上,即 basic_html 和 full_html。没有账户的完全陌生人无法访问此路由(_entity_access: editor.use 加上 CSRF)。
| 权限 | 方法 / 汇聚点 | 类型 | 实验验证或推断 |
|---|---|---|---|
将非图片字节写入 sites/default/files/inline-images/ 下的公开 .png | POST /ckeditor5/upload-image/{editor} multipart upload | 完整性 | 实验验证。x.png 返回 201 和 url=/sites/default/files/inline-images/x.png |
| 在公开文件 URL 处读回这些字节 | GET JSON 中的 url | 侦察 | 实验验证。200,49 字节,正文包含 DRUPAL-CKEDITOR-FILEISIMAGE-WITNESS |
| 如果 Web 服务器或浏览器将文件嗅探为 HTML/JS,则在访问者中触发存储型 XSS | 同一公开 URL,Content-Type 不是图片 | 完整性(访问者源) | 推断。本实验未更改 Apache MIME。Drupal 存储了这些字节。 |
| 通过同一路由上传真实图片 | 对照 control.png 1x1 PNG | 预期行为 | 实验验证。201 |
已排除的遗留问题:通过 .php 实现 PHP/RCE(实验验证 422 FileExtension);未认证上传;JSON:API/REST ImageItem(这些路径已添加 FileIsImage);LPE;在默认图片扩展名允许列表上获得 shell。
在 2026 SA-CORE 浪潮之后,我固定了 Drupal 11.4.8。JSON:API 已经添加了缺失的约束:
if (\is_a($field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE) && !array_key_exists('FileIsImage', $validators)) {
$validators['FileIsImage'] = [];
}
REST FileUploadResource.php 也做了同样的事。CKEditor 5 则停留在文件名 + 大小 + 尺寸上:
return [
'FileExtension' => [
'extensions' => implode(' ', $allowed_extensions),
],
'FileSizeLimit' => [
'fileLimit' => $max_filesize,
],
'FileImageDimensions' => [
'maxDimensions' => $max_dimensions,
],
];
当文件不是有效图片时,FileImageDimensionsConstraintValidator 返回时不会产生违规。Hub drupal:11.4 浮动到了 11.4.9;此固定版本是 composer 精确的 drupal/recommended-project:11.4.8。11.4.0 从 Standard 中移除了 Article,因此该镜像在安装后应用 core/recipes/article_content_type,以便 /node/add/article 加载 CKEditor 5。
已记录的弯路:将 FileImageDimensions 视为 MIME 检查(它是对有效图片的尺寸检查,否则为空操作);声称在默认允许列表上获得 PHP shell(.php 为 422);使用浮动的 hub 标签并看到 11.4.9 出现。表演:反向 shell。判定标准是对伪造的 x.png 返回 201,以及对相同字节命名为 x.php 返回 422。
同一固定版本的遗留问题:oEmbed 第二跳 SSRF。
python3 drupal-ckeditor-fileisimage-Abraxas-Labs.py
或
cd lab
./run.sh
目标仅为 http://127.0.0.1:19500。在 php:8.4-apache-trixie、SQLite 上运行实时 Drupal 11.4.8 Standard。
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
将 'FileIsImage' => [] 添加到 CKEditor5ImageController::getImageUploadValidators。保留 FileExtension,以便 .php 保持 422。Media Library 中的图片媒体类型应使用 ImageItem 而不是通用的 FileItem,以便继承相同的约束。
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。无担保。