
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-ckeditor-fileisimage
技术文章:https://abraxaslabs.tech/research/drupal-ckeditor-fileisimage
类别: 不受限制的文件上传 / 存储型 XSS 影响范围: 远程
Drupal 核心 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 核心 |
| 受影响版本 | 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 读回这些字节 | 对 JSON 中 url 发起 GET | 侦察 | 实验验证。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 已将 Article 从 Standard 中移除,因此该镜像在安装后应用 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 上运行 Drupal 11.4.8 Standard 实时实例,使用 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
将 'FileIsImage' => [] 添加到 CKEditor5ImageController::getImageUploadValidators。保留 FileExtension,以便 .php 仍然返回 422。媒体库中的图像媒体类型应使用 ImageItem 而不是通用的 FileItem,以便继承相同的约束。
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。