skip to content
Back to GitHub.com
Home Bounties Research Advisories CodeQL Wall of Fame Get Involved Events
April 1, 2022

GHSL-2022-004: Partial path traversal in Apache Pinot

Jaroslav Lobacevski

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.