Skip to content

Commit f672199

Browse files
authored
Merge pull request #11082 from erik-krogh/shellArr
JS: treat arrays that gets executed with shell:true as a sink for `js/shell-command-constructed-from-input`
2 parents b2267c0 + 40032f2 commit f672199

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,21 +156,27 @@ module UnsafeShellCommandConstruction {
156156
}
157157

158158
/**
159-
* Gets a node that ends up in an array that is ultimately executed as a shell script by `sys`.
159+
* Holds if the arguments array given to `sys` is joined as a string because `shell` is set to true.
160160
*/
161-
private DataFlow::SourceNode endsInShellExecutedArray(
162-
DataFlow::TypeBackTracker t, SystemCommandExecution sys
163-
) {
164-
t.start() and
165-
result = sys.getArgumentList().getALocalSource() and
166-
// the array gets joined to a string when `shell` is set to true.
161+
predicate executesArrayAsShell(SystemCommandExecution sys) {
167162
sys.getOptionsArg()
168163
.getALocalSource()
169164
.getAPropertyWrite("shell")
170165
.getRhs()
171166
.asExpr()
172167
.(BooleanLiteral)
173168
.getValue() = "true"
169+
}
170+
171+
/**
172+
* Gets a node that ends up in an array that is ultimately executed as a shell script by `sys`.
173+
*/
174+
private DataFlow::SourceNode endsInShellExecutedArray(
175+
DataFlow::TypeBackTracker t, SystemCommandExecution sys
176+
) {
177+
t.start() and
178+
result = sys.getArgumentList().getALocalSource() and
179+
executesArrayAsShell(sys)
174180
or
175181
exists(DataFlow::TypeBackTracker t2 |
176182
result = endsInShellExecutedArray(t2, sys).backtrack(t2, t)
@@ -193,6 +199,10 @@ module UnsafeShellCommandConstruction {
193199
or
194200
this = arr.getAMethodCall(["push", "unshift"]).getAnArgument()
195201
)
202+
or
203+
this = sys.getArgumentList() and
204+
not this instanceof DataFlow::ArrayCreationNode and
205+
executesArrayAsShell(sys)
196206
}
197207

198208
override string getSinkType() { result = "shell argument" }

javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,14 @@ nodes
227227
| lib/lib.js:420:29:420:32 | name |
228228
| lib/lib.js:424:24:424:27 | name |
229229
| lib/lib.js:424:24:424:27 | name |
230+
| lib/lib.js:425:6:425:13 | arr |
231+
| lib/lib.js:425:12:425:13 | [] |
230232
| lib/lib.js:426:11:426:14 | name |
231233
| lib/lib.js:426:11:426:14 | name |
234+
| lib/lib.js:427:14:427:16 | arr |
235+
| lib/lib.js:427:14:427:16 | arr |
236+
| lib/lib.js:428:14:428:58 | build(" ... + '-') |
237+
| lib/lib.js:428:14:428:58 | build(" ... + '-') |
232238
| lib/lib.js:428:28:428:51 | (name ? ... ' : '') |
233239
| lib/lib.js:428:28:428:57 | (name ? ... ) + '-' |
234240
| lib/lib.js:428:29:428:50 | name ? ... :' : '' |
@@ -306,6 +312,10 @@ nodes
306312
| lib/subLib/index.js:7:32:7:35 | name |
307313
| lib/subLib/index.js:8:22:8:25 | name |
308314
| lib/subLib/index.js:8:22:8:25 | name |
315+
| lib/subLib/index.js:13:44:13:46 | arr |
316+
| lib/subLib/index.js:13:44:13:46 | arr |
317+
| lib/subLib/index.js:14:22:14:24 | arr |
318+
| lib/subLib/index.js:14:22:14:24 | arr |
309319
edges
310320
| lib/isImported.js:5:49:5:52 | name | lib/isImported.js:6:22:6:25 | name |
311321
| lib/isImported.js:5:49:5:52 | name | lib/isImported.js:6:22:6:25 | name |
@@ -584,7 +594,13 @@ edges
584594
| lib/lib.js:414:40:414:43 | name | lib/lib.js:426:11:426:14 | name |
585595
| lib/lib.js:414:40:414:43 | name | lib/lib.js:428:36:428:39 | name |
586596
| lib/lib.js:414:40:414:43 | name | lib/lib.js:428:36:428:39 | name |
597+
| lib/lib.js:425:6:425:13 | arr | lib/lib.js:427:14:427:16 | arr |
598+
| lib/lib.js:425:6:425:13 | arr | lib/lib.js:427:14:427:16 | arr |
599+
| lib/lib.js:425:12:425:13 | [] | lib/lib.js:425:6:425:13 | arr |
600+
| lib/lib.js:426:11:426:14 | name | lib/lib.js:425:12:425:13 | [] |
587601
| lib/lib.js:428:28:428:51 | (name ? ... ' : '') | lib/lib.js:428:28:428:57 | (name ? ... ) + '-' |
602+
| lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | lib/lib.js:428:14:428:58 | build(" ... + '-') |
603+
| lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | lib/lib.js:428:14:428:58 | build(" ... + '-') |
588604
| lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | lib/lib.js:431:23:431:26 | last |
589605
| lib/lib.js:428:29:428:50 | name ? ... :' : '' | lib/lib.js:428:28:428:51 | (name ? ... ' : '') |
590606
| lib/lib.js:428:36:428:39 | name | lib/lib.js:428:36:428:45 | name + ':' |
@@ -672,6 +688,10 @@ edges
672688
| lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name |
673689
| lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name |
674690
| lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name |
691+
| lib/subLib/index.js:13:44:13:46 | arr | lib/subLib/index.js:14:22:14:24 | arr |
692+
| lib/subLib/index.js:13:44:13:46 | arr | lib/subLib/index.js:14:22:14:24 | arr |
693+
| lib/subLib/index.js:13:44:13:46 | arr | lib/subLib/index.js:14:22:14:24 | arr |
694+
| lib/subLib/index.js:13:44:13:46 | arr | lib/subLib/index.js:14:22:14:24 | arr |
675695
#select
676696
| lib/isImported.js:6:10:6:25 | "rm -rf " + name | lib/isImported.js:5:49:5:52 | name | lib/isImported.js:6:22:6:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/isImported.js:5:49:5:52 | name | library input | lib/isImported.js:6:2:6:26 | cp.exec ... + name) | shell command |
677697
| lib/lib2.js:4:10:4:25 | "rm -rf " + name | lib/lib2.js:3:28:3:31 | name | lib/lib2.js:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib2.js:3:28:3:31 | name | library input | lib/lib2.js:4:2:4:26 | cp.exec ... + name) | shell command |
@@ -739,6 +759,8 @@ edges
739759
| lib/lib.js:420:29:420:32 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:420:29:420:32 | name | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:420:2:420:49 | cp.spaw ... true}) | shell command |
740760
| lib/lib.js:424:24:424:27 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:424:24:424:27 | name | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:424:2:424:40 | spawn(" ... WN_OPT) | shell command |
741761
| lib/lib.js:426:11:426:14 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:426:11:426:14 | name | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:427:2:427:28 | spawn(" ... WN_OPT) | shell command |
762+
| lib/lib.js:427:14:427:16 | arr | lib/lib.js:414:40:414:43 | name | lib/lib.js:427:14:427:16 | arr | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:427:2:427:28 | spawn(" ... WN_OPT) | shell command |
763+
| lib/lib.js:428:14:428:58 | build(" ... + '-') | lib/lib.js:414:40:414:43 | name | lib/lib.js:428:14:428:58 | build(" ... + '-') | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:428:2:428:70 | spawn(" ... WN_OPT) | shell command |
742764
| lib/lib.js:436:19:436:22 | last | lib/lib.js:414:40:414:43 | name | lib/lib.js:436:19:436:22 | last | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:428:2:428:70 | spawn(" ... WN_OPT) | shell command |
743765
| lib/lib.js:442:12:442:27 | "rm -rf " + name | lib/lib.js:441:39:441:42 | name | lib/lib.js:442:24:442:27 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:441:39:441:42 | name | library input | lib/lib.js:442:2:442:28 | asyncEx ... + name) | shell command |
744766
| lib/lib.js:447:13:447:28 | "rm -rf " + name | lib/lib.js:446:20:446:23 | name | lib/lib.js:447:25:447:28 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:446:20:446:23 | name | library input | lib/lib.js:447:3:447:29 | asyncEx ... + name) | shell command |
@@ -760,3 +782,4 @@ edges
760782
| lib/subLib/amdSub.js:4:10:4:25 | "rm -rf " + name | lib/subLib/amdSub.js:3:28:3:31 | name | lib/subLib/amdSub.js:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib/amdSub.js:3:28:3:31 | name | library input | lib/subLib/amdSub.js:4:2:4:26 | cp.exec ... + name) | shell command |
761783
| lib/subLib/index.js:4:10:4:25 | "rm -rf " + name | lib/subLib/index.js:3:28:3:31 | name | lib/subLib/index.js:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib/index.js:3:28:3:31 | name | library input | lib/subLib/index.js:4:2:4:26 | cp.exec ... + name) | shell command |
762784
| lib/subLib/index.js:8:10:8:25 | "rm -rf " + name | lib/subLib/index.js:7:32:7:35 | name | lib/subLib/index.js:8:22:8:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib/index.js:7:32:7:35 | name | library input | lib/subLib/index.js:8:2:8:26 | cp.exec ... + name) | shell command |
785+
| lib/subLib/index.js:14:22:14:24 | arr | lib/subLib/index.js:13:44:13:46 | arr | lib/subLib/index.js:14:22:14:24 | arr | This shell argument which depends on $@ is later used in a $@. | lib/subLib/index.js:13:44:13:46 | arr | library input | lib/subLib/index.js:14:5:14:40 | cp.spaw ... true}) | shell command |

javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/subLib/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ module.exports.foo = function (name) {
88
cp.exec("rm -rf " + name); // NOT OK - this is being called explicitly from child_process-test.js
99
};
1010

11-
module.exports.amd = require("./amd.js");
11+
module.exports.amd = require("./amd.js");
12+
13+
module.exports.arrToShell = function (cmd, arr) {
14+
cp.spawn("echo", arr, {shell: true}); // NOT OK
15+
}

0 commit comments

Comments
 (0)