diff --git a/server/src/com/mirth/connect/util/messagewriter/MessageWriterFactory.java b/server/src/com/mirth/connect/util/messagewriter/MessageWriterFactory.java index 31afeab11..547c70685 100644 --- a/server/src/com/mirth/connect/util/messagewriter/MessageWriterFactory.java +++ b/server/src/com/mirth/connect/util/messagewriter/MessageWriterFactory.java @@ -10,6 +10,7 @@ package com.mirth.connect.util.messagewriter; import java.io.File; +import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -41,6 +42,17 @@ private MessageWriterFactory() {} public MessageWriter getMessageWriter(MessageWriterOptions options, Encryptor encryptor) throws MessageWriterException { String baseFolder = StringUtils.defaultString(options.getBaseFolder(), System.getProperty("user.dir")); String rootFolder = FilenameUtils.getAbsolutePath(new File(baseFolder), options.getRootFolder()); + // Path traversal validation: Ensure that rootFolder is a subdirectory of baseFolder + try { + File baseDir = new File(baseFolder).getCanonicalFile(); + File rootDir = new File(rootFolder).getCanonicalFile(); + if (!isSafeSubdirectory(baseDir, rootDir)) { + throw new MessageWriterException("Invalid root folder: traversal outside of base directory is not allowed."); + } + } catch (java.io.IOException e) { + throw new MessageWriterException("Failed to validate export folder path: " + e.getMessage(), e); + } + String filePattern = options.getFilePattern(); if (filePattern.substring(0, 1).equals(IOUtils.DIR_SEPARATOR)) { @@ -86,4 +98,22 @@ private String getArchiveExtension(String archiver, String compressor) { return archiver + "." + compressor; } + /** + * Checks if candidate is a subdirectory (or the same directory) as base. + */ + private boolean isSafeSubdirectory(File base, File candidate) { + try { + String basePath = base.getCanonicalPath(); + String candidatePath = candidate.getCanonicalPath(); + if (basePath.equals(candidatePath)) { + return true; + } + if (candidatePath.startsWith(basePath + File.separator)) { + return true; + } + return false; + } catch (java.io.IOException e) { + return false; + } + } } diff --git a/server/src/com/mirth/connect/util/messagewriter/MessageWriterOptions.java b/server/src/com/mirth/connect/util/messagewriter/MessageWriterOptions.java index 7753fae07..3521b5687 100644 --- a/server/src/com/mirth/connect/util/messagewriter/MessageWriterOptions.java +++ b/server/src/com/mirth/connect/util/messagewriter/MessageWriterOptions.java @@ -113,10 +113,18 @@ public String getFilePattern() { /** * @param filePattern * A string defining the folder/filename(s) for writing messages. It may contain - * variables to be replaced. + * variables to be replaced only if allowed by internal logic. User-supplied input is sanitized. */ public void setFilePattern(String filePattern) { - this.filePattern = filePattern; + // Only allow safe patterns: alphanumerics, underscore, dash, dot, forward slash + // Disallow Velocity special chars: $, #, {, } + if (filePattern != null && filePattern.matches("^[a-zA-Z0-9_\\-/\\.]+$")) { + this.filePattern = filePattern; + } else { + throw new IllegalArgumentException( + "Invalid filePattern: Only alphanumerics, underscore, dash, dot, and slash are permitted. Velocity expressions are not allowed." + ); + } } public String getArchiveFileName() {