Why is this an issue?
Zip slip is a special case of path injection. It occurs when an application uses the name of an archive entry to construct a file path and access
this file without validating its path first.
This rule will consider all archives untrusted, assuming they have been created outside the application file system.
A user with malicious intent would inject specially crafted values, such as ../
, in the archive entry name to change the initial
intended path. The resulting path would resolve somewhere in the filesystem where the user should not normally have access.
What is the potential impact?
A web application is vulnerable to Zip Slip and an attacker is able to exploit it by submitting an archive he controls.
The files that can be affected are limited by the permission of the process that runs the application. Worst case scenario: the process runs with
root privileges on Linux, and therefore any file can be affected.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
Override arbitrary files
The application opens the archive to copy its entries to the file system. The entries' names contain path traversal payloads for existing files in
the system, which are overwritten once the entries are copied. The vulnerability is exploited to corrupt files critical for the application or
operating system to work properly.
It could result in data being lost or the application being unavailable.
How to fix it in .NET
Code examples
The following code is vulnerable to Zip Slip as it is constructing a path using an archive entry name. This path is then used to copy a file
without being validated first. Therefore, it can be leveraged by an attacker to overwrite arbitrary files.
Noncompliant code example
public class ExampleController : Controller
{
private static string TargetDirectory = "/example/directory/";
public void ExtractEntry(IEnumerator<ZipArchiveEntry> entriesEnumerator)
{
ZipArchiveEntry entry = entriesEnumerator.Current;
string destinationPath = Path.Combine(TargetDirectory, entry.FullName);
entry.ExtractToFile(destinationPath);
}
}
Compliant solution
public class ExampleController : Controller
{
private static string TargetDirectory = "/example/directory/";
public void ExtractEntry(IEnumerator<ZipArchiveEntry> entriesEnumerator)
{
ZipArchiveEntry entry = entriesEnumerator.Current;
string destinationPath = Path.Combine(TargetDirectory, entry.FullName);
string canonicalDestinationPath = Path.GetFullPath(destinationPath);
if (canonicalDestinationPath.StartsWith(TargetDirectory, StringComparison.Ordinal))
{
entry.ExtractToFile(canonicalDestinationPath);
}
}
}
How does this work?
The universal way to prevent Zip Slip is to validate the paths constructed from untrusted archive entry names.
The validation should be done as follow:
- Resolve the canonical path of the file by using methods like
System.IO.Path.GetFullPath
or
System.IO.Path.GetFileName
. This will resolve relative path or path components like ../
and removes any ambiguity
regarding the file’s location.
- Check that the canonical path is within the directory where the file should be located.
- Ensure the target directory path ends with a forward slash to prevent partial path traversal, for example, /base/dirmalicious
starts with /base/dir but does not start with /base/dir/.
Pitfalls
Partial Path Traversal
When validating untrusted paths by checking if they start with a trusted folder name, ensure the validation strings all contain a path
separator as the last character.
A partial path traversal vulnerability can be unintentionally introduced into the application without a
path separator as the last character of the validation strings.
For example, the following code is vulnerable to partial path injection. Note that the string TargetDirectory
does not end with a path
separator:
static private String TargetDirectory = "/Users/John";
public void ExtractEntry(IEnumerator<ZipArchiveEntry> entriesEnumerator)
{
ZipArchiveEntry entry = entriesEnumerator.Current;
string canonicalDestinationPath = Path.GetFullPath(TargetDirectory);
if (canonicalDestinationPath.StartsWith(TargetDirectory, StringComparison.Ordinal))
{
entry.ExtractToFile(canonicalDestinationPath);
}
}
This check can be bypassed because "/Users/Johnny".startsWith("/Users/John")
returns true
. Thus, for validation,
"/Users/John"
should actually be "/Users/John/"
.
Warning: Some functions remove the terminating path separator in their return value.
The validation code should be tested to
ensure that it cannot be impacted by this issue.
Here is a real-life example of this vulnerability.
Resources
Documentation
- snyk - Zip Slip Vulnerability
Standards