Coordinated Disclosure Timeline

Summary

Partial path traversal allows to break out of expected folder.

Product

Apache Pinot

Tested Version

Latest revision 4bae48b on Linux

Details

Issue 1: Partial path traversal (GHSL-2022-004)

untar validates [2] [3] if the output file path starts with the expected outputDirCanonicalPath [1].

  /**
   * Un-tars an inputstream of a tar.gz file into a directory, returns all the untarred files/directories.
   * <p>For security reason, the untarred files must reside in the output directory.
   */
  public static List<File> untar(InputStream inputStream, File outputDir)
      throws IOException {
    String outputDirCanonicalPath = outputDir.getCanonicalPath(); //<----------- [1]
...
      ArchiveEntry entry;
      while ((entry = tarGzIn.getNextEntry()) != null) {
        String entryName = entry.getName();
        String[] parts = StringUtils.split(entryName, ENTRY_NAME_SEPARATOR);
        File outputFile = outputDir;
        for (String part : parts) {
          outputFile = new File(outputFile, part);
        }
        if (entry.isDirectory()) {
          if (!outputFile.getCanonicalPath().startsWith(outputDirCanonicalPath)) { //<----------- [2]
            throw new IOException(String
                .format("Trying to create directory: %s outside of the output directory: %s", outputFile, outputDir));
          }
...
        } else {
          File parentFile = outputFile.getParentFile();
          if (!parentFile.getCanonicalPath().startsWith(outputDirCanonicalPath)) { //<----------- [3]
            throw new IOException(String
                .format("Trying to create directory: %s outside of the output directory: %s", parentFile, outputDir));
          }
...
          try (OutputStream out = Files.newOutputStream(outputFile.toPath())) {
            IOUtils.copy(tarGzIn, out);
          }

getCanonicalPath transforms the path into a canonical form preventing such attack types as .. in path segments. If the result of outputDir.getCanonicalPath() is not slash terminated it allows for partial path traversal. Consider "/usr/outnot".startsWith("/usr/out"). The check is bypassed although it is not the out directory. The terminating slash may be removed in various places. On Linux println(new File("/var/")) returns /var, but println(new File("/var", "/")) - /var/, however println(new File("/var", "/").getCanonicalPath()) - /var.

Impact

This issue allows to break out of expected folder.

Credit

This issue was discovered and reported by GHSL team member @JarLob (Jaroslav Lobačevski).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2022-004 in any communication regarding this issue.