C#: ZipSlip - Add qhelp file.

This adds a help file which describes the problem, provides
recommendations on how to fix it and an example.
This commit is contained in:
Luke Cartey
2018-08-20 15:25:17 +01:00
parent 99d1cf70be
commit fa78d04f18
3 changed files with 95 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting files from a malicious zip archive without validating that the destination file path
is within the destination directory can cause files outside the destination directory to be
overwritten, due to the possible presence of directory traversal elements ("..") in archive
paths.</p>
<p>Zip archives contain archive entries representing each file in the archive. These entries
include a file path for the entry, but these file paths are not restricted and may contain
unexpected special elements such as the directory traversal element (".."). If these file paths are
use to determine an output file to write the contents of the archive item to, then the file may be
written to an unexpected location. This can result in sensitive information being revealed or
deleted, or an attacker being able to influence behavior by modifying unexpected files.</p>
<p>For example, if a zip file contains a file entry <code>..\sneaky-file</code>, and the zip file
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
written to <code>c:\sneaky-file</code>.</p>
</overview>
<recommendation>
<p>Ensure that output paths constructed from zip archive entries are validated to prevent writing
files to unexpected locations.</p>
<p>The recommended way of writing an output file from a zip archive entry is to:</p>
<ol>
<li>Use <code>Path.Combine(destinationDirectory, archiveEntry.FullName)</code> to determine the raw
output path.</li>
<li>Use <code>Path.GetFullPath(..)</code> on the raw output path to resolve any directory traversal
elements.</li>
<li>Use <code>Path.GetFullPath(destinationDirectory + Path.DirectorySeparatorChar)</code> to
determine the fully resolved path of the destination directory.</li>
<li>Validate that the resolved output path <code>StartsWith</code> the resolved destination
directory, aborting if this is not true.</li>
</ol>
<p>Another alternative is to validate archive entries against a whitelist of expected files.</p>
</recommendation>
<example>
<p>In this example, a file path taken from a zip archive item entry is combined with a
destination directory, and the result is used as the destination file path without verifying that
the result is within the destination directory. If provided with a zip file containing an archive
path like <code>..\sneaky-file</code>, then this file would be written outside of the destination
directory.</p>
<sample src="ZipSlipBroken.cs" />
<p>In order to fix this vulnerability, we need to make three changes. Firstly, we need to resolve any
directory traversal or other special characters in the path by using <code>Path.GetFullPath</code>.
Secondly, we need to identify the destination output directory, again using
<code>Path.GetFullPath</code>, this time on the output directory. Finally, we need to ensure that
the resolved output starts with the resolved destination directory, and throw and exception if this
is not the case.</p>
<sample src="ZipSlipFixed.cs" />
</example>
<references>
<li>
Snyk:
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
</li>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Path_traversal">Path Traversal</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,7 @@
public static void WriteToDirectory(IArchiveEntry entry,
string destDirectory,
ExtractionOptions options){
string file = Path.GetFileName(entry.Key);
string destFileName = Path.Combine(destDirectory, file);
entry.WriteToFile(destFileName, options);
}

View File

@@ -0,0 +1,11 @@
public static void WriteToDirectory(IArchiveEntry entry,
string destDirectory,
ExtractionOptions options){
string file = Path.GetFileName(entry.Key);
string destFileName = Path.GetFullPath(Path.Combine(destDirecory, entry.Key));
string fullDestDirPath = Path.GetFullPath(destDirectory + Path.DirectorySeparatorChar);
if (!destFileName.StartsWith(fullDestDirPath)) {
throw new ExtractionException("Entry is outside of the target dir: " + destFileName);
}
entry.WriteToFile(destFileName, options);
}