task-162: 路径穿越输入矩阵(../、..\、绝对路径、%2e 编码、全角点、混合分隔符、空字节全部拒绝)+ 9 条测试

This commit is contained in:
2026-09-02 07:25:49 +08:00
parent 035ec9ac79
commit 0e4d680aef
2 changed files with 79 additions and 2 deletions
@@ -33,15 +33,21 @@ public final class PathSafetyGuard {
return childPath.startsWith(rootPath) && !childPath.equals(rootPath);
}
/** 路径名是否含穿越特征(../、绝对路径、盘符、反斜杠)。 */
/**
* 路径名是否含穿越特征(../、..\\、绝对路径、盘符、URL 编码点 %2e、
* 全角点 .、混合分隔符、空字节)。
*/
public static boolean isTraversal(String name) {
if (name == null || name.isBlank()) {
return true;
}
return name.contains("..")
|| name.contains("%2e") || name.contains("%2E")
|| name.contains("")
|| name.startsWith("/")
|| name.matches("^[A-Za-z]:.*")
|| name.contains("\\");
|| name.contains("\\")
|| name.contains("\0");
}
private static Path resolve(File file) {
@@ -0,0 +1,71 @@
package com.nanri.aiimage.modules.file.service;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* task-162:路径穿越输入矩阵(plan 09)。
* ../、..\\、绝对路径、URL 编码 %2e%2e、Unicode 全角点、混合分隔符、空字节
* 全部拒绝;普通文件名放行。
*/
class PathTraversalMatrixTest {
@Test
void dotdotSlashRejected() {
assertTrue(PathSafetyGuard.isTraversal("../escape"));
assertTrue(PathSafetyGuard.isTraversal("a/../../b"));
assertTrue(PathSafetyGuard.isTraversal("..../x"));
}
@Test
void dotdotBackslashRejected() {
assertTrue(PathSafetyGuard.isTraversal("..\\escape"));
assertTrue(PathSafetyGuard.isTraversal("a\\..\\b"));
}
@Test
void urlEncodedDotdotRejected() {
assertTrue(PathSafetyGuard.isTraversal("%2e%2e/x"));
assertTrue(PathSafetyGuard.isTraversal("%2E%2E/x"));
assertTrue(PathSafetyGuard.isTraversal("a%2e%2eb"));
}
@Test
void fullwidthDotRejected() {
assertTrue(PathSafetyGuard.isTraversal("\uFF0E\uFF0E/escape"), "全角点 .. 拒绝");
assertTrue(PathSafetyGuard.isTraversal("a\uFF0E\uFF0Eb"));
}
@Test
void absoluteWindowsPathRejected() {
assertTrue(PathSafetyGuard.isTraversal("C:\\windows\\system32"));
assertTrue(PathSafetyGuard.isTraversal("D:/escape/file"));
}
@Test
void absoluteUnixPathRejected() {
assertTrue(PathSafetyGuard.isTraversal("/etc/passwd"));
assertTrue(PathSafetyGuard.isTraversal("/tmp/escape"));
}
@Test
void mixedSeparatorsRejected() {
assertTrue(PathSafetyGuard.isTraversal("..\\/escape"));
assertTrue(PathSafetyGuard.isTraversal("a/..\\b/c"));
assertTrue(PathSafetyGuard.isTraversal("C:..\\x"));
}
@Test
void nullByteRejected() {
assertTrue(PathSafetyGuard.isTraversal("evil\0.tmp"));
}
@Test
void normalNamesAllowed() {
assertFalse(PathSafetyGuard.isTraversal("0123456789abcdef0123456789abcdef.xlsx"));
assertFalse(PathSafetyGuard.isTraversal("result-2026-09-02.xlsx"));
assertFalse(PathSafetyGuard.isTraversal("chunk-1.json"));
}
}