Drupal 11.4.8 - 높음 - 원격 - 업로드 - CKEditor 5 이미지 업로드가 여전히 FileIsImage를 건너뜀
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-ckeditor-fileisimage
분석 문서: https://abraxaslabs.tech/research/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라는 이름의 비이미지 바이트를 /ckeditor5/upload-image/{editor}에 POST하여 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에서 해당 바이트 다시 읽기 | JSON url에 대한 GET | 정찰 | 실습 입증. 200, 49바이트, 본문에 DRUPAL-CKEDITOR-FILEISIMAGE-WITNESS 포함 |
| 웹 서버나 브라우저가 파일을 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; 기본 이미지 확장자 허용 목록에 있는 셸.
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-exact drupal/recommended-project:11.4.8입니다. 11.4.0에서 Standard에서 Article이 제거되었으므로, 이미지는 설치 후 core/recipes/article_content_type을 적용하여 /node/add/article이 CKEditor 5를 로드하도록 합니다.
이미 기록된 잘못된 접근: FileImageDimensions를 MIME 검사로 취급하는 것(이는 유효한 이미지에 대한 크기 검사이며, 그 외에는 no-op임); 기본 허용 목록에서 PHP 셸을 주장하는 것(.php는 422임); 부동 hub 태그를 사용하여 11.4.9가 나타나는 것을 지켜보는 것. 극적 요소: 리버스 셸. 오라클은 가짜 x.png에 대한 201과 동일한 바이트를 x.php로 명명했을 때의 422입니다.
동일한 고정의 잔여물: oEmbed second-hop 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
CKEditor5ImageController::getImageUploadValidators에 'FileIsImage' => []를 추가합니다. .php가 422로 유지되도록 FileExtension은 그대로 둡니다. Media Library의 이미지 미디어 유형은 일반 FileItem 대신 ImageItem을 사용하여 동일한 제약 조건을 상속받도록 해야 합니다.
61044508071749629aaf3380aa85971e94df9e84)CKEditor5ImageController.phpGNU Affero GPL v3.0. LICENSE를 참조하세요. 루프백 실습 전용입니다. 보증은 제공되지 않습니다.