Skip to content

Commit 5be97f3

Browse files
authored
Merge pull request github#11909 from erik-krogh/concatCode
Rb: recognize string concatenations as sinks for unsafe-code-construction
2 parents a10b45e + ee9b01b commit 5be97f3

File tree

7 files changed

+77
-0
lines changed

7 files changed

+77
-0
lines changed

ruby/ql/lib/codeql/ruby/ast/Operation.qll

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,27 @@ class AddExpr extends BinaryArithmeticOperation, TAddExpr {
158158
final override string getAPrimaryQlClass() { result = "AddExpr" }
159159
}
160160

161+
/**
162+
* A series of add expressions, e.g. `1 + 2 + 3`.
163+
* This class is used to represent the root of such a series, and
164+
* the `getALeaf` predicate can be used to get the leaf nodes.
165+
*/
166+
class AddExprRoot extends AddExpr {
167+
AddExprRoot() { not this.getParent() instanceof AddExpr }
168+
169+
private AstNode getALeafOrAdd() {
170+
result = this.getAChild()
171+
or
172+
result = this.getALeafOrAdd().(AddExpr).getAChild()
173+
}
174+
175+
/** Gets a leaf node of this add expression. */
176+
AstNode getALeaf() {
177+
result = this.getALeafOrAdd() and
178+
not result instanceof AddExpr
179+
}
180+
}
181+
161182
/**
162183
* A subtract expression.
163184
* ```rb

ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ module UnsafeCodeConstruction {
9696
override string getSinkType() { result = "string interpolation" }
9797
}
9898

99+
/**
100+
* A component of a string-concatenation (e.g. `"foo " + sink`),
101+
* where the resulting string ends up being executed as a code.
102+
*/
103+
class StringConcatAsSink extends Sink {
104+
Concepts::CodeExecution s;
105+
106+
StringConcatAsSink() {
107+
exists(Ast::AddExprRoot add |
108+
any(DataFlow::Node n | n.asExpr().getExpr() = add) = getANodeExecutedAsCode(s) and
109+
this.asExpr().getExpr() = add.getALeaf()
110+
)
111+
}
112+
113+
override DataFlow::Node getCodeSink() { result = s }
114+
115+
override string getSinkType() { result = "string concatenation" }
116+
}
117+
99118
import codeql.ruby.security.TaintedFormatStringSpecific as TaintedFormat
100119

101120
/**

ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ module UnsafeShellCommandConstruction {
8181
override DataFlow::Node getStringConstruction() { result.asExpr().getExpr() = lit }
8282
}
8383

84+
/**
85+
* A component of a string-concatenation (e.g. `"foo " + sink`),
86+
* where the resulting string ends up being executed as a shell command.
87+
*/
88+
class StringConcatAsSink extends Sink {
89+
Concepts::SystemCommandExecution s;
90+
Ast::AddExprRoot add;
91+
92+
StringConcatAsSink() {
93+
isUsedAsShellCommand(any(DataFlow::Node n | n.asExpr().getExpr() = add), s) and
94+
this.asExpr().getExpr() = add.getALeaf()
95+
}
96+
97+
override DataFlow::Node getCommandExecution() { result = s }
98+
99+
override string describe() { result = "string concatenation" }
100+
101+
override DataFlow::Node getStringConstruction() { result.asExpr().getExpr() = add }
102+
}
103+
84104
/**
85105
* A string constructed using a `.join(" ")` call, where the resulting string ends up being executed as a shell command.
86106
*/

ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ edges
1111
| impl/unsafeShell.rb:47:16:47:21 | target : | impl/unsafeShell.rb:48:19:48:27 | #{...} |
1212
| impl/unsafeShell.rb:51:17:51:17 | x : | impl/unsafeShell.rb:52:14:52:14 | x |
1313
| impl/unsafeShell.rb:51:17:51:17 | x : | impl/unsafeShell.rb:54:29:54:29 | x |
14+
| impl/unsafeShell.rb:57:21:57:21 | x : | impl/unsafeShell.rb:58:23:58:23 | x |
1415
nodes
1516
| impl/sub/notImported.rb:2:12:2:17 | target : | semmle.label | target : |
1617
| impl/sub/notImported.rb:3:19:3:27 | #{...} | semmle.label | #{...} |
@@ -35,6 +36,8 @@ nodes
3536
| impl/unsafeShell.rb:51:17:51:17 | x : | semmle.label | x : |
3637
| impl/unsafeShell.rb:52:14:52:14 | x | semmle.label | x |
3738
| impl/unsafeShell.rb:54:29:54:29 | x | semmle.label | x |
39+
| impl/unsafeShell.rb:57:21:57:21 | x : | semmle.label | x : |
40+
| impl/unsafeShell.rb:58:23:58:23 | x | semmle.label | x |
3841
subpaths
3942
#select
4043
| impl/sub/notImported.rb:3:14:3:28 | "cat #{...}" | impl/sub/notImported.rb:2:12:2:17 | target : | impl/sub/notImported.rb:3:19:3:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/sub/notImported.rb:2:12:2:17 | target | library input | impl/sub/notImported.rb:3:5:3:34 | call to popen | shell command |
@@ -49,3 +52,4 @@ subpaths
4952
| impl/unsafeShell.rb:48:14:48:28 | "cat #{...}" | impl/unsafeShell.rb:47:16:47:21 | target : | impl/unsafeShell.rb:48:19:48:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:47:16:47:21 | target | library input | impl/unsafeShell.rb:48:5:48:34 | call to popen | shell command |
5053
| impl/unsafeShell.rb:52:14:52:24 | call to join | impl/unsafeShell.rb:51:17:51:17 | x : | impl/unsafeShell.rb:52:14:52:14 | x | This array which depends on $@ is later used in a $@. | impl/unsafeShell.rb:51:17:51:17 | x | library input | impl/unsafeShell.rb:52:5:52:30 | call to popen | shell command |
5154
| impl/unsafeShell.rb:54:14:54:40 | call to join | impl/unsafeShell.rb:51:17:51:17 | x : | impl/unsafeShell.rb:54:29:54:29 | x | This array which depends on $@ is later used in a $@. | impl/unsafeShell.rb:51:17:51:17 | x | library input | impl/unsafeShell.rb:54:5:54:46 | call to popen | shell command |
55+
| impl/unsafeShell.rb:58:14:58:23 | ... + ... | impl/unsafeShell.rb:57:21:57:21 | x : | impl/unsafeShell.rb:58:23:58:23 | x | This string concatenation which depends on $@ is later used in a $@. | impl/unsafeShell.rb:57:21:57:21 | x | library input | impl/unsafeShell.rb:58:5:58:29 | call to popen | shell command |

ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/unsafeShell.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ def arrayJoin(x)
5353

