From 192c1dc866f60f89f829c3c59073636313d811f7 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:21:42 +0000 Subject: [PATCH] =?UTF-8?q?=EB=B3=B4=EC=95=88:=20canonicalFile=20=EC=8B=AC?= =?UTF-8?q?=EB=B3=BC=EB=A6=AD=20=EB=A7=81=ED=81=AC=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?=EC=9A=B0=ED=9A=8C=20=EC=B7=A8=EC=95=BD=EC=A0=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit canonicalFile이 심볼릭 링크를 실제 경로로 자동 변환하여 발생하는 경로 탐색 및 심볼릭 링크 제한 우회 취약점을 수정했습니다. - File(topDir).canonicalFile 대신 File(topDir).toPath().toAbsolutePath().normalize().toFile()를 사용하여 심볼릭 링크를 유지하도록 변경. - 테스트 커버리지를 높이기 위해 추가 단위 테스트 구현. - .jules/sentinel.md에 해당 취약점에 대한 Sentinel 학습 기록 작성. --- .jules/sentinel.md | 5 ++++ patch_test.diff | 39 +++++++++++++++++++++++++++ src/main/kotlin/html4tree/main.kt | 2 +- src/test/kotlin/html4tree/MainTest.kt | 22 +++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 patch_test.diff diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 6c61284..33ac92a 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -22,3 +22,8 @@ **Vulnerability:** Defense in Depth (CSP Missing) **Learning:** Even when inputs are properly escaped, statically generated HTML that displays file/directory structures should implement a Content Security Policy (CSP) to provide an extra layer of defense against potential XSS bypasses. **Prevention:** Include a strict CSP meta tag (e.g., `default-src 'none'; style-src 'unsafe-inline';`) in auto-generated HTML headers when external scripts or resources are not required. + +## 2024-06-30 - canonicalFile을 통한 경로 탐색 및 심볼릭 링크 우회 취약점 +**Vulnerability:** `topDir` 검사에서 심볼릭 링크 여부를 확인(`Files.isDirectory(..., LinkOption.NOFOLLOW_LINKS)`)하기 전에 `File(topDir).canonicalFile`을 사용했습니다. +**Learning:** `canonicalFile`은 심볼릭 링크를 실제 경로로 미리 확인합니다. 따라서 사용자가 심볼릭 링크를 입력하면 `canonicalFile`은 실제 타겟으로 경로를 조용히 평가하여 의도한 심볼릭 링크 제한 검사를 우회하게 됩니다. 이는 Java/Kotlin에서 흔히 발생하는 경로 탐색 및 심볼릭 링크 제한 우회 결함입니다. +**Prevention:** `canonicalFile` 대신 `File(topDir).toPath().toAbsolutePath().normalize().toFile()`을 사용합니다. 이는 파일 시스템과 상호 작용하여 심볼릭 링크를 해석하지 않고 경로 문자열을 정규화하므로 후속 심볼릭 링크 검사가 대상이 아닌 실제 심볼릭 링크 경로에서 올바르게 작동할 수 있습니다. diff --git a/patch_test.diff b/patch_test.diff new file mode 100644 index 0000000..7f7f805 --- /dev/null +++ b/patch_test.diff @@ -0,0 +1,39 @@ +<<<<<<< SEARCH + @Test + fun testGoWithMaxLevel() { +======= + @Test + fun testGoWithMaxLevel() { + val subdir = File(tempDir, "subdir") + subdir.mkdir() + val subsubdir = File(subdir, "subsubdir") + subsubdir.mkdir() + + go(tempDir.absolutePath, 0) + + assertTrue(File(tempDir, "index.html").exists()) + assertFalse(File(subdir, "index.html").exists()) + assertFalse(File(subsubdir, "index.html").exists()) + } + + @Test + fun testGoMaxLevelLimitsCrawl() { + val subdir = File(tempDir, "subdir") + subdir.mkdir() + + go(tempDir.absolutePath, -2) // Should not process any dirs theoretically, testing the currentLevel <= maxLevel boundary + } + + @Test + fun testProcessIgnoreFileWithListNull() { + val unreadableDir = File(tempDir, "unreadable2") + unreadableDir.mkdir() + File(unreadableDir, ".html4ignore").writeText(".*\\.txt") + unreadableDir.setReadable(false, false) + try { + process_ignore_file(unreadableDir) + } finally { + unreadableDir.setReadable(true, false) + } + } +>>>>>>> REPLACE diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 2e2809f..ae3e0b5 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -23,7 +23,7 @@ fun main(args: Array) = Html4tree().main(args) fun go(topDir: String, maxLevel: Int) { require(topDir.isNotBlank()) - val top_dir = File(topDir).canonicalFile + val top_dir = File(topDir).toPath().toAbsolutePath().normalize().toFile() require(Files.isDirectory(top_dir.toPath(), LinkOption.NOFOLLOW_LINKS)) { "Top directory must be an existing non-symlink directory" } val ll = LinkedList() diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index e8a3082..84e6684 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -227,6 +227,28 @@ class MainTest { assertFalse(File(subsubdir, "index.html").exists()) } + @Test + fun testGoMaxLevelLimitsCrawl() { + val subdir = File(tempDir, "subdir") + subdir.mkdir() + + go(tempDir.absolutePath, -2) + assertFalse(File(tempDir, "index.html").exists()) + } + + @Test + fun testProcessIgnoreFileWithListNull() { + val unreadableDir = File(tempDir, "unreadable2") + unreadableDir.mkdir() + File(unreadableDir, ".html4ignore").writeText(".*\\.txt") + Assume.assumeTrue(unreadableDir.setReadable(false, false)) + try { + process_ignore_file(unreadableDir) + } finally { + unreadableDir.setReadable(true, false) + } + } + @Test fun testGoWithUnreadableDir() { val unreadableDir = File(tempDir, "unreadable")