Skip to content

Commit b1d5d5b

Browse files
committed
C#: ZipSlip - Refine StartsWith sanitizer.
ZipSlip can be avoided by checking that the combined and resolved path `StartsWith` the appropriate destination directory. Refine the `StartsWith` sanitizer to: * Consider expressions guarded by an appropriate StartsWith check to be sanitized. * Consider a StartsWith check to be inappropriate if it is checking the result of `Path.Combine`, as that has not been appropriately resolved. Tests have been updated to reflect this refinement.
1 parent fc925d4 commit b1d5d5b

File tree

3 files changed

+32
-10
lines changed

3 files changed

+32
-10
lines changed

csharp/ql/src/semmle/code/csharp/security/dataflow/ZipSlip.qll

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import csharp
55

66
module ZipSlip {
7+
import semmle.code.csharp.controlflow.Guards
8+
79
/**
810
* A data flow source for unsafe zip extraction.
911
*/
@@ -125,16 +127,26 @@ module ZipSlip {
125127
}
126128

127129
/**
128-
* A qualifier in a call to `StartsWith` string method.
130+
* An expression which is guarded by a call to `StartsWith`.
129131
*
130132
* A call to a String method such as `StartsWith` can indicate a check for a
131133
* relative path, or a check against the destination folder for whitelisted/target path, etc.
132134
*/
133135
class StringCheckSanitizer extends Sanitizer {
134136
StringCheckSanitizer() {
135-
exists(MethodCall mc |
136-
mc.getTarget().hasQualifiedName("System.String", "StartsWith") |
137-
this.asExpr() = mc.getQualifier()
137+
exists(GuardedExpr ge, MethodCall mc, Expr startsWithQualifier |
138+
ge = this.asExpr() and
139+
ge.isGuardedBy(mc, startsWithQualifier, true) |
140+
mc.getTarget().hasQualifiedName("System.String", "StartsWith") and
141+
mc.getQualifier() = startsWithQualifier and
142+
/*
143+
* A StartsWith check against Path.Combine is not sufficient, because the ".." elements have
144+
* not yet been resolved.
145+
*/
146+
not exists(MethodCall combineCall |
147+
combineCall.getTarget().hasQualifiedName("System.IO.Path", "Combine") and
148+
DataFlow::localFlow(DataFlow::exprNode(combineCall), DataFlow::exprNode(startsWithQualifier))
149+
)
138150
)
139151
}
140152
}

csharp/ql/test/query-tests/Security Features/CWE-022/ZipSlip/ZipSlip.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ public static void UnzipFileByFile(ZipArchive archive,
3131
string destFilePath = Path.Combine(destDirectory, fullPath);
3232
entry.ExtractToFile(destFilePath, true);
3333

34-
// GOOD: some check on destination.
34+
// BAD: destFilePath isn't fully resolved, so may still contain ..
35+
if (destFilePath.StartsWith(destDirectory))
36+
entry.ExtractToFile(destFilePath, true);
37+
38+
// BAD
39+
destFilePath = Path.GetFullPath(Path.Combine(destDirectory, fullPath));
40+
entry.ExtractToFile(destFilePath, true);
41+
42+
// GOOD: a check for StartsWith against a fully resolved path
3543
if (destFilePath.StartsWith(destDirectory))
3644
entry.ExtractToFile(destFilePath, true);
3745
}
@@ -115,7 +123,7 @@ static void Main(string[] args)
115123
// GOOD: the path is checked in this extension method
116124
archive.ExtractToDirectory(targetPath);
117125

118-
UnzipToStream(file, targetPath);
126+
UnzipToStream(file, targetPath);
119127
}
120128
}
121129
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
| ZipSlip.cs:24:41:24:52 | access to local variable destFileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:19:31:19:44 | access to property FullName | item path |
22
| ZipSlip.cs:32:41:32:52 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:16:52:16:65 | access to property FullName | item path |
3-
| ZipSlip.cs:61:74:61:85 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:54:72:54:85 | access to property FullName | item path |
4-
| ZipSlip.cs:68:71:68:82 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:54:72:54:85 | access to property FullName | item path |
5-
| ZipSlip.cs:75:57:75:68 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:54:72:54:85 | access to property FullName | item path |
6-
| ZipSlip.cs:83:58:83:69 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:54:72:54:85 | access to property FullName | item path |
3+
| ZipSlip.cs:36:45:36:56 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:16:52:16:65 | access to property FullName | item path |
4+
| ZipSlip.cs:40:41:40:52 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:16:52:16:65 | access to property FullName | item path |
5+
| ZipSlip.cs:69:74:69:85 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:62:72:62:85 | access to property FullName | item path |
6+
| ZipSlip.cs:76:71:76:82 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:62:72:62:85 | access to property FullName | item path |
7+
| ZipSlip.cs:83:57:83:68 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:62:72:62:85 | access to property FullName | item path |
8+
| ZipSlip.cs:91:58:91:69 | access to local variable destFilePath | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlip.cs:62:72:62:85 | access to property FullName | item path |
79
| ZipSlipBad.cs:10:29:10:40 | access to local variable destFileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.cs:9:59:9:72 | access to property FullName | item path |

0 commit comments

Comments
 (0)