diff --git a/src/Zip/ZipEntry.Extract.cs b/src/Zip/ZipEntry.Extract.cs index 945de82..f8e0d90 100644 --- a/src/Zip/ZipEntry.Extract.cs +++ b/src/Zip/ZipEntry.Extract.cs @@ -1389,6 +1389,16 @@ bool IsDoneWithOutputToBaseDir(string baseDir, out string outFileName) // workitem 10639 outFileName = outFileName.Replace('/', Path.DirectorySeparatorChar); + + // Resolve any directory traversal sequence and compare the result with the intended base directory + // where the file or folder will be created. + // https://gist.github.com/thomas-chauchefoin-bentley-systems/855218959116f870f08857cce2aec731 + var canonicalOutPath = Path.GetFullPath(outFileName); + var canonicalBaseDir = Path.GetFullPath(baseDir); + if (!canonicalOutPath.StartsWith(canonicalBaseDir, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException(string.Format("Extracting {0} would write to {1}, outside of {2}; rejecting.", outFileName, canonicalOutPath, canonicalBaseDir)); + } // check if it is a directory if (IsDirectory || FileName.EndsWith("/")) diff --git a/test/BasicTest.cs b/test/BasicTest.cs index ab045ed..b09219b 100644 --- a/test/BasicTest.cs +++ b/test/BasicTest.cs @@ -1,14 +1,52 @@ using Xunit; +using Xunit.Abstractions; -namespace Ionic.Zip.Tests +namespace Ionic.Zip.Tests; + +public class BasicTest { - public class BasicTest + private readonly ITestOutputHelper _output; + + public BasicTest(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void TestCreate() + { + var result = new ZipFile(); + Assert.NotNull(result); + } + + [Fact] + public void Extract_ZipWithAbsolutePathsOutside() { - [Fact] - public void TestCreate() + Assert.Throws(() => Extract_ZipFile("absolute-path-traversal.zip")); + Assert.False(File.Exists(@"C:\Windows\Temp\foo")); + } + + private void Extract_ZipFile(string fileName) + { + var sourceDir = Directory.GetCurrentDirectory(); + _output.WriteLine("Current Dir: {0}", sourceDir); + + var fqFileName = Path.Combine(sourceDir, "zips", fileName); + + _output.WriteLine("Reading zip file: '{0}'", fqFileName); + using var zip = ZipFile.Read(fqFileName); + const string extractDir = "extract"; + foreach (ZipEntry e in zip) { - var result = new ZipFile(); - Assert.NotNull(result); + _output.WriteLine("{1,-22} {2,9} {3,5:F0}% {4,9} {5,3} {6:X8} {0}", + e.FileName, + e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"), + e.UncompressedSize, + e.CompressionRatio, + e.CompressedSize, + (e.UsesEncryption) ? "Y" : "N", + e.Crc); + e.Extract(extractDir); } } -} +} \ No newline at end of file diff --git a/test/ProDotNetZipNetStandardTest.csproj b/test/ProDotNetZipNetStandardTest.csproj index ae78f3f..3e7a051 100644 --- a/test/ProDotNetZipNetStandardTest.csproj +++ b/test/ProDotNetZipNetStandardTest.csproj @@ -26,4 +26,10 @@ + + + Always + + + diff --git a/test/zips/absolute-path-traversal.zip b/test/zips/absolute-path-traversal.zip new file mode 100644 index 0000000..ac62b13 Binary files /dev/null and b/test/zips/absolute-path-traversal.zip differ