Skip to content

Commit 10a8863

Browse files
authored
Merge pull request #237 from microsoft/fix-fps-in-tainted-path
C#: Fix FPs (and a small FN) in `cs/path-injection`
2 parents 0e887d8 + 2186fef commit 10a8863

File tree

3 files changed

+52
-5
lines changed

3 files changed

+52
-5
lines changed

csharp/ql/lib/semmle/code/csharp/security/dataflow/TaintedPathQuery.qll

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,40 @@ private class GetFullPathStep extends PathNormalizationStep {
5252
}
5353
}
5454

55+
/** Holds if `e` may evaluate to an absolute path. */
56+
bindingset[e]
57+
pragma[inline_late]
58+
private predicate isAbsolute(Expr e) {
59+
exists(Expr absolute | DataFlow::localExprFlow(absolute, e) |
60+
exists(Call call | absolute = call |
61+
call.getARuntimeTarget()
62+
.hasFullyQualifiedName(["System.Web.HttpServerUtilityBase", "System.Web.HttpRequest"],
63+
"MapPath")
64+
or
65+
call.getARuntimeTarget().hasFullyQualifiedName("System.IO.Path", "GetFullPath")
66+
or
67+
call.getARuntimeTarget().hasFullyQualifiedName("System.IO.Directory", "GetCurrentDirectory")
68+
)
69+
or
70+
exists(PropertyRead read | absolute = read |
71+
read.getTarget().hasFullyQualifiedName("System", "Environment", "CurrentDirectory")
72+
)
73+
)
74+
}
75+
76+
private class PathCombineStep extends PathNormalizationStep {
77+
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
78+
exists(Call call |
79+
// The result of `Path.Combine(x, y)` is an absolute path when `x` is an
80+
// absolute path.
81+
call.getARuntimeTarget().hasFullyQualifiedName("System.IO.Path", "Combine") and
82+
isAbsolute(call.getArgument(0)) and
83+
n1.asExpr() = call.getArgument(1) and
84+
n2.asExpr() = call
85+
)
86+
}
87+
}
88+
5589
/**
5690
* A taint-tracking configuration for uncontrolled data in path expression vulnerabilities.
5791
*/
@@ -64,10 +98,9 @@ private module TaintedPathConfig implements DataFlow::StateConfigSig {
6498
source instanceof Source and state = NotNormalized()
6599
}
66100

67-
predicate isSink(DataFlow::Node sink, FlowState state) {
68-
sink instanceof Sink and
69-
exists(state)
70-
}
101+
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
102+
103+
predicate isSink(DataFlow::Node sink, FlowState state) { none() }
71104

72105
predicate isAdditionalFlowStep(DataFlow::Node n1, FlowState s1, DataFlow::Node n2, FlowState s2) {
73106
any(PathNormalizationStep step).isAdditionalFlowStep(n1, n2) and
@@ -78,7 +111,7 @@ private module TaintedPathConfig implements DataFlow::StateConfigSig {
78111
predicate isBarrier(DataFlow::Node node, FlowState state) { node.(Sanitizer).isBarrier(state) }
79112

80113
predicate isBarrierOut(DataFlow::Node node, FlowState state) {
81-
isAdditionalFlowStep(node, state, _, _)
114+
isAdditionalFlowStep(_, state, node, _)
82115
}
83116
}
84117

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ public void ProcessRequest(HttpContext ctx)
6161
{
6262
File.ReadAllText(fullPath); // GOOD
6363
}
64+
65+
// This test ensures that we can flow through `Path.GetFullPath` and still get a result.
66+
ctx.Response.Write(File.ReadAllText(path)); // BAD
67+
68+
string absolutePath = ctx.Request.MapPath("~MyTempFile");
69+
string fullPath2 = Path.Combine(absolutePath, path);
70+
if (fullPath2.StartsWith(absolutePath + Path.DirectorySeparatorChar)) {
71+
File.ReadAllText(fullPath2); // GOOD
72+
}
6473
}
6574

6675
public bool IsReusable

csharp/ql/test/query-tests/Security Features/CWE-022/TaintedPath/TaintedPath.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@
66
| TaintedPath.cs:36:25:36:31 | access to local variable badPath | TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:36:25:36:31 | access to local variable badPath | This path depends on a $@. | TaintedPath.cs:10:23:10:45 | access to property QueryString | user-provided value |
77
| TaintedPath.cs:38:49:38:55 | access to local variable badPath | TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:38:49:38:55 | access to local variable badPath | This path depends on a $@. | TaintedPath.cs:10:23:10:45 | access to property QueryString | user-provided value |
88
| TaintedPath.cs:51:26:51:29 | access to local variable path | TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:51:26:51:29 | access to local variable path | This path depends on a $@. | TaintedPath.cs:10:23:10:45 | access to property QueryString | user-provided value |
9+
| TaintedPath.cs:66:45:66:48 | access to local variable path | TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:66:45:66:48 | access to local variable path | This path depends on a $@. | TaintedPath.cs:10:23:10:45 | access to property QueryString | user-provided value |
910
edges
1011
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:12:50:12:53 | access to local variable path | provenance | |
1112
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:17:51:17:54 | access to local variable path | provenance | |
1213
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:25:30:25:33 | access to local variable path | provenance | |
1314
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:31:30:31:33 | access to local variable path | provenance | |
1415
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:35:16:35:22 | access to local variable badPath : String | provenance | |
1516
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:51:26:51:29 | access to local variable path | provenance | |
17+
| TaintedPath.cs:10:16:10:19 | access to local variable path : String | TaintedPath.cs:59:44:59:47 | access to local variable path : String | provenance | |
1618
| TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:10:16:10:19 | access to local variable path : String | provenance | |
1719
| TaintedPath.cs:10:23:10:45 | access to property QueryString : NameValueCollection | TaintedPath.cs:10:23:10:53 | access to indexer : String | provenance | MaD:1 |
1820
| TaintedPath.cs:10:23:10:53 | access to indexer : String | TaintedPath.cs:10:16:10:19 | access to local variable path : String | provenance | |
1921
| TaintedPath.cs:35:16:35:22 | access to local variable badPath : String | TaintedPath.cs:36:25:36:31 | access to local variable badPath | provenance | |
2022
| TaintedPath.cs:35:16:35:22 | access to local variable badPath : String | TaintedPath.cs:38:49:38:55 | access to local variable badPath | provenance | |
23+
| TaintedPath.cs:59:44:59:47 | access to local variable path : String | TaintedPath.cs:66:45:66:48 | access to local variable path | provenance | |
2124
models
2225
| 1 | Summary: System.Collections.Specialized; NameValueCollection; false; get_Item; (System.String); ; Argument[this]; ReturnValue; taint; df-generated |
2326
nodes
@@ -32,4 +35,6 @@ nodes
3235
| TaintedPath.cs:36:25:36:31 | access to local variable badPath | semmle.label | access to local variable badPath |
3336
| TaintedPath.cs:38:49:38:55 | access to local variable badPath | semmle.label | access to local variable badPath |
3437
| TaintedPath.cs:51:26:51:29 | access to local variable path | semmle.label | access to local variable path |
38+
| TaintedPath.cs:59:44:59:47 | access to local variable path : String | semmle.label | access to local variable path : String |
39+
| TaintedPath.cs:66:45:66:48 | access to local variable path | semmle.label | access to local variable path |
3540
subpaths

0 commit comments

Comments
 (0)