VYPR
Moderate severityNVD Advisory· Published Jan 27, 2025· Updated Feb 6, 2025

Apache Solr: Configset upload on Windows allows arbitrary path write-access

CVE-2024-52012

Description

Relative Path Traversal vulnerability in Apache Solr.

Solr instances running on Windows are vulnerable to arbitrary filepath write-access, due to a lack of input-sanitation in the "configset upload" API.  Commonly known as a "zipslip", maliciously constructed ZIP files can use relative filepaths to write data to unanticipated parts of the filesystem. This issue affects Apache Solr: from 6.6 through 9.7.0.

Users are recommended to upgrade to version 9.8.0, which fixes the issue.  Users unable to upgrade may also safely prevent the issue by using Solr's "Rule-Based Authentication Plugin" to restrict access to the configset upload API, so that it can only be accessed by a trusted set of administrators/users.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache Solr on Windows versions 6.6 through 9.7.0 are vulnerable to a zipslip attack via the configset upload API, allowing arbitrary file writes due to insufficient path validation.

Vulnerability

CVE-2024-52012 is a relative path traversal vulnerability in Apache Solr, specifically affecting instances running on Windows. The flaw resides in the "configset upload" API, which fails to properly sanitize file paths extracted from uploaded ZIP archives [1][4]. This is a classic "zipslip" issue where a maliciously crafted ZIP file containing entries with relative paths (e.g., ../escapePath) can write files outside the intended configset directory [2].

Exploitation

An attacker with access to the configset upload API can supply a specially crafted ZIP archive containing entries with path traversal sequences [2]. The vulnerability does not require authentication if the API is exposed, but in typical deployments the API is intended to be restricted to administrators [1][4]. On Windows, the attacker can leverage backslashes as path separators to escape the configset base directory [2]. The fix in commit 5795edd introduces validation that rejects such paths and logs warnings, preventing the traversal [2].

Impact

Successful exploitation allows an attacker to write arbitrary files to arbitrary locations on the Windows filesystem, potentially leading to remote code execution (e.g., by overwriting JARs, configuration files, or critical system binaries) [1][4]. The vulnerability is rated as moderate severity, but the impact can be severe depending on the Solr instance's privileges and the writable paths [4].

Mitigation

The issue is fixed in Apache Solr 9.8.0 [1][4]. Users unable to upgrade can mitigate the risk by using Solr's Rule-Based Authentication Plugin to restrict access to the configset upload API to trusted administrators only [1][4]. No workarounds for the path traversal itself are available other than the plugin restriction or upgrade.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.solr:solr-coreMaven
>= 6.6, < 9.8.09.8.0

Affected products

8

Patches

1
5795edd143b8

SOLR-17543: Input validation in FSConfigSetService

