Coordinated Disclosure Timeline
- 2022-01-07: Report sent to security at apache.org.
- 2022-01-09: Receipt acknowledged.
- 2022-01-09: Issue fixed in the main branch.
- 2022-01-09: Apache considers it theoretical issue and decides not to assign a tracking CVE for the time being.
- 2022-03-19: v0.10.0 with the fix released.
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.