task-151: 路径安全校验(PathSafetyGuard 规范化 startsWith 判定、../与绝对路径与符号链接逃逸拒绝、接入清理路径)+ 8 条测试
This commit is contained in:
+8
@@ -57,6 +57,11 @@ public class LocalTempCleanupService {
|
||||
|
||||
for (File child : children) {
|
||||
try {
|
||||
// 路径安全:child 必须位于临时根目录内(防穿越/符号链接逃逸),否则跳过并告警
|
||||
if (!PathSafetyGuard.isInside(tempDir, child)) {
|
||||
log.warn("local temp cleanup skipped unsafe path: {}", child.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
if (child.isFile() && isManagedRootTempFile(child) && isExpired(child, sourceExpireBefore)) {
|
||||
if (FileUtil.del(child)) {
|
||||
deletedSourceCount++;
|
||||
@@ -89,6 +94,9 @@ public class LocalTempCleanupService {
|
||||
File[] children = file.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
if (!PathSafetyGuard.isInside(file, child)) {
|
||||
continue;
|
||||
}
|
||||
deletedCount += deleteExpiredChildrenRecursively(child, expireBefore);
|
||||
}
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.nanri.aiimage.modules.file.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* 临时文件删除前的路径安全校验(task-151)。
|
||||
*
|
||||
* 删除前校验:child 规范化后必须位于 root 内(startsWith(root) 且不等于 root);
|
||||
* 穿越(../、绝对路径逃逸、符号链接逃逸)拒绝;null/非法输入返回 false。
|
||||
* 符号链接用 toRealPath 解析(存在时),防止链接指向根外文件被误删。
|
||||
*/
|
||||
public final class PathSafetyGuard {
|
||||
|
||||
private PathSafetyGuard() {
|
||||
}
|
||||
|
||||
/**
|
||||
* child 是否位于 root 内(规范化比较;root 本身不算内部)。
|
||||
* 路径不存在时以 toAbsolutePath().normalize() 兜底,存在时优先 toRealPath
|
||||
* 解析符号链接。
|
||||
*/
|
||||
public static boolean isInside(File root, File child) {
|
||||
if (root == null || child == null) {
|
||||
return false;
|
||||
}
|
||||
Path rootPath = resolve(root);
|
||||
Path childPath = resolve(child);
|
||||
if (rootPath == null || childPath == null) {
|
||||
return false;
|
||||
}
|
||||
return childPath.startsWith(rootPath) && !childPath.equals(rootPath);
|
||||
}
|
||||
|
||||
/** 路径名是否含穿越特征(../、绝对路径、盘符、反斜杠)。 */
|
||||
public static boolean isTraversal(String name) {
|
||||
if (name == null || name.isBlank()) {
|
||||
return true;
|
||||
}
|
||||
return name.contains("..")
|
||||
|| name.startsWith("/")
|
||||
|| name.matches("^[A-Za-z]:.*")
|
||||
|| name.contains("\\");
|
||||
}
|
||||
|
||||
private static Path resolve(File file) {
|
||||
try {
|
||||
return file.toPath().toRealPath();
|
||||
} catch (IOException ex) {
|
||||
return file.toPath().toAbsolutePath().normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user