diff --git a/javascript/ql/lib/change-notes/2026-11-02-shell-command-arguments.md b/javascript/ql/lib/change-notes/2026-11-02-shell-command-arguments.md new file mode 100644 index 000000000000..9f5bd705dc53 --- /dev/null +++ b/javascript/ql/lib/change-notes/2026-11-02-shell-command-arguments.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Command injection modeling now recognizes argument arrays passed to `spawn` and `execFile` when the `shell` option is enabled, including string-valued shell paths and calls to `execFile` without a callback. The model distinguishes argument arrays, options objects, and callbacks, and accounts for local writes to the `shell` option before execution. diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll index 10801d966542..06b99402e5ed 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll @@ -797,13 +797,31 @@ module NodeJSLib { override DataFlow::Node getArgumentList() { methodName = ["execFile", "execFileSync", "fork", "spawn", "spawnSync"] and // all of the above methods take the argument list as their second argument - result = this.getParameter(1).asSink() + result = this.getParameter(1).asSink() and + not this.isOptionsObject(result) and + not result.getALocalSource() instanceof DataFlow::FunctionNode } override predicate isSync() { methodName.matches("%Sync") } + /** Holds if `node` is known to be an options object rather than an argument array or callback. */ + private predicate isOptionsObject(DataFlow::Node node) { + not node.getALocalSource() instanceof DataFlow::ArrayCreationNode and + ( + node.getALocalSource() instanceof DataFlow::ObjectLiteralNode + or + exists(node.getALocalSource().getAPropertyWrite("shell")) + or + exists(API::Node api | + api.asSink() = node and + api.getAValueReachingSink().getALocalSource() instanceof DataFlow::ObjectLiteralNode + ) + ) + } + override DataFlow::Node getOptionsArg() { not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + // Shell execution still takes separate arguments for the array and the options object. not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist not result = this.getParameter(0).asSink() and // fork/spawn and all sync methos always has options as the last argument @@ -812,9 +830,14 @@ module NodeJSLib { methodName.matches("spawn%") or methodName.matches("%Sync") then result = this.getLastArgument() - else - // the rest (exec/execFile) has the options argument as their second last. - result = this.getParameter(this.getNumArgument() - 2).asSink() + else ( + result = this.getParameter(this.getNumArgument() - 2).asSink() and + (methodName = "exec" or this.getNumArgument() = 4 or this.isOptionsObject(result)) + or + // Without a callback, require evidence that the last argument is an options object. + result = this.getLastArgument() and + this.isOptionsObject(result) + ) } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll index b3516a79bf49..7db735de3889 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll @@ -5,6 +5,7 @@ */ import javascript +private import UnsafeShellCommandConstructionCustomizations module CommandInjection { /** @@ -55,6 +56,19 @@ module CommandInjection { SystemCommandExecutionSink() { this = any(SystemCommandExecution sys).getACommandArgument() } } + /** An argument in a `child_process` argument array that is interpreted by a shell. */ + private class ChildProcessArgumentSink extends Sink { + ChildProcessArgumentSink() { + exists(UnsafeShellCommandConstruction::ShellTrueCommandExecutionSink sink | + this = sink and + sink.getCommandExecution() = + API::moduleImport(["mz/child_process", "child_process"]) + .getMember(["execFile", "execFileSync", "spawn", "spawnSync"]) + .getMaybePromisifiedCall() + ) + } + } + private class SinkFromModel extends Sink { SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll index e65fac9530d7..7b686bca878f 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll @@ -174,58 +174,65 @@ module UnsafeShellCommandConstruction { override DataFlow::Node getAlertLocation() { result = this } } - /** - * Holds if the arguments array given to `sys` is joined as a string because `shell` is set to true. - */ - predicate executesArrayAsShell(SystemCommandExecution sys) { - sys.getOptionsArg() - .getALocalSource() - .getAPropertyWrite("shell") - .getRhs() - .asExpr() - .(BooleanLiteral) - .getValue() = "true" + /** Holds if `node` can reach `use` without another write to the `shell` property of `options`. */ + private predicate reachesShellOptionUse( + DataFlow::SourceNode options, ControlFlowNode node, ControlFlowNode use + ) { + exists(SystemCommandExecution sys | + options = sys.getOptionsArg().getALocalSource() and use = sys.asExpr() + ) and + ( + node = use + or + not node = options.getAPropertyWrite("shell").getWriteNode() and + reachesShellOptionUse(options, node.getASuccessor(), use) + ) + } + + /** Gets a value of the `shell` option that can reach the command execution. */ + private DataFlow::Node getShellOption(SystemCommandExecution sys) { + exists(DataFlow::SourceNode options, DataFlow::PropWrite write, Expr use | + options = sys.getOptionsArg().getALocalSource() and + write = options.getAPropertyWrite("shell") and + use = sys.asExpr() and + result = write.getRhs() + | + write.getWriteNode().getContainer() != use.getContainer() + or + reachesShellOptionUse(options, write.getWriteNode().getASuccessor(), use) + ) or - exists(API::Node node | + exists(API::Node node, DataFlow::SourceNode options, Expr use | node.asSink() = sys.getOptionsArg() and - node.getMember("shell").asSink().mayHaveBooleanValue(true) + result = node.getMember("shell").asSink() and + options = sys.getOptionsArg().getALocalSource() and + use = sys.asExpr() and + not result = options.getAPropertyWrite("shell").getRhs() and + reachesShellOptionUse(options, use.getContainer().getStart(), use) ) } /** - * Gets a node that ends up in an array that is ultimately executed as a shell script by `sys`. + * Holds if the arguments array given to `sys` is joined as a string because the `shell` option is enabled. */ - private DataFlow::SourceNode endsInShellExecutedArray( - DataFlow::TypeBackTracker t, SystemCommandExecution sys - ) { - t.start() and - result = sys.getArgumentList().getALocalSource() and - executesArrayAsShell(sys) - or - exists(DataFlow::TypeBackTracker t2 | - result = endsInShellExecutedArray(t2, sys).backtrack(t2, t) + predicate executesArrayAsShell(SystemCommandExecution sys) { + exists(DataFlow::Node shell | shell = getShellOption(sys) | + shell.mayHaveBooleanValue(true) + or + exists(string path | shell.mayHaveStringValue(path) and path != "") ) } /** - * An argument to a command invocation where the `shell` option is set to true. + * An argument to a command invocation where the `shell` option is enabled. */ class ShellTrueCommandExecutionSink extends Sink { SystemCommandExecution sys; ShellTrueCommandExecutionSink() { - // `shell` is set to true. That means string-concatenation happens behind the scenes. + // `shell` is enabled. That means string-concatenation happens behind the scenes. // We just assume that a `shell` option in any library means the same thing as it does in NodeJS. - exists(DataFlow::SourceNode arr | - arr = endsInShellExecutedArray(DataFlow::TypeBackTracker::end(), sys) - | - this = arr.(DataFlow::ArrayCreationNode).getAnElement() - or - this = arr.getAMethodCall(["push", "unshift"]).getAnArgument() - ) - or this = sys.getArgumentList() and - not this instanceof DataFlow::ArrayCreationNode and executesArrayAsShell(sys) } diff --git a/javascript/ql/src/change-notes/2026-11-02-shell-command-arguments.md b/javascript/ql/src/change-notes/2026-11-02-shell-command-arguments.md new file mode 100644 index 000000000000..c091b52c3e86 --- /dev/null +++ b/javascript/ql/src/change-notes/2026-11-02-shell-command-arguments.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `js/command-line-injection` query now reports remote input that reaches argument arrays when the `shell` option is enabled. The `js/shell-command-constructed-from-input` query also recognizes string-valued shell paths. Both queries now handle `execFile` calls without a callback and check argument-array input at the execution call, avoiding alerts for later array mutations. diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/SystemCommandExecution.qll b/javascript/ql/test/library-tests/frameworks/NodeJSLib/SystemCommandExecution.qll index 4df4b65d72cb..ed3a1865994a 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/SystemCommandExecution.qll +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/SystemCommandExecution.qll @@ -3,3 +3,9 @@ import javascript query predicate test_SystemCommandExecution(SystemCommandExecution cmd, DataFlow::Node res) { res = cmd.getACommandArgument() } + +query predicate test_SystemCommandExecution_getOptionsArg( + SystemCommandExecution cmd, DataFlow::Node res +) { + res = cmd.getOptionsArg() +} diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/exec.js b/javascript/ql/test/library-tests/frameworks/NodeJSLib/exec.js index d22f0bb760d8..fc7d5702a4ab 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/exec.js +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/exec.js @@ -5,3 +5,17 @@ cp.execFileSync("sh", ["-c", "node --version"]); cp.fork("foo", ["arg"]); cp.spawn("echo", ["Hi"], cb); cp.spawnSync("echo", ["Hi", "there"]); + +cp.spawn("echo", { shell: true }); +cp.spawn("echo", ["Hi"], { shell: true }); +cp.execFile("echo", { shell: "/bin/sh" }); +cp.execFile("echo", ["Hi"], { shell: "/bin/sh" }); + +function overloads(args, callback) { + cp.exec("echo", callback); + cp.execFile("echo", args, callback); + cp.spawn("echo", args, { shell: true }); + cp.execFile("echo", args, { shell: "/bin/sh" }); + cp.execFile("echo", args, { shell: true }, callback); + cp.execFile("echo", { shell: true }, callback); +} diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/tests.expected b/javascript/ql/test/library-tests/frameworks/NodeJSLib/tests.expected index 8da9b5087438..8005f75f790e 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/tests.expected @@ -230,6 +230,16 @@ test_SystemCommandExecution | exec.js:5:1:5:23 | cp.fork ... "arg"]) | exec.js:5:9:5:13 | "foo" | | exec.js:6:1:6:28 | cp.spaw ... "], cb) | exec.js:6:10:6:15 | "echo" | | exec.js:7:1:7:37 | cp.spaw ... here"]) | exec.js:7:14:7:19 | "echo" | +| exec.js:9:1:9:33 | cp.spaw ... true }) | exec.js:9:10:9:15 | "echo" | +| exec.js:10:1:10:41 | cp.spaw ... true }) | exec.js:10:10:10:15 | "echo" | +| exec.js:11:1:11:41 | cp.exec ... /sh" }) | exec.js:11:13:11:18 | "echo" | +| exec.js:12:1:12:49 | cp.exec ... /sh" }) | exec.js:12:13:12:18 | "echo" | +| exec.js:15:5:15:29 | cp.exec ... llback) | exec.js:15:13:15:18 | "echo" | +| exec.js:16:5:16:39 | cp.exec ... llback) | exec.js:16:17:16:22 | "echo" | +| exec.js:17:5:17:43 | cp.spaw ... true }) | exec.js:17:14:17:19 | "echo" | +| exec.js:18:5:18:51 | cp.exec ... /sh" }) | exec.js:18:17:18:22 | "echo" | +| exec.js:19:5:19:56 | cp.exec ... llback) | exec.js:19:17:19:22 | "echo" | +| exec.js:20:5:20:50 | cp.exec ... llback) | exec.js:20:17:20:22 | "echo" | test_HeaderDefinition_defines | src/http.js:13:3:13:44 | res.set ... /html') | content-type | text/html | | src/https.js:13:3:13:44 | res.set ... /html') | content-type | text/html | @@ -417,3 +427,19 @@ test_SystemCommandExecution_getAnArgumentForCommand | exec.js:5:1:5:23 | cp.fork ... "arg"]) | exec.js:5:16:5:22 | ["arg"] | | exec.js:6:1:6:28 | cp.spaw ... "], cb) | exec.js:6:18:6:23 | ["Hi"] | | exec.js:7:1:7:37 | cp.spaw ... here"]) | exec.js:7:22:7:36 | ["Hi", "there"] | +| exec.js:10:1:10:41 | cp.spaw ... true }) | exec.js:10:18:10:23 | ["Hi"] | +| exec.js:12:1:12:49 | cp.exec ... /sh" }) | exec.js:12:21:12:26 | ["Hi"] | +| exec.js:16:5:16:39 | cp.exec ... llback) | exec.js:16:25:16:28 | args | +| exec.js:17:5:17:43 | cp.spaw ... true }) | exec.js:17:22:17:25 | args | +| exec.js:18:5:18:51 | cp.exec ... /sh" }) | exec.js:18:25:18:28 | args | +| exec.js:19:5:19:56 | cp.exec ... llback) | exec.js:19:25:19:28 | args | +test_SystemCommandExecution_getOptionsArg +| exec.js:6:1:6:28 | cp.spaw ... "], cb) | exec.js:6:26:6:27 | cb | +| exec.js:9:1:9:33 | cp.spaw ... true }) | exec.js:9:18:9:32 | { shell: true } | +| exec.js:10:1:10:41 | cp.spaw ... true }) | exec.js:10:26:10:40 | { shell: true } | +| exec.js:11:1:11:41 | cp.exec ... /sh" }) | exec.js:11:21:11:40 | { shell: "/bin/sh" } | +| exec.js:12:1:12:49 | cp.exec ... /sh" }) | exec.js:12:29:12:48 | { shell: "/bin/sh" } | +| exec.js:17:5:17:43 | cp.spaw ... true }) | exec.js:17:28:17:42 | { shell: true } | +| exec.js:18:5:18:51 | cp.exec ... /sh" }) | exec.js:18:31:18:50 | { shell: "/bin/sh" } | +| exec.js:19:5:19:56 | cp.exec ... llback) | exec.js:19:31:19:45 | { shell: true } | +| exec.js:20:5:20:50 | cp.exec ... llback) | exec.js:20:25:20:39 | { shell: true } | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected index ddebc6baeaf3..2fecf64d818f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/CommandInjection.expected @@ -21,6 +21,29 @@ | child_process-test.js:75:29:75:31 | cmd | child_process-test.js:73:25:73:31 | req.url | child_process-test.js:75:29:75:31 | cmd | This command line depends on a $@. | child_process-test.js:73:25:73:31 | req.url | user-provided value | | child_process-test.js:83:19:83:36 | req.query.fileName | child_process-test.js:83:19:83:36 | req.query.fileName | child_process-test.js:83:19:83:36 | req.query.fileName | This command line depends on a $@. | child_process-test.js:83:19:83:36 | req.query.fileName | user-provided value | | child_process-test.js:94:11:94:35 | "ping " ... ms.host | child_process-test.js:94:21:94:30 | ctx.params | child_process-test.js:94:11:94:35 | "ping " ... ms.host | This command line depends on a $@. | child_process-test.js:94:21:94:30 | ctx.params | user-provided value | +| child_process-test.js:100:22:100:36 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:100:22:100:36 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:101:22:101:36 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:101:22:101:36 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:102:26:102:40 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:102:26:102:40 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:103:26:103:40 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:103:26:103:40 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:104:25:104:39 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:104:25:104:39 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:105:25:105:39 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:105:25:105:39 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:106:25:106:39 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:106:25:106:39 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:107:25:107:39 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:107:25:107:39 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:108:29:108:43 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:108:29:108:43 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:109:29:109:43 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:109:29:109:43 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:110:41:110:55 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:110:41:110:55 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:111:41:111:55 | ["hello", name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:111:41:111:55 | ["hello", name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:116:22:116:25 | args | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:116:22:116:25 | args | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:117:25:117:28 | args | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:117:25:117:28 | args | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:122:22:122:38 | arrayWithProperty | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:122:22:122:38 | arrayWithProperty | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:126:22:126:27 | pushed | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:126:22:126:27 | pushed | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:129:25:129:31 | shifted | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:129:25:129:31 | shifted | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:161:22:161:27 | [name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:161:22:161:27 | [name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:171:22:171:27 | [name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:171:22:171:27 | [name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:180:22:180:27 | [name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:180:22:180:27 | [name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:184:26:184:29 | args | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:184:26:184:29 | args | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:185:29:185:32 | args | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:185:29:185:32 | args | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | +| child_process-test.js:191:26:191:31 | [name] | child_process-test.js:98:28:98:34 | req.url | child_process-test.js:191:26:191:31 | [name] | This command line depends on a $@. | child_process-test.js:98:28:98:34 | req.url | user-provided value | | command-line-libs.js:14:8:14:18 | options.cmd | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:14:8:14:18 | options.cmd | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value | | command-line-libs.js:15:8:15:18 | program.cmd | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:15:8:15:18 | program.cmd | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value | | command-line-libs.js:21:12:21:17 | script | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:21:12:21:17 | script | This command line depends on a $@. | command-line-libs.js:9:16:9:23 | req.body | user-provided value | @@ -151,6 +174,122 @@ edges | child_process-test.js:73:15:73:38 | url.par ... , true) | child_process-test.js:73:9:73:11 | cmd | provenance | | | child_process-test.js:73:25:73:31 | req.url | child_process-test.js:73:15:73:38 | url.par ... , true) | provenance | | | child_process-test.js:94:21:94:30 | ctx.params | child_process-test.js:94:11:94:35 | "ping " ... ms.host | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:100:32:100:35 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:101:32:101:35 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:102:36:102:39 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:103:36:103:39 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:104:35:104:38 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:105:35:105:38 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:106:35:106:38 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:107:35:107:38 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:108:39:108:42 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:109:39:109:42 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:110:51:110:54 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:111:51:111:54 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:115:28:115:31 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:120:32:120:35 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:125:17:125:20 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:128:21:128:24 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:131:23:131:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:132:23:132:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:133:23:133:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:134:27:134:30 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:135:27:135:30 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:136:27:136:30 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:137:26:137:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:138:26:138:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:139:26:139:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:140:30:140:33 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:141:30:141:33 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:142:30:142:33 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:148:20:148:23 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:151:26:151:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:155:23:155:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:158:26:158:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:161:23:161:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:164:26:164:29 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:166:23:166:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:171:23:171:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:177:23:177:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:180:23:180:26 | name | provenance | | +| child_process-test.js:98:11:98:14 | name | child_process-test.js:187:18:187:21 | name | provenance | | +| child_process-test.js:98:18:98:41 | url.par ... , true) | child_process-test.js:98:11:98:14 | name | provenance | | +| child_process-test.js:98:28:98:34 | req.url | child_process-test.js:98:18:98:41 | url.par ... , true) | provenance | | +| child_process-test.js:100:32:100:35 | name | child_process-test.js:100:22:100:36 | ["hello", name] | provenance | | +| child_process-test.js:100:32:100:35 | name | child_process-test.js:101:32:101:35 | name | provenance | | +| child_process-test.js:101:32:101:35 | name | child_process-test.js:101:22:101:36 | ["hello", name] | provenance | | +| child_process-test.js:101:32:101:35 | name | child_process-test.js:102:36:102:39 | name | provenance | | +| child_process-test.js:102:36:102:39 | name | child_process-test.js:102:26:102:40 | ["hello", name] | provenance | | +| child_process-test.js:102:36:102:39 | name | child_process-test.js:103:36:103:39 | name | provenance | | +| child_process-test.js:103:36:103:39 | name | child_process-test.js:103:26:103:40 | ["hello", name] | provenance | | +| child_process-test.js:103:36:103:39 | name | child_process-test.js:104:35:104:38 | name | provenance | | +| child_process-test.js:104:35:104:38 | name | child_process-test.js:104:25:104:39 | ["hello", name] | provenance | | +| child_process-test.js:104:35:104:38 | name | child_process-test.js:105:35:105:38 | name | provenance | | +| child_process-test.js:105:35:105:38 | name | child_process-test.js:105:25:105:39 | ["hello", name] | provenance | | +| child_process-test.js:105:35:105:38 | name | child_process-test.js:106:35:106:38 | name | provenance | | +| child_process-test.js:106:35:106:38 | name | child_process-test.js:106:25:106:39 | ["hello", name] | provenance | | +| child_process-test.js:106:35:106:38 | name | child_process-test.js:107:35:107:38 | name | provenance | | +| child_process-test.js:107:35:107:38 | name | child_process-test.js:107:25:107:39 | ["hello", name] | provenance | | +| child_process-test.js:107:35:107:38 | name | child_process-test.js:108:39:108:42 | name | provenance | | +| child_process-test.js:108:39:108:42 | name | child_process-test.js:108:29:108:43 | ["hello", name] | provenance | | +| child_process-test.js:108:39:108:42 | name | child_process-test.js:109:39:109:42 | name | provenance | | +| child_process-test.js:109:39:109:42 | name | child_process-test.js:109:29:109:43 | ["hello", name] | provenance | | +| child_process-test.js:109:39:109:42 | name | child_process-test.js:110:51:110:54 | name | provenance | | +| child_process-test.js:110:51:110:54 | name | child_process-test.js:110:41:110:55 | ["hello", name] | provenance | | +| child_process-test.js:110:51:110:54 | name | child_process-test.js:111:51:111:54 | name | provenance | | +| child_process-test.js:111:51:111:54 | name | child_process-test.js:111:41:111:55 | ["hello", name] | provenance | | +| child_process-test.js:111:51:111:54 | name | child_process-test.js:115:28:115:31 | name | provenance | | +| child_process-test.js:115:11:115:14 | args [1] | child_process-test.js:116:22:116:25 | args | provenance | | +| child_process-test.js:115:11:115:14 | args [1] | child_process-test.js:117:25:117:28 | args | provenance | | +| child_process-test.js:115:18:115:32 | ["hello", name] [1] | child_process-test.js:115:11:115:14 | args [1] | provenance | | +| child_process-test.js:115:28:115:31 | name | child_process-test.js:115:18:115:32 | ["hello", name] [1] | provenance | | +| child_process-test.js:115:28:115:31 | name | child_process-test.js:120:32:120:35 | name | provenance | | +| child_process-test.js:120:11:120:27 | arrayWithProperty [0] | child_process-test.js:122:22:122:38 | arrayWithProperty | provenance | | +| child_process-test.js:120:31:120:36 | [name] [0] | child_process-test.js:120:11:120:27 | arrayWithProperty [0] | provenance | | +| child_process-test.js:120:32:120:35 | name | child_process-test.js:120:31:120:36 | [name] [0] | provenance | | +| child_process-test.js:120:32:120:35 | name | child_process-test.js:125:17:125:20 | name | provenance | | +| child_process-test.js:125:5:125:10 | [post update] pushed [ArrayElement] | child_process-test.js:126:22:126:27 | pushed | provenance | | +| child_process-test.js:125:17:125:20 | name | child_process-test.js:125:5:125:10 | [post update] pushed [ArrayElement] | provenance | | +| child_process-test.js:125:17:125:20 | name | child_process-test.js:128:21:128:24 | name | provenance | | +| child_process-test.js:128:5:128:11 | [post update] shifted [ArrayElement] | child_process-test.js:129:25:129:31 | shifted | provenance | | +| child_process-test.js:128:21:128:24 | name | child_process-test.js:128:5:128:11 | [post update] shifted [ArrayElement] | provenance | | +| child_process-test.js:128:21:128:24 | name | child_process-test.js:131:23:131:26 | name | provenance | | +| child_process-test.js:131:23:131:26 | name | child_process-test.js:132:23:132:26 | name | provenance | | +| child_process-test.js:132:23:132:26 | name | child_process-test.js:133:23:133:26 | name | provenance | | +| child_process-test.js:133:23:133:26 | name | child_process-test.js:134:27:134:30 | name | provenance | | +| child_process-test.js:134:27:134:30 | name | child_process-test.js:135:27:135:30 | name | provenance | | +| child_process-test.js:135:27:135:30 | name | child_process-test.js:136:27:136:30 | name | provenance | | +| child_process-test.js:136:27:136:30 | name | child_process-test.js:137:26:137:29 | name | provenance | | +| child_process-test.js:137:26:137:29 | name | child_process-test.js:138:26:138:29 | name | provenance | | +| child_process-test.js:138:26:138:29 | name | child_process-test.js:139:26:139:29 | name | provenance | | +| child_process-test.js:139:26:139:29 | name | child_process-test.js:140:30:140:33 | name | provenance | | +| child_process-test.js:140:30:140:33 | name | child_process-test.js:141:30:141:33 | name | provenance | | +| child_process-test.js:141:30:141:33 | name | child_process-test.js:142:30:142:33 | name | provenance | | +| child_process-test.js:142:30:142:33 | name | child_process-test.js:148:20:148:23 | name | provenance | | +| child_process-test.js:148:20:148:23 | name | child_process-test.js:151:26:151:29 | name | provenance | | +| child_process-test.js:151:26:151:29 | name | child_process-test.js:155:23:155:26 | name | provenance | | +| child_process-test.js:155:23:155:26 | name | child_process-test.js:158:26:158:29 | name | provenance | | +| child_process-test.js:158:26:158:29 | name | child_process-test.js:161:23:161:26 | name | provenance | | +| child_process-test.js:161:23:161:26 | name | child_process-test.js:161:22:161:27 | [name] | provenance | | +| child_process-test.js:161:23:161:26 | name | child_process-test.js:164:26:164:29 | name | provenance | | +| child_process-test.js:164:26:164:29 | name | child_process-test.js:166:23:166:26 | name | provenance | | +| child_process-test.js:166:23:166:26 | name | child_process-test.js:171:23:171:26 | name | provenance | | +| child_process-test.js:171:23:171:26 | name | child_process-test.js:171:22:171:27 | [name] | provenance | | +| child_process-test.js:171:23:171:26 | name | child_process-test.js:177:23:177:26 | name | provenance | | +| child_process-test.js:177:23:177:26 | name | child_process-test.js:180:23:180:26 | name | provenance | | +| child_process-test.js:180:23:180:26 | name | child_process-test.js:180:22:180:27 | [name] | provenance | | +| child_process-test.js:180:23:180:26 | name | child_process-test.js:187:18:187:21 | name | provenance | | +| child_process-test.js:183:26:183:29 | args [0] | child_process-test.js:184:26:184:29 | args | provenance | | +| child_process-test.js:183:26:183:29 | args [0] | child_process-test.js:185:29:185:32 | args | provenance | | +| child_process-test.js:187:17:187:22 | [name] [0] | child_process-test.js:183:26:183:29 | args [0] | provenance | | +| child_process-test.js:187:18:187:21 | name | child_process-test.js:187:17:187:22 | [name] [0] | provenance | | +| child_process-test.js:187:18:187:21 | name | child_process-test.js:189:5:192:5 | functio ... t\\n } [name] | provenance | | +| child_process-test.js:187:18:187:21 | name | child_process-test.js:193:5:193:18 | runWithOptions [name] | provenance | | +| child_process-test.js:189:5:192:5 | functio ... t\\n } [name] | child_process-test.js:189:14:189:27 | runWithOptions [name] | provenance | | +| child_process-test.js:189:5:192:5 | functio ... t\\n } [name] | child_process-test.js:191:27:191:30 | name | provenance | | +| child_process-test.js:189:14:189:27 | runWithOptions [name] | child_process-test.js:193:5:193:18 | runWithOptions [name] | provenance | | +| child_process-test.js:191:27:191:30 | name | child_process-test.js:191:26:191:31 | [name] | provenance | | +| child_process-test.js:193:5:193:18 | runWithOptions [name] | child_process-test.js:191:27:191:30 | name | provenance | | | command-line-libs.js:9:9:9:12 | args | command-line-libs.js:12:17:12:20 | args | provenance | | | command-line-libs.js:9:9:9:12 | args | command-line-libs.js:23:29:23:32 | args | provenance | | | command-line-libs.js:9:16:9:23 | req.body | command-line-libs.js:9:9:9:12 | args | provenance | | @@ -390,6 +529,83 @@ nodes | child_process-test.js:83:19:83:36 | req.query.fileName | semmle.label | req.query.fileName | | child_process-test.js:94:11:94:35 | "ping " ... ms.host | semmle.label | "ping " ... ms.host | | child_process-test.js:94:21:94:30 | ctx.params | semmle.label | ctx.params | +| child_process-test.js:98:11:98:14 | name | semmle.label | name | +| child_process-test.js:98:18:98:41 | url.par ... , true) | semmle.label | url.par ... , true) | +| child_process-test.js:98:28:98:34 | req.url | semmle.label | req.url | +| child_process-test.js:100:22:100:36 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:100:32:100:35 | name | semmle.label | name | +| child_process-test.js:101:22:101:36 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:101:32:101:35 | name | semmle.label | name | +| child_process-test.js:102:26:102:40 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:102:36:102:39 | name | semmle.label | name | +| child_process-test.js:103:26:103:40 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:103:36:103:39 | name | semmle.label | name | +| child_process-test.js:104:25:104:39 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:104:35:104:38 | name | semmle.label | name | +| child_process-test.js:105:25:105:39 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:105:35:105:38 | name | semmle.label | name | +| child_process-test.js:106:25:106:39 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:106:35:106:38 | name | semmle.label | name | +| child_process-test.js:107:25:107:39 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:107:35:107:38 | name | semmle.label | name | +| child_process-test.js:108:29:108:43 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:108:39:108:42 | name | semmle.label | name | +| child_process-test.js:109:29:109:43 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:109:39:109:42 | name | semmle.label | name | +| child_process-test.js:110:41:110:55 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:110:51:110:54 | name | semmle.label | name | +| child_process-test.js:111:41:111:55 | ["hello", name] | semmle.label | ["hello", name] | +| child_process-test.js:111:51:111:54 | name | semmle.label | name | +| child_process-test.js:115:11:115:14 | args [1] | semmle.label | args [1] | +| child_process-test.js:115:18:115:32 | ["hello", name] [1] | semmle.label | ["hello", name] [1] | +| child_process-test.js:115:28:115:31 | name | semmle.label | name | +| child_process-test.js:116:22:116:25 | args | semmle.label | args | +| child_process-test.js:117:25:117:28 | args | semmle.label | args | +| child_process-test.js:120:11:120:27 | arrayWithProperty [0] | semmle.label | arrayWithProperty [0] | +| child_process-test.js:120:31:120:36 | [name] [0] | semmle.label | [name] [0] | +| child_process-test.js:120:32:120:35 | name | semmle.label | name | +| child_process-test.js:122:22:122:38 | arrayWithProperty | semmle.label | arrayWithProperty | +| child_process-test.js:125:5:125:10 | [post update] pushed [ArrayElement] | semmle.label | [post update] pushed [ArrayElement] | +| child_process-test.js:125:17:125:20 | name | semmle.label | name | +| child_process-test.js:126:22:126:27 | pushed | semmle.label | pushed | +| child_process-test.js:128:5:128:11 | [post update] shifted [ArrayElement] | semmle.label | [post update] shifted [ArrayElement] | +| child_process-test.js:128:21:128:24 | name | semmle.label | name | +| child_process-test.js:129:25:129:31 | shifted | semmle.label | shifted | +| child_process-test.js:131:23:131:26 | name | semmle.label | name | +| child_process-test.js:132:23:132:26 | name | semmle.label | name | +| child_process-test.js:133:23:133:26 | name | semmle.label | name | +| child_process-test.js:134:27:134:30 | name | semmle.label | name | +| child_process-test.js:135:27:135:30 | name | semmle.label | name | +| child_process-test.js:136:27:136:30 | name | semmle.label | name | +| child_process-test.js:137:26:137:29 | name | semmle.label | name | +| child_process-test.js:138:26:138:29 | name | semmle.label | name | +| child_process-test.js:139:26:139:29 | name | semmle.label | name | +| child_process-test.js:140:30:140:33 | name | semmle.label | name | +| child_process-test.js:141:30:141:33 | name | semmle.label | name | +| child_process-test.js:142:30:142:33 | name | semmle.label | name | +| child_process-test.js:148:20:148:23 | name | semmle.label | name | +| child_process-test.js:151:26:151:29 | name | semmle.label | name | +| child_process-test.js:155:23:155:26 | name | semmle.label | name | +| child_process-test.js:158:26:158:29 | name | semmle.label | name | +| child_process-test.js:161:22:161:27 | [name] | semmle.label | [name] | +| child_process-test.js:161:23:161:26 | name | semmle.label | name | +| child_process-test.js:164:26:164:29 | name | semmle.label | name | +| child_process-test.js:166:23:166:26 | name | semmle.label | name | +| child_process-test.js:171:22:171:27 | [name] | semmle.label | [name] | +| child_process-test.js:171:23:171:26 | name | semmle.label | name | +| child_process-test.js:177:23:177:26 | name | semmle.label | name | +| child_process-test.js:180:22:180:27 | [name] | semmle.label | [name] | +| child_process-test.js:180:23:180:26 | name | semmle.label | name | +| child_process-test.js:183:26:183:29 | args [0] | semmle.label | args [0] | +| child_process-test.js:184:26:184:29 | args | semmle.label | args | +| child_process-test.js:185:29:185:32 | args | semmle.label | args | +| child_process-test.js:187:17:187:22 | [name] [0] | semmle.label | [name] [0] | +| child_process-test.js:187:18:187:21 | name | semmle.label | name | +| child_process-test.js:189:5:192:5 | functio ... t\\n } [name] | semmle.label | functio ... t\\n } [name] | +| child_process-test.js:189:14:189:27 | runWithOptions [name] | semmle.label | runWithOptions [name] | +| child_process-test.js:191:26:191:31 | [name] | semmle.label | [name] | +| child_process-test.js:191:27:191:30 | name | semmle.label | name | +| child_process-test.js:193:5:193:18 | runWithOptions [name] | semmle.label | runWithOptions [name] | | command-line-libs.js:9:9:9:12 | args | semmle.label | args | | command-line-libs.js:9:16:9:23 | req.body | semmle.label | req.body | | command-line-libs.js:12:17:12:20 | args | semmle.label | args | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/child_process-test.js b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/child_process-test.js index 5fea8a3e4d68..b6d93e14a19d 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/child_process-test.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/child_process-test.js @@ -93,3 +93,112 @@ const router = new Router(); router.get("/ping/:host", async (ctx) => { cp.exec("ping " + ctx.params.host); // $ Alert }); + +http.createServer(function(req, res) { + const name = url.parse(req.url, true).query.name; // $ Source + + cp.spawn("echo", ["hello", name], { shell: true }); // $ Alert + cp.spawn("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.spawnSync("echo", ["hello", name], { shell: true }); // $ Alert + cp.spawnSync("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: true }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: true }, () => {}); // $ Alert + cp.execFile("echo", ["hello", name], { shell: "/bin/sh" }, () => {}); // $ Alert + cp.execFileSync("echo", ["hello", name], { shell: true }); // $ Alert + cp.execFileSync("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + util.promisify(cp.execFile)("echo", ["hello", name], { shell: true }); // $ Alert + util.promisify(cp.execFile)("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + + const shell = "/bin/sh"; + const options = { shell }; + const args = ["hello", name]; + cp.spawn("echo", args, options); // $ Alert + cp.execFile("echo", args, options); // $ Alert + + + const arrayWithProperty = [name]; + arrayWithProperty.shell = false; + cp.spawn("echo", arrayWithProperty, { shell: true }); // $ Alert + + const pushed = []; + pushed.push(name); + cp.spawn("echo", pushed, { shell: true }); // $ Alert + const shifted = []; + shifted.unshift(name); + cp.execFile("echo", shifted, { shell: "/bin/sh" }); // $ Alert + + cp.spawn("echo", [name]); + cp.spawn("echo", [name], { shell: false }); + cp.spawn("echo", [name], { shell: "" }); + cp.spawnSync("echo", [name]); + cp.spawnSync("echo", [name], { shell: false }); + cp.spawnSync("echo", [name], { shell: "" }); + cp.execFile("echo", [name]); + cp.execFile("echo", [name], { shell: false }); + cp.execFile("echo", [name], { shell: "" }); + cp.execFileSync("echo", [name]); + cp.execFileSync("echo", [name], { shell: false }); + cp.execFileSync("echo", [name], { shell: "" }); + cp.spawn("echo", ["hello"], { shell: true }); + cp.execFile("echo", ["hello"], { shell: "/bin/sh" }); + + const laterPush = []; + cp.spawn("echo", laterPush, { shell: true }); + laterPush.push(name); + const laterUnshift = []; + cp.execFile("echo", laterUnshift, { shell: "/bin/sh" }); + laterUnshift.unshift(name); + + const disabled = { shell: "/bin/sh" }; + disabled.shell = false; + cp.spawn("echo", [name], disabled); + const empty = { shell: true }; + empty.shell = ""; + cp.execFile("echo", [name], empty); + const enabled = { shell: false }; + enabled.shell = "/bin/sh"; + cp.spawn("echo", [name], enabled); // $ Alert + const alias = enabled; + alias.shell = false; + cp.execFile("echo", [name], enabled); + const changedAfterCall = { shell: false }; + cp.spawn("echo", [name], changedAfterCall); + changedAfterCall.shell = true; + + const conditional = { shell: "/bin/sh" }; + if (process.env.DISABLE_SHELL) conditional.shell = false; + cp.spawn("echo", [name], conditional); // $ Alert + + + const disabledOnBothBranches = { shell: true }; + if (process.env.DISABLE_SHELL) disabledOnBothBranches.shell = false; + else disabledOnBothBranches.shell = ""; + cp.spawn("echo", [name], disabledOnBothBranches); + + const disabledAfterCall = { shell: true }; + cp.spawn("echo", [name], disabledAfterCall); // $ Alert + disabledAfterCall.shell = false; + + function runWithArgs(args) { + cp.spawn("echo", args, { shell: true }); // $ Alert + cp.execFile("echo", args, { shell: "/bin/sh" }); // $ Alert + } + runWithArgs([name]); + + function runWithOptions(options) { + if (process.env.DISABLE_SHELL) options.shell = false; + cp.spawn("echo", [name], options); // $ Alert + } + runWithOptions({ shell: true }); +}); + +http.createServer(function(req, res) { + const options = req.body; + options.shell = true; + cp.spawn("echo", options); + cp.spawnSync("echo", options); + cp.execFile("echo", options); + cp.execFile("echo", options, () => {}); + cp.execFileSync("echo", options); +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected index 564d9ca221e7..6fb03c27bb06 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected @@ -61,15 +61,13 @@ | lib/lib.js:351:10:351:27 | "rm -rf " + unsafe | lib/lib.js:349:29:349:34 | unsafe | lib/lib.js:351:22:351:27 | unsafe | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:349:29:349:34 | unsafe | library input | lib/lib.js:351:2:351:28 | cp.exec ... unsafe) | shell command | | lib/lib.js:406:10:406:25 | "rm -rf " + name | lib/lib.js:405:39:405:42 | name | lib/lib.js:406:22:406:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:405:39:405:42 | name | library input | lib/lib.js:406:2:406:26 | cp.exec ... + name) | shell command | | lib/lib.js:415:10:415:25 | "rm -rf " + name | lib/lib.js:414:40:414:43 | name | lib/lib.js:415:22:415:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:414:40:414:43 | name | library input | lib/lib.js:415:2:415:26 | cp.exec ... + name) | shell command | -| lib/lib.js:417:28:417:31 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:417:28:417:31 | 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:417:2:417:66 | cp.exec ... => {}) | shell command | -| lib/lib.js:418:25:418:28 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:418:25:418:28 | 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:418:2:418:45 | cp.spaw ... true}) | shell command | -| lib/lib.js:419:32:419:35 | name | lib/lib.js:414:40:414:43 | name | lib/lib.js:419:32:419:35 | 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:419:2:419:52 | cp.exec ... true}) | shell command | -| 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 | -| 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 | -| 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 | +| lib/lib.js:417:20:417:32 | ["-rf", name] | lib/lib.js:414:40:414:43 | name | lib/lib.js:417:20:417:32 | ["-rf", 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:417:2:417:66 | cp.exec ... => {}) | shell command | +| lib/lib.js:418:17:418:29 | ["-rf", name] | lib/lib.js:414:40:414:43 | name | lib/lib.js:418:17:418:29 | ["-rf", 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:418:2:418:45 | cp.spaw ... true}) | shell command | +| lib/lib.js:419:24:419:36 | ["-rf", name] | lib/lib.js:414:40:414:43 | name | lib/lib.js:419:24:419:36 | ["-rf", 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:419:2:419:52 | cp.exec ... true}) | shell command | +| lib/lib.js:420:21:420:33 | ["-rf", name] | lib/lib.js:414:40:414:43 | name | lib/lib.js:420:21:420:33 | ["-rf", 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 | +| lib/lib.js:424:14:424:28 | ["first", name] | lib/lib.js:414:40:414:43 | name | lib/lib.js:424:14:424:28 | ["first", 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 | | 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 | | 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 | -| 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 | | 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 | | 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 | | lib/lib.js:478:27:478:46 | config.installedPath | lib/lib.js:477:39:477:44 | config | lib/lib.js:478:27:478:46 | config.installedPath | This path concatenation which depends on $@ is later used in a $@. | lib/lib.js:477:39:477:44 | config | library input | lib/lib.js:479:12:479:20 | exec(cmd) | shell command | @@ -84,7 +82,6 @@ | lib/lib.js:543:11:543:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:543:23:543:26 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:543:3:543:27 | cp.exec ... + name) | shell command | | lib/lib.js:545:11:545:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:545:3:545:27 | cp.exec ... + name) | shell command | | lib/lib.js:552:23:552:26 | args | lib/lib.js:550:39:550:42 | name | lib/lib.js:552:23:552:26 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:550:39:550:42 | name | library input | lib/lib.js:552:9:552:38 | cp.spaw ... wnOpts) | shell command | -| lib/lib.js:555:33:555:36 | name | lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:550:39:550:42 | name | library input | lib/lib.js:552:9:552:38 | cp.spaw ... wnOpts) | shell command | | lib/lib.js:560:14:560:29 | "rm -rf " + name | lib/lib.js:558:41:558:44 | name | lib/lib.js:560:26:560:29 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:558:41:558:44 | name | library input | lib/lib.js:560:9:560:30 | exec("r ... + name) | shell command | | lib/lib.js:562:14:562:29 | "rm -rf " + name | lib/lib.js:558:41:558:44 | name | lib/lib.js:562:26:562:29 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:558:41:558:44 | name | library input | lib/lib.js:562:9:562:30 | exec("r ... + name) | shell command | | lib/lib.js:566:14:566:29 | "rm -rf " + name | lib/lib.js:558:41:558:44 | name | lib/lib.js:566:26:566:29 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:558:41:558:44 | name | library input | lib/lib.js:566:9:566:30 | exec("r ... + name) | shell command | @@ -97,6 +94,29 @@ | lib/lib.js:629:13:629:28 | "rm -rf " + name | lib/lib.js:608:42:608:45 | name | lib/lib.js:629:25:629:28 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:608:42:608:45 | name | library input | lib/lib.js:629:5:629:29 | cp.exec ... + name) | shell command | | lib/lib.js:633:18:633:68 | "'" + n ... ) + "'" | lib/lib.js:632:38:632:41 | name | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:632:38:632:41 | name | library input | lib/lib.js:634:2:634:31 | cp.exec ... itized) | shell command | | lib/lib.js:634:10:634:30 | "rm -rf ... nitized | lib/lib.js:632:38:632:41 | name | lib/lib.js:634:22:634:30 | sanitized | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:632:38:632:41 | name | library input | lib/lib.js:634:2:634:31 | cp.exec ... itized) | shell command | +| lib/lib.js:645:22:645:36 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:645:22:645:36 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:645:5:645:54 | cp.spaw ... true }) | shell command | +| lib/lib.js:646:22:646:36 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:646:22:646:36 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:646:5:646:59 | cp.spaw ... /sh" }) | shell command | +| lib/lib.js:647:26:647:40 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:647:26:647:40 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:647:5:647:58 | cp.spaw ... true }) | shell command | +| lib/lib.js:648:26:648:40 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:648:26:648:40 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:648:5:648:63 | cp.spaw ... /sh" }) | shell command | +| lib/lib.js:649:25:649:39 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:649:25:649:39 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:649:5:649:57 | cp.exec ... true }) | shell command | +| lib/lib.js:650:25:650:39 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:650:25:650:39 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:650:5:650:62 | cp.exec ... /sh" }) | shell command | +| lib/lib.js:651:25:651:39 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:651:25:651:39 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:651:5:651:67 | cp.exec ... => {}) | shell command | +| lib/lib.js:652:25:652:39 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:652:25:652:39 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:652:5:652:72 | cp.exec ... => {}) | shell command | +| lib/lib.js:653:29:653:43 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:653:29:653:43 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:653:5:653:61 | cp.exec ... true }) | shell command | +| lib/lib.js:654:29:654:43 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:654:29:654:43 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:654:5:654:66 | cp.exec ... /sh" }) | shell command | +| lib/lib.js:655:41:655:55 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:655:41:655:55 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:655:5:655:73 | util.pr ... true }) | shell command | +| lib/lib.js:656:41:656:55 | ["hello", name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:656:41:656:55 | ["hello", name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:656:5:656:78 | util.pr ... /sh" }) | shell command | +| lib/lib.js:661:22:661:25 | args | lib/lib.js:643:48:643:51 | name | lib/lib.js:661:22:661:25 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:661:5:661:35 | cp.spaw ... ptions) | shell command | +| lib/lib.js:662:25:662:28 | args | lib/lib.js:643:48:643:51 | name | lib/lib.js:662:25:662:28 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:662:5:662:38 | cp.exec ... ptions) | shell command | +| lib/lib.js:667:22:667:38 | arrayWithProperty | lib/lib.js:643:48:643:51 | name | lib/lib.js:667:22:667:38 | arrayWithProperty | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:667:5:667:56 | cp.spaw ... true }) | shell command | +| lib/lib.js:671:22:671:27 | pushed | lib/lib.js:643:48:643:51 | name | lib/lib.js:671:22:671:27 | pushed | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:671:5:671:45 | cp.spaw ... true }) | shell command | +| lib/lib.js:674:25:674:31 | shifted | lib/lib.js:643:48:643:51 | name | lib/lib.js:674:25:674:31 | shifted | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:674:5:674:54 | cp.exec ... /sh" }) | shell command | +| lib/lib.js:706:22:706:27 | [name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:706:22:706:27 | [name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:706:5:706:37 | cp.spaw ... nabled) | shell command | +| lib/lib.js:716:22:716:27 | [name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:716:22:716:27 | [name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:716:5:716:41 | cp.spaw ... tional) | shell command | +| lib/lib.js:725:22:725:27 | [name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:725:22:725:27 | [name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:725:5:725:47 | cp.spaw ... erCall) | shell command | +| lib/lib.js:729:26:729:29 | args | lib/lib.js:643:48:643:51 | name | lib/lib.js:729:26:729:29 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:729:9:729:47 | cp.spaw ... true }) | shell command | +| lib/lib.js:730:29:730:32 | args | lib/lib.js:643:48:643:51 | name | lib/lib.js:730:29:730:32 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:730:9:730:55 | cp.exec ... /sh" }) | shell command | +| lib/lib.js:736:26:736:31 | [name] | lib/lib.js:643:48:643:51 | name | lib/lib.js:736:26:736:31 | [name] | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:643:48:643:51 | name | library input | lib/lib.js:736:9:736:41 | cp.spaw ... ptions) | shell command | | lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib2/compiled-file.ts:3:26:3:29 | name | library input | lib/subLib2/compiled-file.ts:4:5:4:29 | cp.exec ... + name) | shell command | | lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | lib/subLib2/special-file.js:3:28:3:31 | name | lib/subLib2/special-file.js:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib2/special-file.js:3:28:3:31 | name | library input | lib/subLib2/special-file.js:4:2:4:26 | cp.exec ... + name) | shell command | | lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | lib/subLib3/my-file.ts:3:28:3:31 | name | lib/subLib3/my-file.ts:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib3/my-file.ts:3:28:3:31 | name | library input | lib/subLib3/my-file.ts:4:2:4:26 | cp.exec ... + name) | shell command | @@ -194,15 +214,18 @@ edges | lib/lib.js:414:40:414:43 | name | lib/lib.js:420:29:420:32 | name | provenance | | | lib/lib.js:414:40:414:43 | name | lib/lib.js:424:24:424:27 | name | provenance | | | lib/lib.js:414:40:414:43 | name | lib/lib.js:426:11:426:14 | name | provenance | | -| lib/lib.js:414:40:414:43 | name | lib/lib.js:426:11:426:14 | name | provenance | | | lib/lib.js:414:40:414:43 | name | lib/lib.js:428:36:428:39 | name | provenance | | +| lib/lib.js:417:28:417:31 | name | lib/lib.js:417:20:417:32 | ["-rf", name] | provenance | | +| lib/lib.js:418:25:418:28 | name | lib/lib.js:418:17:418:29 | ["-rf", name] | provenance | | +| lib/lib.js:419:32:419:35 | name | lib/lib.js:419:24:419:36 | ["-rf", name] | provenance | | +| lib/lib.js:420:29:420:32 | name | lib/lib.js:420:21:420:33 | ["-rf", name] | provenance | | +| lib/lib.js:424:24:424:27 | name | lib/lib.js:424:14:424:28 | ["first", name] | provenance | | | lib/lib.js:426:2:426:4 | [post update] arr [ArrayElement] | lib/lib.js:427:14:427:16 | arr | provenance | | | lib/lib.js:426:11:426:14 | name | lib/lib.js:426:2:426:4 | [post update] arr [ArrayElement] | provenance | | | lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | lib/lib.js:428:14:428:58 | build(" ... + '-') | provenance | | | lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | lib/lib.js:431:23:431:26 | last | provenance | | | lib/lib.js:428:36:428:39 | name | lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | provenance | | | lib/lib.js:431:23:431:26 | last | lib/lib.js:436:19:436:22 | last | provenance | | -| lib/lib.js:431:23:431:26 | last | lib/lib.js:436:19:436:22 | last | provenance | | | lib/lib.js:436:10:436:12 | [post update] arr [ArrayElement] | lib/lib.js:437:9:437:11 | arr [ArrayElement] | provenance | | | lib/lib.js:436:19:436:22 | last | lib/lib.js:436:10:436:12 | [post update] arr [ArrayElement] | provenance | | | lib/lib.js:441:39:441:42 | name | lib/lib.js:442:24:442:27 | name | provenance | | @@ -220,7 +243,6 @@ edges | lib/lib.js:509:39:509:42 | name | lib/lib.js:543:23:543:26 | name | provenance | | | lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name | provenance | | | lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name | provenance | | -| lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name | provenance | | | lib/lib.js:551:33:551:36 | args [1] | lib/lib.js:552:23:552:26 | args | provenance | | | lib/lib.js:555:25:555:37 | ["-rf", name] [1] | lib/lib.js:551:33:551:36 | args [1] | provenance | | | lib/lib.js:555:33:555:36 | name | lib/lib.js:555:25:555:37 | ["-rf", name] [1] | provenance | | @@ -239,6 +261,120 @@ edges | lib/lib.js:633:24:633:27 | name | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | provenance | | | lib/lib.js:633:24:633:27 | name | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | provenance | | | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | lib/lib.js:633:6:633:14 | sanitized | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:645:32:645:35 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:646:32:646:35 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:647:36:647:39 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:648:36:648:39 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:649:35:649:38 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:650:35:650:38 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:651:35:651:38 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:652:35:652:38 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:653:39:653:42 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:654:39:654:42 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:655:51:655:54 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:656:51:656:54 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:660:28:660:31 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:665:32:665:35 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:670:17:670:20 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:673:21:673:24 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:676:23:676:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:677:23:677:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:678:23:678:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:679:27:679:30 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:680:27:680:30 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:681:27:681:30 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:682:26:682:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:683:26:683:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:684:26:684:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:685:30:685:33 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:686:30:686:33 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:687:30:687:33 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:693:20:693:23 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:696:26:696:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:700:23:700:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:703:26:703:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:706:23:706:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:709:26:709:29 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:711:23:711:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:716:23:716:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:722:23:722:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:725:23:725:26 | name | provenance | | +| lib/lib.js:643:48:643:51 | name | lib/lib.js:732:18:732:21 | name | provenance | | +| lib/lib.js:645:32:645:35 | name | lib/lib.js:645:22:645:36 | ["hello", name] | provenance | | +| lib/lib.js:645:32:645:35 | name | lib/lib.js:646:32:646:35 | name | provenance | | +| lib/lib.js:646:32:646:35 | name | lib/lib.js:646:22:646:36 | ["hello", name] | provenance | | +| lib/lib.js:646:32:646:35 | name | lib/lib.js:647:36:647:39 | name | provenance | | +| lib/lib.js:647:36:647:39 | name | lib/lib.js:647:26:647:40 | ["hello", name] | provenance | | +| lib/lib.js:647:36:647:39 | name | lib/lib.js:648:36:648:39 | name | provenance | | +| lib/lib.js:648:36:648:39 | name | lib/lib.js:648:26:648:40 | ["hello", name] | provenance | | +| lib/lib.js:648:36:648:39 | name | lib/lib.js:649:35:649:38 | name | provenance | | +| lib/lib.js:649:35:649:38 | name | lib/lib.js:649:25:649:39 | ["hello", name] | provenance | | +| lib/lib.js:649:35:649:38 | name | lib/lib.js:650:35:650:38 | name | provenance | | +| lib/lib.js:650:35:650:38 | name | lib/lib.js:650:25:650:39 | ["hello", name] | provenance | | +| lib/lib.js:650:35:650:38 | name | lib/lib.js:651:35:651:38 | name | provenance | | +| lib/lib.js:651:35:651:38 | name | lib/lib.js:651:25:651:39 | ["hello", name] | provenance | | +| lib/lib.js:651:35:651:38 | name | lib/lib.js:652:35:652:38 | name | provenance | | +| lib/lib.js:652:35:652:38 | name | lib/lib.js:652:25:652:39 | ["hello", name] | provenance | | +| lib/lib.js:652:35:652:38 | name | lib/lib.js:653:39:653:42 | name | provenance | | +| lib/lib.js:653:39:653:42 | name | lib/lib.js:653:29:653:43 | ["hello", name] | provenance | | +| lib/lib.js:653:39:653:42 | name | lib/lib.js:654:39:654:42 | name | provenance | | +| lib/lib.js:654:39:654:42 | name | lib/lib.js:654:29:654:43 | ["hello", name] | provenance | | +| lib/lib.js:654:39:654:42 | name | lib/lib.js:655:51:655:54 | name | provenance | | +| lib/lib.js:655:51:655:54 | name | lib/lib.js:655:41:655:55 | ["hello", name] | provenance | | +| lib/lib.js:655:51:655:54 | name | lib/lib.js:656:51:656:54 | name | provenance | | +| lib/lib.js:656:51:656:54 | name | lib/lib.js:656:41:656:55 | ["hello", name] | provenance | | +| lib/lib.js:656:51:656:54 | name | lib/lib.js:660:28:660:31 | name | provenance | | +| lib/lib.js:660:11:660:14 | args [1] | lib/lib.js:661:22:661:25 | args | provenance | | +| lib/lib.js:660:11:660:14 | args [1] | lib/lib.js:662:25:662:28 | args | provenance | | +| lib/lib.js:660:18:660:32 | ["hello", name] [1] | lib/lib.js:660:11:660:14 | args [1] | provenance | | +| lib/lib.js:660:28:660:31 | name | lib/lib.js:660:18:660:32 | ["hello", name] [1] | provenance | | +| lib/lib.js:660:28:660:31 | name | lib/lib.js:665:32:665:35 | name | provenance | | +| lib/lib.js:665:11:665:27 | arrayWithProperty [0] | lib/lib.js:667:22:667:38 | arrayWithProperty | provenance | | +| lib/lib.js:665:31:665:36 | [name] [0] | lib/lib.js:665:11:665:27 | arrayWithProperty [0] | provenance | | +| lib/lib.js:665:32:665:35 | name | lib/lib.js:665:31:665:36 | [name] [0] | provenance | | +| lib/lib.js:665:32:665:35 | name | lib/lib.js:670:17:670:20 | name | provenance | | +| lib/lib.js:670:5:670:10 | [post update] pushed [ArrayElement] | lib/lib.js:671:22:671:27 | pushed | provenance | | +| lib/lib.js:670:17:670:20 | name | lib/lib.js:670:5:670:10 | [post update] pushed [ArrayElement] | provenance | | +| lib/lib.js:670:17:670:20 | name | lib/lib.js:673:21:673:24 | name | provenance | | +| lib/lib.js:673:5:673:11 | [post update] shifted [ArrayElement] | lib/lib.js:674:25:674:31 | shifted | provenance | | +| lib/lib.js:673:21:673:24 | name | lib/lib.js:673:5:673:11 | [post update] shifted [ArrayElement] | provenance | | +| lib/lib.js:673:21:673:24 | name | lib/lib.js:676:23:676:26 | name | provenance | | +| lib/lib.js:676:23:676:26 | name | lib/lib.js:677:23:677:26 | name | provenance | | +| lib/lib.js:677:23:677:26 | name | lib/lib.js:678:23:678:26 | name | provenance | | +| lib/lib.js:678:23:678:26 | name | lib/lib.js:679:27:679:30 | name | provenance | | +| lib/lib.js:679:27:679:30 | name | lib/lib.js:680:27:680:30 | name | provenance | | +| lib/lib.js:680:27:680:30 | name | lib/lib.js:681:27:681:30 | name | provenance | | +| lib/lib.js:681:27:681:30 | name | lib/lib.js:682:26:682:29 | name | provenance | | +| lib/lib.js:682:26:682:29 | name | lib/lib.js:683:26:683:29 | name | provenance | | +| lib/lib.js:683:26:683:29 | name | lib/lib.js:684:26:684:29 | name | provenance | | +| lib/lib.js:684:26:684:29 | name | lib/lib.js:685:30:685:33 | name | provenance | | +| lib/lib.js:685:30:685:33 | name | lib/lib.js:686:30:686:33 | name | provenance | | +| lib/lib.js:686:30:686:33 | name | lib/lib.js:687:30:687:33 | name | provenance | | +| lib/lib.js:687:30:687:33 | name | lib/lib.js:693:20:693:23 | name | provenance | | +| lib/lib.js:693:20:693:23 | name | lib/lib.js:696:26:696:29 | name | provenance | | +| lib/lib.js:696:26:696:29 | name | lib/lib.js:700:23:700:26 | name | provenance | | +| lib/lib.js:700:23:700:26 | name | lib/lib.js:703:26:703:29 | name | provenance | | +| lib/lib.js:703:26:703:29 | name | lib/lib.js:706:23:706:26 | name | provenance | | +| lib/lib.js:706:23:706:26 | name | lib/lib.js:706:22:706:27 | [name] | provenance | | +| lib/lib.js:706:23:706:26 | name | lib/lib.js:709:26:709:29 | name | provenance | | +| lib/lib.js:709:26:709:29 | name | lib/lib.js:711:23:711:26 | name | provenance | | +| lib/lib.js:711:23:711:26 | name | lib/lib.js:716:23:716:26 | name | provenance | | +| lib/lib.js:716:23:716:26 | name | lib/lib.js:716:22:716:27 | [name] | provenance | | +| lib/lib.js:716:23:716:26 | name | lib/lib.js:722:23:722:26 | name | provenance | | +| lib/lib.js:722:23:722:26 | name | lib/lib.js:725:23:725:26 | name | provenance | | +| lib/lib.js:725:23:725:26 | name | lib/lib.js:725:22:725:27 | [name] | provenance | | +| lib/lib.js:725:23:725:26 | name | lib/lib.js:732:18:732:21 | name | provenance | | +| lib/lib.js:728:26:728:29 | args [0] | lib/lib.js:729:26:729:29 | args | provenance | | +| lib/lib.js:728:26:728:29 | args [0] | lib/lib.js:730:29:730:32 | args | provenance | | +| lib/lib.js:732:17:732:22 | [name] [0] | lib/lib.js:728:26:728:29 | args [0] | provenance | | +| lib/lib.js:732:18:732:21 | name | lib/lib.js:732:17:732:22 | [name] [0] | provenance | | +| lib/lib.js:732:18:732:21 | name | lib/lib.js:734:5:737:5 | functio ... t\\n } [name] | provenance | | +| lib/lib.js:732:18:732:21 | name | lib/lib.js:738:5:738:18 | runWithOptions [name] | provenance | | +| lib/lib.js:734:5:737:5 | functio ... t\\n } [name] | lib/lib.js:734:14:734:27 | runWithOptions [name] | provenance | | +| lib/lib.js:734:5:737:5 | functio ... t\\n } [name] | lib/lib.js:736:27:736:30 | name | provenance | | +| lib/lib.js:734:14:734:27 | runWithOptions [name] | lib/lib.js:738:5:738:18 | runWithOptions [name] | provenance | | +| lib/lib.js:736:27:736:30 | name | lib/lib.js:736:26:736:31 | [name] | provenance | | +| lib/lib.js:738:5:738:18 | runWithOptions [name] | lib/lib.js:736:27:736:30 | name | provenance | | | lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name | provenance | | | lib/subLib2/special-file.js:3:28:3:31 | name | lib/subLib2/special-file.js:4:22:4:25 | name | provenance | | | lib/subLib3/my-file.ts:3:28:3:31 | name | lib/subLib3/my-file.ts:4:22:4:25 | name | provenance | | @@ -371,14 +507,18 @@ nodes | lib/lib.js:406:22:406:25 | name | semmle.label | name | | lib/lib.js:414:40:414:43 | name | semmle.label | name | | lib/lib.js:415:22:415:25 | name | semmle.label | name | +| lib/lib.js:417:20:417:32 | ["-rf", name] | semmle.label | ["-rf", name] | | lib/lib.js:417:28:417:31 | name | semmle.label | name | +| lib/lib.js:418:17:418:29 | ["-rf", name] | semmle.label | ["-rf", name] | | lib/lib.js:418:25:418:28 | name | semmle.label | name | +| lib/lib.js:419:24:419:36 | ["-rf", name] | semmle.label | ["-rf", name] | | lib/lib.js:419:32:419:35 | name | semmle.label | name | +| lib/lib.js:420:21:420:33 | ["-rf", name] | semmle.label | ["-rf", name] | | lib/lib.js:420:29:420:32 | name | semmle.label | name | +| lib/lib.js:424:14:424:28 | ["first", name] | semmle.label | ["first", name] | | lib/lib.js:424:24:424:27 | name | semmle.label | name | | lib/lib.js:426:2:426:4 | [post update] arr [ArrayElement] | semmle.label | [post update] arr [ArrayElement] | | lib/lib.js:426:11:426:14 | name | semmle.label | name | -| lib/lib.js:426:11:426:14 | name | semmle.label | name | | lib/lib.js:427:14:427:16 | arr | semmle.label | arr | | lib/lib.js:428:14:428:58 | build(" ... + '-') | semmle.label | build(" ... + '-') | | lib/lib.js:428:28:428:57 | (name ? ... ) + '-' | semmle.label | (name ? ... ) + '-' | @@ -386,7 +526,6 @@ nodes | lib/lib.js:431:23:431:26 | last | semmle.label | last | | lib/lib.js:436:10:436:12 | [post update] arr [ArrayElement] | semmle.label | [post update] arr [ArrayElement] | | lib/lib.js:436:19:436:22 | last | semmle.label | last | -| lib/lib.js:436:19:436:22 | last | semmle.label | last | | lib/lib.js:437:9:437:11 | arr [ArrayElement] | semmle.label | arr [ArrayElement] | | lib/lib.js:441:39:441:42 | name | semmle.label | name | | lib/lib.js:442:24:442:27 | name | semmle.label | name | @@ -413,7 +552,6 @@ nodes | lib/lib.js:552:23:552:26 | args | semmle.label | args | | lib/lib.js:555:25:555:37 | ["-rf", name] [1] | semmle.label | ["-rf", name] [1] | | lib/lib.js:555:33:555:36 | name | semmle.label | name | -| lib/lib.js:555:33:555:36 | name | semmle.label | name | | lib/lib.js:558:41:558:44 | name | semmle.label | name | | lib/lib.js:560:26:560:29 | name | semmle.label | name | | lib/lib.js:562:26:562:29 | name | semmle.label | name | @@ -433,6 +571,81 @@ nodes | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | semmle.label | name.re ... '\\\\''") | | lib/lib.js:633:24:633:62 | name.re ... '\\\\''") | semmle.label | name.re ... '\\\\''") | | lib/lib.js:634:22:634:30 | sanitized | semmle.label | sanitized | +| lib/lib.js:643:48:643:51 | name | semmle.label | name | +| lib/lib.js:645:22:645:36 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:645:32:645:35 | name | semmle.label | name | +| lib/lib.js:646:22:646:36 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:646:32:646:35 | name | semmle.label | name | +| lib/lib.js:647:26:647:40 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:647:36:647:39 | name | semmle.label | name | +| lib/lib.js:648:26:648:40 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:648:36:648:39 | name | semmle.label | name | +| lib/lib.js:649:25:649:39 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:649:35:649:38 | name | semmle.label | name | +| lib/lib.js:650:25:650:39 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:650:35:650:38 | name | semmle.label | name | +| lib/lib.js:651:25:651:39 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:651:35:651:38 | name | semmle.label | name | +| lib/lib.js:652:25:652:39 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:652:35:652:38 | name | semmle.label | name | +| lib/lib.js:653:29:653:43 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:653:39:653:42 | name | semmle.label | name | +| lib/lib.js:654:29:654:43 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:654:39:654:42 | name | semmle.label | name | +| lib/lib.js:655:41:655:55 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:655:51:655:54 | name | semmle.label | name | +| lib/lib.js:656:41:656:55 | ["hello", name] | semmle.label | ["hello", name] | +| lib/lib.js:656:51:656:54 | name | semmle.label | name | +| lib/lib.js:660:11:660:14 | args [1] | semmle.label | args [1] | +| lib/lib.js:660:18:660:32 | ["hello", name] [1] | semmle.label | ["hello", name] [1] | +| lib/lib.js:660:28:660:31 | name | semmle.label | name | +| lib/lib.js:661:22:661:25 | args | semmle.label | args | +| lib/lib.js:662:25:662:28 | args | semmle.label | args | +| lib/lib.js:665:11:665:27 | arrayWithProperty [0] | semmle.label | arrayWithProperty [0] | +| lib/lib.js:665:31:665:36 | [name] [0] | semmle.label | [name] [0] | +| lib/lib.js:665:32:665:35 | name | semmle.label | name | +| lib/lib.js:667:22:667:38 | arrayWithProperty | semmle.label | arrayWithProperty | +| lib/lib.js:670:5:670:10 | [post update] pushed [ArrayElement] | semmle.label | [post update] pushed [ArrayElement] | +| lib/lib.js:670:17:670:20 | name | semmle.label | name | +| lib/lib.js:671:22:671:27 | pushed | semmle.label | pushed | +| lib/lib.js:673:5:673:11 | [post update] shifted [ArrayElement] | semmle.label | [post update] shifted [ArrayElement] | +| lib/lib.js:673:21:673:24 | name | semmle.label | name | +| lib/lib.js:674:25:674:31 | shifted | semmle.label | shifted | +| lib/lib.js:676:23:676:26 | name | semmle.label | name | +| lib/lib.js:677:23:677:26 | name | semmle.label | name | +| lib/lib.js:678:23:678:26 | name | semmle.label | name | +| lib/lib.js:679:27:679:30 | name | semmle.label | name | +| lib/lib.js:680:27:680:30 | name | semmle.label | name | +| lib/lib.js:681:27:681:30 | name | semmle.label | name | +| lib/lib.js:682:26:682:29 | name | semmle.label | name | +| lib/lib.js:683:26:683:29 | name | semmle.label | name | +| lib/lib.js:684:26:684:29 | name | semmle.label | name | +| lib/lib.js:685:30:685:33 | name | semmle.label | name | +| lib/lib.js:686:30:686:33 | name | semmle.label | name | +| lib/lib.js:687:30:687:33 | name | semmle.label | name | +| lib/lib.js:693:20:693:23 | name | semmle.label | name | +| lib/lib.js:696:26:696:29 | name | semmle.label | name | +| lib/lib.js:700:23:700:26 | name | semmle.label | name | +| lib/lib.js:703:26:703:29 | name | semmle.label | name | +| lib/lib.js:706:22:706:27 | [name] | semmle.label | [name] | +| lib/lib.js:706:23:706:26 | name | semmle.label | name | +| lib/lib.js:709:26:709:29 | name | semmle.label | name | +| lib/lib.js:711:23:711:26 | name | semmle.label | name | +| lib/lib.js:716:22:716:27 | [name] | semmle.label | [name] | +| lib/lib.js:716:23:716:26 | name | semmle.label | name | +| lib/lib.js:722:23:722:26 | name | semmle.label | name | +| lib/lib.js:725:22:725:27 | [name] | semmle.label | [name] | +| lib/lib.js:725:23:725:26 | name | semmle.label | name | +| lib/lib.js:728:26:728:29 | args [0] | semmle.label | args [0] | +| lib/lib.js:729:26:729:29 | args | semmle.label | args | +| lib/lib.js:730:29:730:32 | args | semmle.label | args | +| lib/lib.js:732:17:732:22 | [name] [0] | semmle.label | [name] [0] | +| lib/lib.js:732:18:732:21 | name | semmle.label | name | +| lib/lib.js:734:5:737:5 | functio ... t\\n } [name] | semmle.label | functio ... t\\n } [name] | +| lib/lib.js:734:14:734:27 | runWithOptions [name] | semmle.label | runWithOptions [name] | +| lib/lib.js:736:26:736:31 | [name] | semmle.label | [name] | +| lib/lib.js:736:27:736:30 | name | semmle.label | name | +| lib/lib.js:738:5:738:18 | runWithOptions [name] | semmle.label | runWithOptions [name] | | lib/subLib2/compiled-file.ts:3:26:3:29 | name | semmle.label | name | | lib/subLib2/compiled-file.ts:4:25:4:28 | name | semmle.label | name | | lib/subLib2/special-file.js:3:28:3:31 | name | semmle.label | name | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js index e7911746d6eb..86db944c2fe1 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js @@ -423,7 +423,7 @@ module.exports.shellOption = function (name) { // $ Source spawn("rm", ["first", name], SPAWN_OPT); // $ Alert var arr = []; - arr.push(name); // $ Alert + arr.push(name); spawn("rm", arr, SPAWN_OPT); // $ Alert spawn("rm", build("node", (name ? name + ':' : '') + '-'), SPAWN_OPT); // $ Alert } @@ -433,7 +433,7 @@ function build(first, last) { if (something() === 'gm') arr.push('convert'); first && arr.push(first); - last && arr.push(last); // $ Alert + last && arr.push(last); return arr; }; @@ -552,7 +552,7 @@ module.exports.shellThing = function (name) { // $ Source cp.spawn(cmd, args, spawnOpts); // $ Alert } - indirectShell("rm", ["-rf", name], {shell: true}); // $ Alert + indirectShell("rm", ["-rf", name], {shell: true}); } module.exports.badSanitizer = function (name) { // $ Source @@ -639,3 +639,110 @@ module.exports.sanitizer = function (name) { // $ Source var sanitized = "'" + name.replace(new RegExp("\'", unknownFlags()), "'\\''") + "'" cp.exec("rm -rf " + sanitized); // OK - Most likely should be okay and not flagged to reduce false positives. } + +module.exports.shellArgumentArrays = function (name) { // $ Source + + cp.spawn("echo", ["hello", name], { shell: true }); // $ Alert + cp.spawn("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.spawnSync("echo", ["hello", name], { shell: true }); // $ Alert + cp.spawnSync("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: true }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + cp.execFile("echo", ["hello", name], { shell: true }, () => {}); // $ Alert + cp.execFile("echo", ["hello", name], { shell: "/bin/sh" }, () => {}); // $ Alert + cp.execFileSync("echo", ["hello", name], { shell: true }); // $ Alert + cp.execFileSync("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + util.promisify(cp.execFile)("echo", ["hello", name], { shell: true }); // $ Alert + util.promisify(cp.execFile)("echo", ["hello", name], { shell: "/bin/sh" }); // $ Alert + + const shell = "/bin/sh"; + const options = { shell }; + const args = ["hello", name]; + cp.spawn("echo", args, options); // $ Alert + cp.execFile("echo", args, options); // $ Alert + + + const arrayWithProperty = [name]; + arrayWithProperty.shell = false; + cp.spawn("echo", arrayWithProperty, { shell: true }); // $ Alert + + const pushed = []; + pushed.push(name); + cp.spawn("echo", pushed, { shell: true }); // $ Alert + const shifted = []; + shifted.unshift(name); + cp.execFile("echo", shifted, { shell: "/bin/sh" }); // $ Alert + + cp.spawn("echo", [name]); + cp.spawn("echo", [name], { shell: false }); + cp.spawn("echo", [name], { shell: "" }); + cp.spawnSync("echo", [name]); + cp.spawnSync("echo", [name], { shell: false }); + cp.spawnSync("echo", [name], { shell: "" }); + cp.execFile("echo", [name]); + cp.execFile("echo", [name], { shell: false }); + cp.execFile("echo", [name], { shell: "" }); + cp.execFileSync("echo", [name]); + cp.execFileSync("echo", [name], { shell: false }); + cp.execFileSync("echo", [name], { shell: "" }); + cp.spawn("echo", ["hello"], { shell: true }); + cp.execFile("echo", ["hello"], { shell: "/bin/sh" }); + + const laterPush = []; + cp.spawn("echo", laterPush, { shell: true }); + laterPush.push(name); + const laterUnshift = []; + cp.execFile("echo", laterUnshift, { shell: "/bin/sh" }); + laterUnshift.unshift(name); + + const disabled = { shell: "/bin/sh" }; + disabled.shell = false; + cp.spawn("echo", [name], disabled); + const empty = { shell: true }; + empty.shell = ""; + cp.execFile("echo", [name], empty); + const enabled = { shell: false }; + enabled.shell = "/bin/sh"; + cp.spawn("echo", [name], enabled); // $ Alert + const alias = enabled; + alias.shell = false; + cp.execFile("echo", [name], enabled); + const changedAfterCall = { shell: false }; + cp.spawn("echo", [name], changedAfterCall); + changedAfterCall.shell = true; + + const conditional = { shell: "/bin/sh" }; + if (process.env.DISABLE_SHELL) conditional.shell = false; + cp.spawn("echo", [name], conditional); // $ Alert + + + const disabledOnBothBranches = { shell: true }; + if (process.env.DISABLE_SHELL) disabledOnBothBranches.shell = false; + else disabledOnBothBranches.shell = ""; + cp.spawn("echo", [name], disabledOnBothBranches); + + const disabledAfterCall = { shell: true }; + cp.spawn("echo", [name], disabledAfterCall); // $ Alert + disabledAfterCall.shell = false; + + function runWithArgs(args) { + cp.spawn("echo", args, { shell: true }); // $ Alert + cp.execFile("echo", args, { shell: "/bin/sh" }); // $ Alert + } + runWithArgs([name]); + + function runWithOptions(options) { + if (process.env.DISABLE_SHELL) options.shell = false; + cp.spawn("echo", [name], options); // $ Alert + } + runWithOptions({ shell: true }); +}; + +module.exports.shellOptionsOnly = function (options) { + options.shell = true; + cp.spawn("echo", options); + cp.spawnSync("echo", options); + cp.execFile("echo", options); + cp.execFile("echo", options, () => {}); + cp.execFileSync("echo", options); +}; diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/UselessUseOfCat.expected index 820d8af4767d..7fc324d595de 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/UselessUseOfCat.expected @@ -34,6 +34,12 @@ readFile | uselesscat.js:162:1:162:56 | execmod ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:163:1:163:42 | execmod ... utf8'}) | fs.readFile("foo/bar") | | uselesscat.js:164:1:164:76 | execmod ... (out)}) | fs.readFile("foo/bar", {encoding: 'utf8'}, (err, out) => {...}) | +| uselesscat.js:168:5:168:23 | exec("cat foo", cb) | fs.readFile("foo") | +| uselesscat.js:169:5:169:32 | execFil ... "], cb) | fs.readFile("foo") | +| uselesscat.js:170:5:170:45 | exec("c ... }, cb) | fs.readFile("foo", { encoding: "utf8" }) | +| uselesscat.js:171:5:171:54 | execFil ... }, cb) | fs.readFile("foo", { encoding: "utf8" }) | +| uselesscat.js:174:1:174:37 | exec("c ... tf8" }) | fs.readFile("foo", { encoding: "utf8" }) | +| uselesscat.js:175:1:175:46 | execFil ... tf8" }) | fs.readFile("foo", { encoding: "utf8" }) | syncCommand | uselesscat.js:16:1:16:29 | execSyn ... uinfo') | | uselesscat.js:18:1:18:26 | execSyn ... path}`) | @@ -84,3 +90,9 @@ options | uselesscat.js:156:1:156:35 | cspawn( ... tf8' }) | uselesscat.js:156:15:156:34 | { encoding: 'utf8' } | | uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | uselesscat.js:159:48:159:67 | { encoding: 'utf8' } | | uselesscat.js:164:1:164:76 | execmod ... (out)}) | uselesscat.js:164:24:164:41 | {encoding: 'utf8'} | +| uselesscat.js:170:5:170:45 | exec("c ... }, cb) | uselesscat.js:170:21:170:40 | { encoding: "utf8" } | +| uselesscat.js:171:5:171:54 | execFil ... }, cb) | uselesscat.js:171:30:171:49 | { encoding: "utf8" } | +| uselesscat.js:174:1:174:37 | exec("c ... tf8" }) | uselesscat.js:174:17:174:36 | { encoding: "utf8" } | +| uselesscat.js:175:1:175:46 | execFil ... tf8" }) | uselesscat.js:175:26:175:45 | { encoding: "utf8" } | +| uselesscat.js:176:1:176:32 | exec("c ... tmp" }) | uselesscat.js:176:17:176:31 | { cwd: "/tmp" } | +| uselesscat.js:177:1:177:41 | execFil ... tmp" }) | uselesscat.js:177:26:177:40 | { cwd: "/tmp" } | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/uselesscat.js index 6e62a1655420..a6e912ed65e6 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat/uselesscat.js @@ -163,4 +163,15 @@ execmod("cat foo/bar", (err, out) => {console.log(out)}); // $ Alert execmod("cat foo/bar", {encoding: 'utf8'}); // $ Alert execmod("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // $ Alert - \ No newline at end of file + +function unresolvedCallback(cb) { + exec("cat foo", cb); // $ Alert + execFile("cat", ["foo"], cb); // $ Alert + exec("cat foo", { encoding: "utf8" }, cb); // $ Alert + execFile("cat", ["foo"], { encoding: "utf8" }, cb); // $ Alert +} + +exec("cat foo", { encoding: "utf8" }); // $ Alert +execFile("cat", ["foo"], { encoding: "utf8" }); // $ Alert +exec("cat foo", { cwd: "/tmp" }); +execFile("cat", ["foo"], { cwd: "/tmp" });