Skip to content

Commit 3bf0d66

Browse files
authored
Merge pull request #13906 from geoffw0/commandinject2
Swift: Add tests and develop command injection query
2 parents 7a7dc9b + b2d3d46 commit 3bf0d66

File tree

11 files changed

+541
-96
lines changed

11 files changed

+541
-96
lines changed

swift/ql/lib/codeql/swift/frameworks/StandardLibrary/NsUrl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ private import codeql.swift.dataflow.ExternalFlow
1010
*/
1111
private class NsUrlSummaries extends SummaryModelCsv {
1212
override predicate row(string row) {
13-
row = ";NSURL;true;init(string:);(String);;Argument[0];ReturnValue;taint"
13+
row = ";NSURL;true;init(string:);(String);;Argument[0];ReturnValue.OptionalSome;taint"
1414
}
1515
}

swift/ql/lib/codeql/swift/frameworks/StandardLibrary/Url.qll

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,33 @@ private class UrlSummaries extends SummaryModelCsv {
8585
override predicate row(string row) {
8686
row =
8787
[
88-
";URL;true;init(string:);(String);;Argument[0];ReturnValue;taint",
89-
";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0..1];ReturnValue;taint",
88+
";URL;true;init(string:);(String);;Argument[0];ReturnValue.OptionalSome;taint",
89+
";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0];ReturnValue.OptionalSome;taint",
90+
";URL;true;init(string:relativeTo:);(String,URL?);;Argument[1].OptionalSome;ReturnValue.OptionalSome;taint",
9091
";URL;true;init(fileURLWithPath:);;;Argument[0];ReturnValue;taint",
9192
";URL;true;init(fileURLWithPath:isDirectory:);;;Argument[0];ReturnValue;taint",
92-
";URL;true;init(fileURLWithPath:relativeTo:);;;Argument[0..1];ReturnValue;taint",
93+
";URL;true;init(fileURLWithPath:relativeTo:);;;Argument[0];ReturnValue;taint",
94+
";URL;true;init(fileURLWithPath:relativeTo:);;;Argument[1].OptionalSome;ReturnValue;taint",
9395
";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint",
94-
";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint",
96+
";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[2].OptionalSome;ReturnValue;taint",
9597
";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint",
96-
";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint",
98+
";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[2].OptionalSome;ReturnValue;taint",
9799
";URL;true;init(fileReferenceLiteralResourceName:);;;Argument[0];ReturnValue;taint",
98-
";URL;true;init(_:);;;Argument[0];ReturnValue;taint",
99-
";URL;true;init(_:isDirectory:);;;Argument[0];ReturnValue;taint",
100+
";URL;true;init(_:);;;Argument[0];ReturnValue.OptionalSome;taint",
101+
";URL;true;init(_:isDirectory:);;;Argument[0];ReturnValue.OptionalSome;taint",
100102
";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[0];ReturnValue;taint",
101-
";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[2];ReturnValue;taint",
103+
";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[2].OptionalSome;ReturnValue;taint",
102104
";URL;true;init(resolvingAliasFileAt:options:);;;Argument[0];ReturnValue;taint",
103105
";URL;true;init(resource:);;;Argument[0];ReturnValue;taint",
104-
";URL;true;init(dataRepresentation:relativeTo:isAbsolute:);;;Argument[0..1];ReturnValue;taint",
106+
";URL;true;init(dataRepresentation:relativeTo:isAbsolute:);;;Argument[0];ReturnValue;taint",
107+
";URL;true;init(dataRepresentation:relativeTo:isAbsolute:);;;Argument[1].OptionalSome;ReturnValue;taint",
105108
";URL;true;init(_:strategy:);;;Argument[0];ReturnValue;taint",
106-
";URL;true;init(filePath:directoryHint:);;;Argument[0];ReturnValue;taint",
109+
";URL;true;init(filePath:directoryHint:);;;Argument[0];ReturnValue.OptionalSome;taint",
107110
";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[0];ReturnValue;taint",
108-
";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[2];ReturnValue;taint",
109-
";URL;true;init(for:in:appropriateFor:create:);;;Argument[0..2];ReturnValue;taint",
110-
";URL;true;init(string:encodingInvalidCharacters:);;;Argument[0];ReturnValue;taint",
111+
";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[2].OptionalSome;ReturnValue;taint",
112+
";URL;true;init(for:in:appropriateFor:create:);;;Argument[0..1];ReturnValue;taint",
113+
";URL;true;init(for:in:appropriateFor:create:);;;Argument[2].OptionalSome;ReturnValue;taint",
114+
";URL;true;init(string:encodingInvalidCharacters:);;;Argument[0];ReturnValue.OptionalSome;taint",
111115
";URL;true;resourceValues(forKeys:);;;Argument[-1];ReturnValue;taint",
112116
";URL;true;setResourceValues(_:);;;Argument[0];Argument[-1];taint",
113117
";URL;true;setTemporaryResourceValue(_:forKey:);;;Argument[-1..0];Argument[-1];taint",
@@ -125,7 +129,8 @@ private class UrlSummaries extends SummaryModelCsv {
125129
";URL;true;deletingLastPathComponent();;;Argument[-1];ReturnValue;taint",
126130
";URL;true;deletingPathExtension();;;Argument[-1];ReturnValue;taint",
127131
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[-1];ReturnValue;taint",
128-
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[1..2];ReturnValue;taint",
132+
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[1].OptionalSome.CollectionElement;ReturnValue;taint",
133+
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[2].OptionalSome;ReturnValue;taint",
129134
";URL;true;bookmarkData(withContentsOf:);;;Argument[0];ReturnValue;taint",
130135
";URL;true;resourceValues(forKeys:fromBookmarkData:);;;Argument[1];ReturnValue;taint",
131136
";URL;true;promisedItemResourceValues(forKeys:);;;Argument[-1];ReturnValue;taint",

swift/ql/lib/codeql/swift/security/CommandInjectionExtensions.qll

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,50 +29,15 @@ class CommandInjectionAdditionalFlowStep extends Unit {
2929
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
3030
}
3131

32-
private class ProcessSink2 extends CommandInjectionSink instanceof DataFlow::Node {
33-
ProcessSink2() {
34-
exists(AssignExpr assign, ProcessHost s |
35-
assign.getDest() = s and
36-
this.asExpr() = assign.getSource()
37-
)
38-
or
39-
exists(AssignExpr assign, ProcessHost s, ArrayExpr a |
40-
assign.getDest() = s and
41-
a = assign.getSource() and
42-
this.asExpr() = a.getAnElement()
43-
)
44-
}
45-
}
46-
47-
private class ProcessHost extends MemberRefExpr {
48-
ProcessHost() { this.getBase() instanceof ProcessRef }
49-
}
50-
51-
/** An expression of type `Process`. */
52-
private class ProcessRef extends Expr {
53-
ProcessRef() {
54-
this.getType() instanceof ProcessType or
55-
this.getType() = any(OptionalType t | t.getBaseType() instanceof ProcessType)
56-
}
57-
}
58-
59-
/** The type `Process`. */
60-
private class ProcessType extends NominalType {
61-
ProcessType() { this.getFullName() = "Process" }
62-
}
63-
6432
/**
65-
* A `DataFlow::Node` that is written into a `Process` object.
33+
* An additional taint step for command injection vulnerabilities.
6634
*/
67-
private class ProcessSink extends CommandInjectionSink instanceof DataFlow::Node {
68-
ProcessSink() {
69-
// any write into a class derived from `Process` is a sink. For
70-
// example in `Process.launchPath = sensitive` the post-update node corresponding
71-
// with `Process.launchPath` is a sink.
72-
exists(NominalType t, Expr e |
73-
t.getABaseType*().getUnderlyingType().getName() = "Process" and
74-
e.getFullyConverted() = this.asExpr() and
75-
e.getFullyConverted().getType() = t
35+
private class CommandInjectionArrayAdditionalFlowStep extends CommandInjectionAdditionalFlowStep {
36+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
37+
// needed until we have proper content flow through arrays.
38+
exists(ArrayExpr arr |
39+
nodeFrom.asExpr() = arr.getAnElement() and
40+
nodeTo.asExpr() = arr
7641
)
7742
}
7843
}
@@ -83,3 +48,24 @@ private class ProcessSink extends CommandInjectionSink instanceof DataFlow::Node
8348
private class DefaultCommandInjectionSink extends CommandInjectionSink {
8449
DefaultCommandInjectionSink() { sinkNode(this, "command-injection") }
8550
}
51+
52+
private class CommandInjectionSinks extends SinkModelCsv {
53+
override predicate row(string row) {
54+
row =
55+
[
56+
";Process;true;run(_:arguments:terminationHandler:);;;Argument[0..1];command-injection",
57+
";Process;true;launchedProcess(launchPath:arguments:);;;Argument[0..1];command-injection",
58+
";Process;true;arguments;;;PostUpdate;command-injection",
59+
";Process;true;currentDirectory;;;PostUpdate;command-injection",
60+
";Process;true;environment;;;PostUpdate;command-injection",
61+
";Process;true;executableURL;;;PostUpdate;command-injection",
62+
";Process;true;standardError;;;PostUpdate;command-injection",
63+
";Process;true;standardInput;;;PostUpdate;command-injection",
64+
";Process;true;standardOutput;;;PostUpdate;command-injection",
65+
";Process;true;currentDirectoryPath;;;PostUpdate;command-injection",
66+
";Process;true;launchPath;;;PostUpdate;command-injection",
67+
";NSUserScriptTask;true;init(url:);;;Argument[0];command-injection",
68+
";NSUserUnixTask;true;execute(withArguments:completionHandler:);;;Argument[0];command-injection",
69+
]
70+
}
71+
}

swift/ql/lib/codeql/swift/security/CommandInjectionQuery.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Provides a taint-tracking configuration for reasoning about system
3-
* commands built from user-controlled sources (that is, Command injection
3+
* commands built from user-controlled sources (that is, command injection
44
* vulnerabilities).
55
*/
66

@@ -11,7 +11,7 @@ import codeql.swift.dataflow.FlowSources
1111
import codeql.swift.security.CommandInjectionExtensions
1212

1313
/**
14-
* A taint configuration for tainted data that reaches a Command Injection sink.
14+
* A taint configuration for tainted data that reaches a command injection sink.
1515
*/
1616
module CommandInjectionConfig implements DataFlow::ConfigSig {
1717
predicate isSource(DataFlow::Node node) { node instanceof FlowSource }
@@ -26,6 +26,6 @@ module CommandInjectionConfig implements DataFlow::ConfigSig {
2626
}
2727

2828
/**
29-
* Detect taint flow of tainted data that reaches a Command Injection sink.
29+
* Detect taint flow of tainted data that reaches a command injection sink.
3030
*/
3131
module CommandInjectionFlow = TaintTracking::Global<CommandInjectionConfig>;

swift/ql/src/experimental/Security/CWE-078/CommandInjection.qhelp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ using it.
2424

2525
<example>
2626
<p>
27-
The following examples execute code from user input without
27+
The following example executes code from user input without
2828
sanitizing it first:
2929
</p>
3030
<sample src="CommandInjectionBad.swift" />
3131
<p>
3232
If user input is used to construct a command it should be checked
3333
first. This ensures that the user cannot insert characters that have special
34-
meanings.
34+
meanings:
3535
</p>
3636
<sample src="CommandInjectionGood.swift" />
3737
</example>
@@ -42,4 +42,4 @@ OWASP:
4242
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
4343
</li>
4444
</references>
45-
</qhelp>
45+
</qhelp>

swift/ql/src/experimental/Security/CWE-078/CommandInjection.ql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* @name System command built from user-controlled sources
3-
* @description Building a system command from user-controlled sources is vulnerable to insertion of malicious code by the user.
3+
* @description Building a system command from user-controlled sources may allow a malicious
4+
* user to change the meaning of the command.
45
* @kind path-problem
56
* @problem.severity error
67
* @security-severity 9.8

swift/ql/src/experimental/Security/CWE-078/CommandInjectionGood.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ func validateCommand(_ command: String) -> String? {
66
return nil
77
}
88

9-
var task = Process()
10-
task.launchPath = "/bin/bash"
11-
task.arguments = ["-c", validateCommand(userControlledString)] // GOOD
9+
if let validatedString = validateCommand(userControlledString) {
10+
var task = Process()
11+
task.launchPath = "/bin/bash"
12+
task.arguments = ["-c", validatedString] // GOOD
1213

13-
task.launch()
14+
task.launch()
15+
}

0 commit comments

Comments
 (0)