From 214dfe5b24d288933302d8c9534322c583771f39 Mon Sep 17 00:00:00 2001 From: JinwooHwang Date: Tue, 6 Oct 2026 14:22:41 -0400 Subject: [PATCH] [GEODE-10671] Normalize uploaded file names when staging files --- .../internal/beans/FileUploader.java | 32 ++++++++++++++++++- .../DeploymentManagementController.java | 2 +- .../AbstractCommandsController.java | 25 ++++++++------- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/beans/FileUploader.java b/geode-core/src/main/java/org/apache/geode/management/internal/beans/FileUploader.java index a2578e675daa..985e2fbbc887 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/beans/FileUploader.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/beans/FileUploader.java @@ -30,6 +30,8 @@ import com.healthmarketscience.rmiio.SimpleRemoteOutputStream; import com.healthmarketscience.rmiio.exporter.RemoteStreamExporter; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.geode.logging.internal.log4j.api.LogService; @@ -66,7 +68,13 @@ public FileUploader(RemoteStreamExporter exporter) { public RemoteFile uploadFile(String filename) throws IOException { Path tempDir = createSecuredTempDirectory(STAGED_DIR_PREFIX); - File stagedFile = new File(tempDir.toString(), filename); + File stagedFile; + try { + stagedFile = getStagedFile(tempDir, filename); + } catch (RuntimeException e) { + FileUtils.deleteQuietly(tempDir.toFile()); + throw e; + } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(stagedFile)); RemoteOutputStreamMonitor monitor = new RemoteOutputStreamMonitor() { @@ -116,4 +124,26 @@ public static Path createSecuredTempDirectory(String prefix) throws IOException return tempDir; } + + /** + * Returns the file within {@code stagingDir} that an uploaded file named {@code fileName} is + * staged as. Only the last name segment of {@code fileName} is used, so the returned file is + * always a direct child of {@code stagingDir}. + * + * @throws IllegalArgumentException if {@code fileName} has no usable name segment + */ + public static File getStagedFile(Path stagingDir, String fileName) throws IOException { + String name = FilenameUtils.getName(fileName); + if (StringUtils.isBlank(name) || ".".equals(name) || "..".equals(name)) { + throw new IllegalArgumentException("Uploaded file name is not valid."); + } + + Path dir = stagingDir.toRealPath(); + Path stagedFile = dir.resolve(name).normalize(); + if (!dir.equals(stagedFile.getParent())) { + throw new IllegalArgumentException("Uploaded file name is not valid."); + } + + return stagedFile.toFile(); + } } diff --git a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/DeploymentManagementController.java b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/DeploymentManagementController.java index 23894924fa15..f17bb05230ae 100644 --- a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/DeploymentManagementController.java +++ b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/DeploymentManagementController.java @@ -151,7 +151,7 @@ public ResponseEntity deploy( throw new IllegalArgumentException("No file uploaded"); } Path tempDir = FileUploader.createSecuredTempDirectory("uploaded-"); - File targetFile = new File(tempDir.toFile(), file.getOriginalFilename()); + File targetFile = FileUploader.getStagedFile(tempDir, file.getOriginalFilename()); file.transferTo(targetFile); Deployment deployment = new Deployment(); if (StringUtils.isNotBlank(json)) { diff --git a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java index 3f875aece30a..b9884fe14d97 100644 --- a/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java +++ b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java @@ -50,6 +50,7 @@ import org.apache.geode.management.internal.ManagementAgent; import org.apache.geode.management.internal.SystemManagementService; import org.apache.geode.management.internal.beans.FileUploader; +import org.apache.geode.management.internal.cli.result.model.ResultModel; import org.apache.geode.management.internal.cli.shell.Gfsh; import org.apache.geode.management.internal.web.controllers.support.LoginHandlerInterceptor; import org.apache.geode.management.internal.web.util.UriUtils; @@ -243,20 +244,22 @@ protected String processCommand(final String command, final Map final MultipartFile[] multipartFiles) throws IOException { List filePaths = null; Path tempDir = null; - if (multipartFiles != null) { - tempDir = FileUploader.createSecuredTempDirectory("uploaded-"); - // staging the files to local - filePaths = new ArrayList<>(); - for (MultipartFile multipartFile : multipartFiles) { - File dest = new File(tempDir.toFile(), multipartFile.getOriginalFilename()); - multipartFile.transferTo(dest); - filePaths.add(dest.getAbsolutePath()); + try { + if (multipartFiles != null) { + tempDir = FileUploader.createSecuredTempDirectory("uploaded-"); + // staging the files to local + filePaths = new ArrayList<>(); + for (MultipartFile multipartFile : multipartFiles) { + File dest = FileUploader.getStagedFile(tempDir, multipartFile.getOriginalFilename()); + multipartFile.transferTo(dest); + filePaths.add(dest.getAbsolutePath()); + } } - } - MemberMXBean manager = getManagingMemberMXBean(); - try { + MemberMXBean manager = getManagingMemberMXBean(); return manager.processCommand(command, environment, filePaths); + } catch (IllegalArgumentException e) { + return ResultModel.createError(e.getMessage()).toJson(); } finally { if (tempDir != null) { FileUtils.deleteDirectory(tempDir.toFile());