5454
IO.popen(["foo", "bar", x].join(' '), "w") # NOT OK
5555
end
56+
57+
def string_concat(x)
58+
IO.popen("cat " + x, "w") # NOT OK
59+
end
5660
end

ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/UnsafeCodeConstruction.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edges
77
| impl/unsafeCode.rb:37:15:37:15 | x : | impl/unsafeCode.rb:40:10:40:12 | arr |
88
| impl/unsafeCode.rb:37:15:37:15 | x : | impl/unsafeCode.rb:44:10:44:12 | arr |
99
| impl/unsafeCode.rb:47:15:47:15 | x : | impl/unsafeCode.rb:49:9:49:12 | #{...} |
10+
| impl/unsafeCode.rb:54:21:54:21 | x : | impl/unsafeCode.rb:55:22:55:22 | x |
1011
nodes
1112
| impl/unsafeCode.rb:2:12:2:17 | target : | semmle.label | target : |
1213
| impl/unsafeCode.rb:3:17:3:25 | #{...} | semmle.label | #{...} |
@@ -23,6 +24,8 @@ nodes
2324
| impl/unsafeCode.rb:44:10:44:12 | arr | semmle.label | arr |
2425
| impl/unsafeCode.rb:47:15:47:15 | x : | semmle.label | x : |
2526
| impl/unsafeCode.rb:49:9:49:12 | #{...} | semmle.label | #{...} |
27+
| impl/unsafeCode.rb:54:21:54:21 | x : | semmle.label | x : |
28+
| impl/unsafeCode.rb:55:22:55:22 | x | semmle.label | x |
2629
subpaths
2730
#select
2831
| impl/unsafeCode.rb:3:17:3:25 | #{...} | impl/unsafeCode.rb:2:12:2:17 | target : | impl/unsafeCode.rb:3:17:3:25 | #{...} | This string interpolation which depends on $@ is later $@. | impl/unsafeCode.rb:2:12:2:17 | target | library input | impl/unsafeCode.rb:3:5:3:27 | call to eval | interpreted as code |
@@ -33,3 +36,4 @@ subpaths
3336
| impl/unsafeCode.rb:40:10:40:12 | arr | impl/unsafeCode.rb:37:15:37:15 | x : | impl/unsafeCode.rb:40:10:40:12 | arr | This array which depends on $@ is later $@. | impl/unsafeCode.rb:37:15:37:15 | x | library input | impl/unsafeCode.rb:40:5:40:24 | call to eval | interpreted as code |
3437
| impl/unsafeCode.rb:44:10:44:12 | arr | impl/unsafeCode.rb:37:15:37:15 | x : | impl/unsafeCode.rb:44:10:44:12 | arr | This array which depends on $@ is later $@. | impl/unsafeCode.rb:37:15:37:15 | x | library input | impl/unsafeCode.rb:44:5:44:24 | call to eval | interpreted as code |
3538
| impl/unsafeCode.rb:49:9:49:12 | #{...} | impl/unsafeCode.rb:47:15:47:15 | x : | impl/unsafeCode.rb:49:9:49:12 | #{...} | This string interpolation which depends on $@ is later $@. | impl/unsafeCode.rb:47:15:47:15 | x | library input | impl/unsafeCode.rb:51:5:51:13 | call to eval | interpreted as code |
39+
| impl/unsafeCode.rb:55:22:55:22 | x | impl/unsafeCode.rb:54:21:54:21 | x : | impl/unsafeCode.rb:55:22:55:22 | x | This string concatenation which depends on $@ is later $@. | impl/unsafeCode.rb:54:21:54:21 | x | library input | impl/unsafeCode.rb:56:5:56:13 | call to eval | interpreted as code |

ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,9 @@ def hereDoc(x)
5050
HERE
5151
eval(foo) # NOT OK
5252
end
53+
54+
def string_concat(x)
55+
foo = "foo = " + x
56+
eval(foo) # NOT OK
57+
end
5358
end

0 commit comments

Comments
 (0)