https://github.com/apache/solrJason GerlowskiNov 11, 2024via ghsa
4 files changed · +134 16
  • solr/core/src/java/org/apache/solr/core/FileSystemConfigSetService.java+25 13 modified
    @@ -38,6 +38,7 @@
     import org.apache.solr.common.cloud.ZkMaintenanceUtils;
     import org.apache.solr.common.util.Utils;
     import org.apache.solr.util.FileTypeMagicUtil;
    +import org.apache.solr.util.FileUtils;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    @@ -150,19 +151,30 @@ public void uploadFileToConfig(
           throws IOException {
         if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(fileName)) {
           log.warn("Not including uploading file to config, as it is a forbidden type: {}", fileName);
    -    } else {
    -      if (!FileTypeMagicUtil.isFileForbiddenInConfigset(data)) {
    -        Path filePath = getConfigDir(configName).resolve(normalizePathToOsSeparator(fileName));
    -        if (!Files.exists(filePath) || overwriteOnExists) {
    -          Files.write(filePath, data);
    -        }
    -      } else {
    -        String mimeType = FileTypeMagicUtil.INSTANCE.guessMimeType(data);
    -        log.warn(
    -            "Not including uploading file {}, as it matched the MAGIC signature of a forbidden mime type {}",
    -            fileName,
    -            mimeType);
    -      }
    +      return;
    +    }
    +    if (FileTypeMagicUtil.isFileForbiddenInConfigset(data)) {
    +      String mimeType = FileTypeMagicUtil.INSTANCE.guessMimeType(data);
    +      log.warn(
    +          "Not including uploading file {}, as it matched the MAGIC signature of a forbidden mime type {}",
    +          fileName,
    +          mimeType);
    +      return;
    +    }
    +    final var configsetBasePath = getConfigDir(configName);
    +    final var configsetFilePath = configsetBasePath.resolve(normalizePathToOsSeparator(fileName));
    +    if (!FileUtils.isPathAChildOfParent(
    +        configsetBasePath, configsetFilePath)) { // See SOLR-17543 for context
    +      log.warn(
    +          "Not uploading file [{}], as it resolves to a location [{}] outside of the configset root directory [{}]",
    +          fileName,
    +          configsetFilePath,
    +          configsetBasePath);
    +      return;
    +    }
    +
    +    if (overwriteOnExists || !Files.exists(configsetFilePath)) {
    +      Files.write(configsetFilePath, data);
         }
       }
     
    
  • solr/core/src/java/org/apache/solr/util/FileUtils.java+24 0 modified
    @@ -103,4 +103,28 @@ public static Path createDirectories(Path path) throws IOException {
         }
         return Files.createDirectories(path);
       }
    +
    +  /**
    +   * Checks whether a child path falls under a particular parent
    +   *
    +   * <p>Useful for validating user-provided relative paths, which generally aren't expected to
    +   * "escape" a given parent/root directory. Parent and child paths are "normalized" by {@link
    +   * Path#normalize()}. This removes explicit backtracking (e.g. "../") though it will not resolve
    +   * symlinks if any are present in the provided Paths, so some forms of parent "escape" remain
    +   * undetected. Paths needn't exist as a file or directory for comparison purposes.
    +   *
    +   * <p>Note, this method does not consult the file system
    +   *
    +   * @param parent the path of a 'parent' node. Path must be syntactically valid but needn't exist.
    +   * @param potentialChild the path of a potential child. Typically obtained via:
    +   *     parent.resolve(relativeChildPath). Path must be syntactically valid but needn't exist.
    +   * @return true if 'potentialChild' nests under the provided 'parent', false otherwise.
    +   */
    +  public static boolean isPathAChildOfParent(Path parent, Path potentialChild) {
    +    final var normalizedParent = parent.toAbsolutePath().normalize();
    +    final var normalizedChild = potentialChild.toAbsolutePath().normalize();
    +
    +    return normalizedChild.startsWith(normalizedParent)
    +        && !normalizedChild.equals(normalizedParent);
    +  }
     }
    
  • solr/core/src/test/org/apache/solr/core/TestFileSystemConfigSetService.java+35 3 modified
    @@ -17,7 +17,9 @@
     package org.apache.solr.core;
     
     import static org.apache.solr.core.FileSystemConfigSetService.METADATA_FILE;
    +import static org.hamcrest.Matchers.hasItem;
     
    +import java.io.File;
     import java.io.IOException;
     import java.nio.charset.StandardCharsets;
     import java.nio.file.Files;
    @@ -49,13 +51,43 @@ public static void afterClass() throws Exception {
         fileSystemConfigSetService = null;
       }
     
    +  @Test
    +  public void testIgnoresFileUploadsOutsideOfConfigSetDirectory() throws IOException {
    +    final var initialNumConfigs = fileSystemConfigSetService.listConfigs().size();
    +    final String configName = "fileEscapeTestConfig";
    +    final var specificConfigSetBase = configSetBase.resolve(configName);
    +
    +    fileSystemConfigSetService.uploadConfig(configName, configset("cloud-minimal"));
    +    assertEquals(fileSystemConfigSetService.listConfigs().size(), initialNumConfigs + 1);
    +    assertTrue(fileSystemConfigSetService.checkConfigExists(configName));
    +
    +    // This succeeds, as the file is an allowed type and the path doesn't attempt to escape the
    +    // configset's root
    +    byte[] testdata = "test data".getBytes(StandardCharsets.UTF_8);
    +    fileSystemConfigSetService.uploadFileToConfig(configName, "validPath", testdata, true);
    +    final var knownFiles = fileSystemConfigSetService.getAllConfigFiles(configName);
    +    assertThat(knownFiles, hasItem("validPath"));
    +    assertTrue(Files.exists(specificConfigSetBase.resolve("validPath")));
    +
    +    // Each of these will fail "quietly" as ConfigSetService opts to log warnings but otherwise not
    +    // surface validation errors to enable bulk uploading
    +    final var invalidFilePaths =
    +        List.of(
    +            ".." + File.separator + "escapePath",
    +            "foo" + File.separator + ".." + File.separator + ".." + File.separator + "bar");
    +    for (String invalidFilePath : invalidFilePaths) {
    +      fileSystemConfigSetService.uploadFileToConfig(configName, invalidFilePath, testdata, true);
    +      assertFalse(Files.exists(specificConfigSetBase.resolve(invalidFilePath)));
    +    }
    +  }
    +
       @Test
       public void testUploadAndDeleteConfig() throws IOException {
    +    final var initialNumConfigs = fileSystemConfigSetService.listConfigs().size();
         String configName = "testconfig";
     
         fileSystemConfigSetService.uploadConfig(configName, configset("cloud-minimal"));
    -
    -    assertEquals(fileSystemConfigSetService.listConfigs().size(), 1);
    +    assertEquals(fileSystemConfigSetService.listConfigs().size(), initialNumConfigs + 1);
         assertTrue(fileSystemConfigSetService.checkConfigExists(configName));
     
         byte[] testdata = "test data".getBytes(StandardCharsets.UTF_8);
    @@ -79,7 +111,7 @@ public void testUploadAndDeleteConfig() throws IOException {
         assertEquals("[schema.xml, solrconfig.xml]", allConfigFiles.toString());
     
         fileSystemConfigSetService.copyConfig(configName, "copytestconfig");
    -    assertEquals(fileSystemConfigSetService.listConfigs().size(), 2);
    +    assertEquals(fileSystemConfigSetService.listConfigs().size(), initialNumConfigs + 2);
     
         allConfigFiles = fileSystemConfigSetService.getAllConfigFiles("copytestconfig");
         assertEquals("[schema.xml, solrconfig.xml]", allConfigFiles.toString());
    
  • solr/core/src/test/org/apache/solr/util/FileUtilsTest.java+50 0 modified
    @@ -17,15 +17,65 @@
     package org.apache.solr.util;
     
     import java.io.File;
    +import java.nio.file.Path;
     import org.apache.solr.SolrTestCase;
    +import org.junit.Test;
     
     public class FileUtilsTest extends SolrTestCase {
     
    +  @Test
       public void testResolve() {
         String cwd = new File(".").getAbsolutePath();
         assertEquals(new File("conf/data"), FileUtils.resolvePath(new File("conf"), "data"));
         assertEquals(
             new File(cwd + "/conf/data"), FileUtils.resolvePath(new File(cwd + "/conf"), "data"));
         assertEquals(new File(cwd + "/data"), FileUtils.resolvePath(new File("conf"), cwd + "/data"));
       }
    +
    +  @Test
    +  public void testDetectsPathEscape() {
    +    final var parent = Path.of(".");
    +
    +    // Allows simple child
    +    assertTrue(FileUtils.isPathAChildOfParent(parent, parent.resolve("child")));
    +
    +    // Allows "./" prefixed child
    +    assertTrue(FileUtils.isPathAChildOfParent(parent, parent.resolve(buildPath(".", "child"))));
    +
    +    // Allows nested child
    +    assertTrue(
    +        FileUtils.isPathAChildOfParent(parent, parent.resolve(buildPath("nested", "child"))));
    +
    +    // Allows backtracking, provided it stays "under" parent
    +    assertTrue(
    +        FileUtils.isPathAChildOfParent(
    +            parent, parent.resolve(buildPath("child1", "..", "child2"))));
    +    assertTrue(
    +        FileUtils.isPathAChildOfParent(
    +            parent, parent.resolve(buildPath("child", "grandchild1", "..", "grandchild2"))));
    +
    +    // Prevents identical path
    +    assertFalse(FileUtils.isPathAChildOfParent(parent, parent));
    +
    +    // Detects sibling of parent
    +    assertFalse(FileUtils.isPathAChildOfParent(parent, parent.resolve(buildPath("..", "sibling"))));
    +
    +    // Detects "grandparent" of parent
    +    assertFalse(FileUtils.isPathAChildOfParent(parent, parent.resolve("..")));
    +
    +    // Detects many-layered backtracking
    +    assertFalse(
    +        FileUtils.isPathAChildOfParent(parent, parent.resolve(buildPath("..", "..", "..", ".."))));
    +  }
    +
    +  private static String buildPath(String... pathSegments) {
    +    final var sb = new StringBuilder();
    +    for (int i = 0; i < pathSegments.length; i++) {
    +      sb.append(pathSegments[i]);
    +      if (i < pathSegments.length - 1) {
    +        sb.append(File.separator);
    +      }
    +    }
    +    return sb.toString();
    +  }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.