mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-06-15 10:27:12 +00:00
fix: harden listFiles and addTask validation
This commit is contained in:
@@ -46,6 +46,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
filterUri.add("/pdfjs/web/viewer.html");
|
||||
filterUri.add("/msg/index.html");
|
||||
filterUri.add("/eml/index.html");
|
||||
@@ -64,6 +65,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
TrustDirFilter filter = new TrustDirFilter();
|
||||
FilterRegistrationBean<TrustDirFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.keking.service;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.FileType;
|
||||
import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.web.filter.TrustDirFilter;
|
||||
import cn.keking.web.filter.TrustHostFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -58,6 +60,10 @@ public class FileConvertQueueTask {
|
||||
try {
|
||||
url = cacheService.takeQueueTask();
|
||||
if (url != null) {
|
||||
if (!TrustHostFilter.isTrustedSourceUrl(url) || !TrustDirFilter.isTrustedFileUrl(url)) {
|
||||
logger.warn("拒绝处理不受信任的预览转换任务,url:{}", url);
|
||||
continue;
|
||||
}
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(url, null);
|
||||
FileType fileType = fileAttribute.getType();
|
||||
logger.info("正在处理预览转换任务,url:{},预览类型:{}", url, fileType);
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
@@ -147,6 +148,28 @@ public class FileController {
|
||||
}
|
||||
}
|
||||
|
||||
private Path getDemoBasePath() {
|
||||
return Paths.get(fileDir, demoDir).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private Path resolveDemoPath(String path) {
|
||||
Path demoBasePath = getDemoBasePath();
|
||||
try {
|
||||
if (ObjectUtils.isEmpty(path)) {
|
||||
return demoBasePath;
|
||||
}
|
||||
Path normalizedPath = demoBasePath.resolve(path).normalize();
|
||||
if (!normalizedPath.startsWith(demoBasePath)) {
|
||||
logger.warn("检测到非法目录访问,path:{}", path);
|
||||
return null;
|
||||
}
|
||||
return normalizedPath;
|
||||
} catch (InvalidPathException e) {
|
||||
logger.warn("解析目录路径失败,path:{}", path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/fileUpload")
|
||||
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "path", defaultValue = "") String path) {
|
||||
@@ -341,17 +364,20 @@ public class FileController {
|
||||
}
|
||||
|
||||
// ==================== 2. 构建路径和验证 ====================
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
Path resolvedPath = resolveDemoPath(path);
|
||||
if (resolvedPath == null) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
}
|
||||
|
||||
File currentDir = new File(basePath);
|
||||
File currentDir = resolvedPath.toFile();
|
||||
if (!currentDir.exists() || !currentDir.isDirectory()) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
}
|
||||
String basePath = resolvedPath.toString();
|
||||
|
||||
// ==================== 3. 收集所有文件路径 ====================
|
||||
List<Path> allPaths = new ArrayList<>();
|
||||
|
||||
@@ -8,6 +8,8 @@ import cn.keking.service.FilePreviewFactory;
|
||||
import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.service.impl.OtherFilePreviewImpl;
|
||||
import cn.keking.utils.*;
|
||||
import cn.keking.web.filter.TrustDirFilter;
|
||||
import cn.keking.web.filter.TrustHostFilter;
|
||||
import fr.opensagres.xdocreport.core.io.IOUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
@@ -231,6 +233,11 @@ public class OnlinePreviewController {
|
||||
logger.info("{},url:{}", errorMsg, fileUrls);
|
||||
return errorMsg;
|
||||
}
|
||||
if (!TrustHostFilter.isTrustedSourceUrl(fileUrls) || !TrustDirFilter.isTrustedFileUrl(fileUrls)) {
|
||||
String errorMsg = "访问不合法:来源地址不受信任!";
|
||||
logger.info("{},url:{}", errorMsg, fileUrls);
|
||||
return errorMsg;
|
||||
}
|
||||
logger.info("添加转码队列url:{}", fileUrls);
|
||||
cacheService.addQueueTask(fileUrls);
|
||||
return "success";
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.Locale;
|
||||
public class TrustDirFilter implements Filter {
|
||||
|
||||
private String notTrustDirView;
|
||||
private final Logger logger = LoggerFactory.getLogger(TrustDirFilter.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(TrustDirFilter.class);
|
||||
|
||||
|
||||
@Override
|
||||
@@ -59,6 +59,47 @@ public class TrustDirFilter implements Filter {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isTrustedFileUrl(String urlPath) {
|
||||
// 判断URL是否合法
|
||||
if (KkFileUtils.isIllegalFileName(urlPath) || !StringUtils.hasText(urlPath) || !WebUtils.isValidUrl(urlPath)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URL url = WebUtils.normalizedURL(urlPath);
|
||||
|
||||
if ("file".equals(url.getProtocol().toLowerCase(Locale.ROOT))) {
|
||||
String filePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8.name());
|
||||
// 将文件路径转换为File对象
|
||||
File targetFile = new File(filePath);
|
||||
// 将配置目录也转换为File对象
|
||||
File fileDir = new File(ConfigConstants.getFileDir());
|
||||
File localPreviewDir = new File(ConfigConstants.getLocalPreviewDir());
|
||||
try {
|
||||
// 获取规范路径
|
||||
String canonicalFilePath = targetFile.getCanonicalPath();
|
||||
String canonicalFileDir = fileDir.getCanonicalPath();
|
||||
String canonicalLocalPreviewDir = localPreviewDir.getCanonicalPath();
|
||||
return isSubDirectory(canonicalFileDir, canonicalFilePath) || isSubDirectory(canonicalLocalPreviewDir, canonicalFilePath);
|
||||
} catch (IOException e) {
|
||||
LoggerFactory.getLogger(TrustDirFilter.class).warn("获取规范路径失败,使用原始路径比较", e);
|
||||
String absFilePath = targetFile.getAbsolutePath();
|
||||
String absFileDir = fileDir.getAbsolutePath();
|
||||
String absLocalPreviewDir = localPreviewDir.getAbsolutePath();
|
||||
absFilePath = absFilePath.replace('\\', '/');
|
||||
absFileDir = absFileDir.replace('\\', '/');
|
||||
absLocalPreviewDir = absLocalPreviewDir.replace('\\', '/');
|
||||
if (!absFileDir.endsWith("/")) absFileDir += "/";
|
||||
if (!absLocalPreviewDir.endsWith("/")) absLocalPreviewDir += "/";
|
||||
return absFilePath.startsWith(absFileDir) || absFilePath.startsWith(absLocalPreviewDir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | GalimatiasParseException e) {
|
||||
LoggerFactory.getLogger(TrustDirFilter.class).error("解析URL异常,url:{}", urlPath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowPreview(String urlPath) {
|
||||
// 判断URL是否合法
|
||||
if (KkFileUtils.isIllegalFileName(urlPath) || !StringUtils.hasText(urlPath) || !WebUtils.isValidUrl(urlPath)) {
|
||||
@@ -107,7 +148,7 @@ public class TrustDirFilter implements Filter {
|
||||
/**
|
||||
* 检查子路径是否在父路径下(跨平台)
|
||||
*/
|
||||
private boolean isSubDirectory(String parentDir, String childPath) {
|
||||
private static boolean isSubDirectory(String parentDir, String childPath) {
|
||||
try {
|
||||
File parent = new File(parentDir);
|
||||
File child = new File(childPath);
|
||||
|
||||
@@ -70,6 +70,14 @@ public class TrustHostFilter implements Filter {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTrustedSourceUrl(String url) {
|
||||
if (!WebUtils.isValidUrl(url)) {
|
||||
return false;
|
||||
}
|
||||
String host = WebUtils.getHost(url);
|
||||
return !new TrustHostFilter().isNotTrustHost(host);
|
||||
}
|
||||
|
||||
public boolean isNotTrustHost(String host) {
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
logger.warn("主机名为空或无效,拒绝访问");
|
||||
|
||||
Reference in New Issue
Block a user