From 86232024bac52e3cffaa4acd04747f1cadf654e0 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:03:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20?= =?UTF-8?q?=EB=84=90=20=EB=B0=94=EC=9D=B4=ED=8A=B8=20=EC=82=BD=EC=9E=85=20?= =?UTF-8?q?=EC=9A=B0=ED=9A=8C=20=EA=B3=B5=EA=B2=A9=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 파일 업로드 시 원본 파일명에 널 바이트(`\u0000`)가 포함된 경우 이를 명시적으로 차단하는 로직을 `DefaultDocumentValidationService`에 추가했습니다. 이를 통해 확장자 유효성 검사 우회 등의 파일명 관련 취약점을 방어합니다. 관련 단위 테스트도 함께 추가하여 커버리지 100%를 달성했습니다. --- .jules/sentinel.md | 8 ++++---- .../DefaultDocumentValidationService.java | 3 +++ .../DefaultDocumentValidationServiceTest.java | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 76a9742..6e6e26b 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -1,4 +1,4 @@ -## 2026-06-30 - Prevent DOM-based XSS in Viewer JS -**Vulnerability:** Untrusted paths from API responses were directly assigned to `a.href` and used in `iframe` generation, which allows execution of malicious URIs like `javascript:` or `data:`. -**Learning:** Even when avoiding `innerHTML`, directly setting URL-like strings to DOM attributes without protocol validation introduces XSS vectors. The payload can be executed when the link is clicked or the iframe is loaded. -**Prevention:** Implement an `isSafeUrl` verification function to ensure the protocol is strictly `http:` or `https:` (using `new URL()`) before assigning untrusted inputs to DOM attributes like `href` or `src`. +## 2026-07-01 - [Null Byte Injection Prevention] +**Vulnerability:** Filename inputs missing sanitization for null bytes could be truncated, bypassing extension validation rules. +**Learning:** Checking the `file.getOriginalFilename()` for null bytes (`\u0000`) before proceeding ensures that files with hidden payloads or manipulated extensions are rejected explicitly. +**Prevention:** Always validate and reject file names that contain null bytes (`\u0000`) at the very beginning of processing, throwing an `IllegalArgumentException` rather than ignoring them. diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java index b898f7a..3cdbdba 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java @@ -55,6 +55,9 @@ public void validateOrThrow(MultipartFile file, PolicyOverrideRequest overrideRe } String fileName = file.getOriginalFilename(); + if (fileName != null && fileName.indexOf('\u0000') != -1) { + throw new IllegalArgumentException("File name contains invalid characters."); + } String extension = extensionOf(fileName); if (extension.isEmpty()) { throw new IllegalArgumentException("File extension is required."); diff --git a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java index dc1df79..cb2dba8 100644 --- a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java +++ b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentValidationServiceTest.java @@ -191,6 +191,22 @@ void rejectsMissingExtension() { assertEquals("File extension is required.", ex.getMessage()); } + @Test + void rejectsFilenameWithNullByte() { + ConversionProperties conversionProperties = new ConversionProperties(); + conversionProperties.setBlockedExtensions(Set.of("hwp", "hwpx")); + DefaultDocumentValidationService validationService = new DefaultDocumentValidationService(conversionProperties); + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> validationService.validateOrThrow( + new MockMultipartFile("file", "contract\u0000.pdf", "application/pdf", new byte[] {1}) + ) + ); + + assertEquals("File name contains invalid characters.", ex.getMessage()); + } + @Test void rejectsBlankFilenameOrMissingName() { ConversionProperties conversionProperties = new ConversionProperties();