Skip to content

Commit 242fba3

Browse files
author
Denis Levin
committed
cs: Query for ZipSlip vulnerability (CVE-2018-1002200)
Initial check in to validate the tests
1 parent 9d2dd97 commit 242fba3

File tree

6 files changed

+215
-0
lines changed

6 files changed

+215
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @name Potential ZipSlip vulnerability
3+
* @description When extracting files from an archive, don't add archive item's path to the target file system path. Archive path can be relative and can lead to
4+
* file system access outside of the expected file system target path, leading to malicious config changes and remote code execution via lay-and-wait technique
5+
* @kind problem
6+
* @id cs/zipslip
7+
* @problem.severity error
8+
* @precision high
9+
* @tags security
10+
* external/cwe/cwe-022
11+
*/
12+
13+
import csharp
14+
15+
// access to full name of the archive item
16+
Expr archiveFullName(PropertyAccess pa) {
17+
pa.getTarget().getDeclaringType().hasQualifiedName("System.IO.Compression.ZipArchiveEntry")
18+
and pa.getTarget().getName() = "FullName"
19+
and result = pa
20+
}
21+
22+
// argument to extract to file extension method
23+
Expr compressionExtractToFileArgument(MethodCall mc) {
24+
mc.getTarget().hasQualifiedName("System.IO.Compression.ZipFileExtensions", "ExtractToFile")
25+
and result = mc.getArgumentForName("destinationFileName")
26+
}
27+
28+
// File Stream created from tainted file name through File.Open/File.Create
29+
Expr fileOpenArgument(MethodCall mc) {
30+
(mc.getTarget().hasQualifiedName("System.IO.File", "Open") or
31+
mc.getTarget().hasQualifiedName("System.IO.File", "OpenWrite") or
32+
mc.getTarget().hasQualifiedName("System.IO.File", "Create"))
33+
and result = mc.getArgumentForName("path")
34+
}
35+
36+
// File Stream created from tainted file name passed directly to the constructor
37+
Expr streamConstructorArgument(ObjectCreation oc) {
38+
oc.getTarget().getDeclaringType().hasQualifiedName("System.IO.FileStream")
39+
and result = oc.getArgumentForName("path")
40+
}
41+
42+
// constructor to FileInfo can take tainted file name and subsequently be used to open file stream
43+
Expr fileInfoConstructorArgument(ObjectCreation oc) {
44+
oc.getTarget().getDeclaringType().hasQualifiedName("System.IO.FileInfo")
45+
and result = oc.getArgumentForName("fileName")
46+
}
47+
// extracting just file name, not the full path
48+
Expr fileNameExtraction(MethodCall mc) {
49+
mc.getTarget().hasQualifiedName("System.IO.Path", "GetFileName")
50+
and result = mc.getAnArgument()
51+
}
52+
53+
// Checks the string for relative path, or checks the destination folder for whitelisted/target path, etc.
54+
Expr stringCheck(MethodCall mc) {
55+
(mc.getTarget().hasQualifiedName("System.String", "StartsWith") or
56+
mc.getTarget().hasQualifiedName("System.String", "Substring"))
57+
and result = mc.getQualifier()
58+
}
59+
60+
// Taint tracking configuration for ZipSlip
61+
class ZipSlipTaintTrackingConfiguration extends TaintTracking::Configuration {
62+
ZipSlipTaintTrackingConfiguration() {
63+
this = "ZipSlipTaintTracking"
64+
}
65+
66+
override predicate isSource(DataFlow::Node source) {
67+
exists(PropertyAccess pa |
68+
source.asExpr() = archiveFullName(pa))
69+
}
70+
71+
override predicate isSink(DataFlow::Node sink) {
72+
exists(MethodCall mc |
73+
sink.asExpr() = compressionExtractToFileArgument(mc) or
74+
sink.asExpr() = fileOpenArgument(mc))
75+
or
76+
exists(ObjectCreation oc |
77+
sink.asExpr() = streamConstructorArgument(oc) or
78+
sink.asExpr() = fileInfoConstructorArgument(oc))
79+
}
80+
81+
override predicate isSanitizer(DataFlow::Node node) {
82+
exists(MethodCall mc |
83+
node.asExpr() = fileNameExtraction(mc) or
84+
node.asExpr() = stringCheck(mc))
85+
}
86+
}
87+
88+
89+
from ZipSlipTaintTrackingConfiguration zipTaintTracking, DataFlow::Node source, DataFlow::Node sink
90+
where zipTaintTracking.hasFlow(source, sink)
91+
select sink, "Make sure to sanitize relative archive item path before creating path for file extraction if the source of $@ is untrusted", source, "zip archive"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// semmle-extractor-options: /nostdlib /noconfig /r:${env.windir}\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll /r:${env.windir}\Microsoft.NET\Framework64\v4.0.30319\System.dll /r:${env.windir}\Microsoft.NET\Framework64\v4.0.30319\System.IO.Compression.dll /r:${env.windir}\Microsoft.NET\Framework64\v4.0.30319\System.IO.Compression.FileSystem.dll
2+
using System;
3+
using System.IO;
4+
using System.IO.Compression;
5+
6+
namespace ZipSlip
7+
{
8+
class Program
9+
{
10+
11+
public static void UnzipFileByFile(ZipArchive archive,
12+
string destDirectory)
13+
{
14+
foreach (var entry in archive.Entries)
15+
{
16+
string fullPath = Path.GetFullPath(entry.FullName);
17+
string fileName = Path.GetFileName(entry.FullName);
18+
string filename = entry.Name;
19+
string file = entry.FullName;
20+
if (!string.IsNullOrEmpty(file))
21+
{
22+
// BAD
23+
string destFileName = Path.Combine(destDirectory, file);
24+
entry.ExtractToFile(destFileName, true);
25+
26+
// GOOD
27+
string sanitizedFileName = Path.Combine(destDirectory, fileName);
28+
entry.ExtractToFile(sanitizedFileName, true);
29+
30+
// BAD
31+
string destFilePath = Path.Combine(destDirectory, fullPath);
32+
entry.ExtractToFile(destFilePath, true);
33+
34+
// GOOD: some check on destination.
35+
if (destFilePath.StartsWith(destDirectory))
36+
entry.ExtractToFile(destFilePath, true);
37+
}
38+
}
39+
}
40+
41+
private static int UnzipToStream(Stream zipStream, string installDir)
42+
{
43+
int returnCode = 0;
44+
try
45+
{
46+
// normalize InstallDir for use in check below
47+
var InstallDir = Path.GetFullPath(installDir + Path.DirectorySeparatorChar);
48+
49+
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Read))
50+
{
51+
foreach (ZipArchiveEntry entry in archive.Entries)
52+
{
53+
// figure out where we are putting the file
54+
string destFilePath = Path.Combine(InstallDir, entry.FullName);
55+
56+
Directory.CreateDirectory(Path.GetDirectoryName(destFilePath));
57+
58+
using (Stream archiveFileStream = entry.Open())
59+
{
60+
// BAD: writing to file stream
61+
using (Stream tfsFileStream = new FileStream(destFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
62+
{
63+
Console.WriteLine(@"Writing ""{0}""", destFilePath);
64+
archiveFileStream.CopyTo(tfsFileStream);
65+
}
66+
67+
// BAD: can do it this way too
68+
using (Stream tfsFileStream = File.Create(destFilePath))
69+
{
70+
Console.WriteLine(@"Writing ""{0}""", destFilePath);
71+
archiveFileStream.CopyTo(tfsFileStream);
72+
}
73+
74+
// BAD: creating stream using fileInfo
75+
var fileInfo = new FileInfo(destFilePath);
76+
using (FileStream fs = fileInfo.OpenWrite())
77+
{
78+
Console.WriteLine(@"Writing ""{0}""", destFilePath);
79+
archiveFileStream.CopyTo(fs);
80+
}
81+
82+
// BAD: creating stream using fileInfo
83+
var fileInfo1 = new FileInfo(destFilePath);
84+
using (FileStream fs = fileInfo1.Open(FileMode.Create))
85+
{
86+
Console.WriteLine(@"Writing ""{0}""", destFilePath);
87+
archiveFileStream.CopyTo(fs);
88+
}
89+
}
90+
}
91+
}
92+
}
93+
catch (Exception ex)
94+
{
95+
Console.WriteLine("Error patching files: {0}", ex.ToString());
96+
returnCode = -1;
97+
}
98+
99+
return returnCode;
100+
}
101+
102+
static void Main(string[] args)
103+
{
104+
string zipFileName;
105+
zipFileName = args[0];
106+
107+
string targetPath = args.Length == 2 ? args[1] : ".";
108+
109+
using (FileStream file = new FileStream(zipFileName, FileMode.Open))
110+
{
111+
using (ZipArchive archive = new ZipArchive(file, ZipArchiveMode.Read))
112+
{
113+
UnzipFileByFile(archive, targetPath);
114+
115+
// GOOD: the path is checked in this extension method
116+
archive.ExtractToDirectory(targetPath);
117+
118+
UnzipToStream(file, targetPath);
119+
}
120+
}
121+
}
122+
}
123+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security Features/CWE-022/ZipSlip.ql

0 commit comments

Comments
 (0)