diff --git a/javascript/ql/lib/change-notes/2025-09-15-promisifications.md b/javascript/ql/lib/change-notes/2025-09-15-promisifications.md new file mode 100644 index 000000000000..8864ab23a022 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-09-15-promisifications.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Added modeling for promisification libraries `@gar/promisify`, `es6-promisify`, `util.promisify`, `thenify-all`, `call-me-maybe`, `@google-cloud/promisify`, and `util-promisify`. +* Data flow is now tracked through promisified user-defined functions. diff --git a/javascript/ql/lib/ext/call-me-maybe.model.yml b/javascript/ql/lib/ext/call-me-maybe.model.yml new file mode 100644 index 000000000000..ceda3e8b0922 --- /dev/null +++ b/javascript/ql/lib/ext/call-me-maybe.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: summaryModel + data: + - ["call-me-maybe", "", "Argument[1]", "ReturnValue", "value"] diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 3bb04f2686be..833f309021ba 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -1115,6 +1115,17 @@ module API { ref = awaited(call) ) or + // Handle promisified object member access: promisify(obj).member should be treated as obj.member (promisified) + exists( + Promisify::PromisifyAllCall promisifiedObj, DataFlow::SourceNode originalObj, + string member + | + originalObj.flowsTo(promisifiedObj.getArgument(0)) and + use(base, originalObj) and + lbl = Label::member(member) and + ref = promisifiedObj.getAPropertyRead(member) + ) + or decoratorDualEdge(base, lbl, ref) or decoratorUseEdge(base, lbl, ref) diff --git a/javascript/ql/lib/semmle/javascript/Promises.qll b/javascript/ql/lib/semmle/javascript/Promises.qll index 51411463efc6..2feb92e2e553 100644 --- a/javascript/ql/lib/semmle/javascript/Promises.qll +++ b/javascript/ql/lib/semmle/javascript/Promises.qll @@ -727,8 +727,12 @@ module Promisify { PromisifyAllCall() { this = [ - DataFlow::moduleMember("bluebird", "promisifyAll"), - DataFlow::moduleImport(["util-promisifyall", "pify"]) + DataFlow::moduleMember(["bluebird", "@google-cloud/promisify", "es6-promisify"], + "promisifyAll"), + DataFlow::moduleMember("thenify-all", "withCallback"), + DataFlow::moduleImport([ + "util-promisifyall", "pify", "thenify-all", "@gar/promisify", "util.promisify-all" + ]) ].getACall() } } @@ -741,11 +745,13 @@ module Promisify { PromisifyCall() { this = DataFlow::moduleImport(["util", "bluebird"]).getAMemberCall("promisify") or - this = DataFlow::moduleImport(["pify", "util.promisify"]).getACall() + this = DataFlow::moduleImport(["pify", "util.promisify", "util-promisify"]).getACall() or - this = DataFlow::moduleImport("thenify").getACall() + this = DataFlow::moduleImport(["thenify", "@gar/promisify", "es6-promisify"]).getACall() or this = DataFlow::moduleMember("thenify", "withCallback").getACall() + or + this = DataFlow::moduleMember("@google-cloud/promisify", "promisify").getACall() } } } diff --git a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll index edc591ceff46..071d6d084333 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll @@ -1912,6 +1912,7 @@ module DataFlow { deprecated import Configuration import TypeTracking import AdditionalFlowSteps + import PromisifyFlow import internal.FunctionWrapperSteps import internal.sharedlib.DataFlow import internal.BarrierGuards diff --git a/javascript/ql/lib/semmle/javascript/dataflow/PromisifyFlow.qll b/javascript/ql/lib/semmle/javascript/dataflow/PromisifyFlow.qll new file mode 100644 index 000000000000..4389a3d031a6 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/dataflow/PromisifyFlow.qll @@ -0,0 +1,29 @@ +/** + * Provides data flow steps for promisified user-defined function calls. + * This ensures that when you call a promisified user-defined function, + * arguments flow to the original function's parameters. + */ + +private import javascript +private import semmle.javascript.dataflow.AdditionalFlowSteps + +/** + * A data flow step from arguments of promisified user-defined function calls to + * the parameters of the original function. + */ +class PromisifiedUserFunctionArgumentFlow extends AdditionalFlowStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists( + DataFlow::CallNode promisifiedCall, Promisify::PromisifyCall promisify, + DataFlow::FunctionNode originalFunc, int i + | + // The promisified call flows from a promisify result + promisify.flowsTo(promisifiedCall.getCalleeNode()) and + // The original function was promisified + originalFunc.flowsTo(promisify.getArgument(0)) and + // Argument i of the promisified call flows to parameter i of the original function + pred = promisifiedCall.getArgument(i) and + succ = originalFunc.getParameter(i) + ) + } +} 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 dc9c65822ba0..f1d547bdfb1c 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 @@ -82,6 +82,33 @@ | other.js:28:27:28:29 | cmd | other.js:5:25:5:31 | req.url | other.js:28:27:28:29 | cmd | This command line depends on a $@. | other.js:5:25:5:31 | req.url | user-provided value | | other.js:30:33:30:35 | cmd | other.js:5:25:5:31 | req.url | other.js:30:33:30:35 | cmd | This command line depends on a $@. | other.js:5:25:5:31 | req.url | user-provided value | | other.js:34:44:34:46 | cmd | other.js:5:25:5:31 | req.url | other.js:34:44:34:46 | cmd | This command line depends on a $@. | other.js:5:25:5:31 | req.url | user-provided value | +| promisification.js:9:13:9:21 | code.code | promisification.js:15:18:15:25 | req.body | promisification.js:9:13:9:21 | code.code | This command line depends on a $@. | promisification.js:15:18:15:25 | req.body | user-provided value | +| promisification.js:24:22:24:25 | code | promisification.js:21:18:21:25 | req.body | promisification.js:24:22:24:25 | code | This command line depends on a $@. | promisification.js:21:18:21:25 | req.body | user-provided value | +| promisification.js:31:24:31:27 | code | promisification.js:30:18:30:25 | req.body | promisification.js:31:24:31:27 | code | This command line depends on a $@. | promisification.js:30:18:30:25 | req.body | user-provided value | +| promisification.js:40:21:40:24 | code | promisification.js:37:18:37:25 | req.body | promisification.js:40:21:40:24 | code | This command line depends on a $@. | promisification.js:37:18:37:25 | req.body | user-provided value | +| promisification.js:43:24:43:27 | code | promisification.js:37:18:37:25 | req.body | promisification.js:43:24:43:27 | code | This command line depends on a $@. | promisification.js:37:18:37:25 | req.body | user-provided value | +| promisification.js:52:21:52:24 | code | promisification.js:49:18:49:25 | req.body | promisification.js:52:21:52:24 | code | This command line depends on a $@. | promisification.js:49:18:49:25 | req.body | user-provided value | +| promisification.js:55:15:55:18 | code | promisification.js:49:18:49:25 | req.body | promisification.js:55:15:55:18 | code | This command line depends on a $@. | promisification.js:49:18:49:25 | req.body | user-provided value | +| promisification.js:65:21:65:23 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:65:21:65:23 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:69:20:69:22 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:69:20:69:22 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:74:26:74:28 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:74:26:74:28 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:77:24:77:26 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:77:24:77:26 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:78:28:78:30 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:78:28:78:30 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:79:25:79:27 | cmd | promisification.js:61:15:61:22 | req.body | promisification.js:79:25:79:27 | cmd | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:83:36:83:39 | code | promisification.js:61:15:61:22 | req.body | promisification.js:83:36:83:39 | code | This command line depends on a $@. | promisification.js:61:15:61:22 | req.body | user-provided value | +| promisification.js:100:23:100:26 | code | promisification.js:99:18:99:25 | req.body | promisification.js:100:23:100:26 | code | This command line depends on a $@. | promisification.js:99:18:99:25 | req.body | user-provided value | +| promisification.js:101:27:101:30 | code | promisification.js:99:18:99:25 | req.body | promisification.js:101:27:101:30 | code | This command line depends on a $@. | promisification.js:99:18:99:25 | req.body | user-provided value | +| promisification.js:102:27:102:30 | code | promisification.js:99:18:99:25 | req.body | promisification.js:102:27:102:30 | code | This command line depends on a $@. | promisification.js:99:18:99:25 | req.body | user-provided value | +| promisification.js:106:24:106:27 | code | promisification.js:99:18:99:25 | req.body | promisification.js:106:24:106:27 | code | This command line depends on a $@. | promisification.js:99:18:99:25 | req.body | user-provided value | +| promisification.js:109:24:109:27 | code | promisification.js:99:18:99:25 | req.body | promisification.js:109:24:109:27 | code | This command line depends on a $@. | promisification.js:99:18:99:25 | req.body | user-provided value | +| promisification.js:124:17:124:19 | cmd | promisification.js:114:18:114:25 | req.body | promisification.js:124:17:124:19 | cmd | This command line depends on a $@. | promisification.js:114:18:114:25 | req.body | user-provided value | +| promisification.js:133:21:133:24 | code | promisification.js:130:18:130:25 | req.body | promisification.js:133:21:133:24 | code | This command line depends on a $@. | promisification.js:130:18:130:25 | req.body | user-provided value | +| promisification.js:136:15:136:18 | code | promisification.js:130:18:130:25 | req.body | promisification.js:136:15:136:18 | code | This command line depends on a $@. | promisification.js:130:18:130:25 | req.body | user-provided value | +| promisification.js:144:21:144:24 | code | promisification.js:141:18:141:25 | req.body | promisification.js:144:21:144:24 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | +| promisification.js:147:15:147:18 | code | promisification.js:141:18:141:25 | req.body | promisification.js:147:15:147:18 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | +| promisification.js:150:24:150:27 | code | promisification.js:141:18:141:25 | req.body | promisification.js:150:24:150:27 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | +| promisification.js:151:28:151:31 | code | promisification.js:141:18:141:25 | req.body | promisification.js:151:28:151:31 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | +| promisification.js:152:25:152:28 | code | promisification.js:141:18:141:25 | req.body | promisification.js:152:25:152:28 | code | This command line depends on a $@. | promisification.js:141:18:141:25 | req.body | user-provided value | | third-party-command-injection.js:6:21:6:27 | command | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | This command line depends on a $@. | third-party-command-injection.js:5:20:5:26 | command | user-provided value | edges | actions.js:8:9:8:13 | title | actions.js:9:16:9:20 | title | provenance | | @@ -259,6 +286,59 @@ edges | other.js:5:9:5:11 | cmd | other.js:34:44:34:46 | cmd | provenance | | | other.js:5:15:5:38 | url.par ... , true) | other.js:5:9:5:11 | cmd | provenance | | | other.js:5:25:5:31 | req.url | other.js:5:15:5:38 | url.par ... , true) | provenance | | +| promisification.js:8:21:8:24 | code | promisification.js:9:13:9:16 | code | provenance | | +| promisification.js:9:13:9:16 | code | promisification.js:9:13:9:21 | code.code | provenance | | +| promisification.js:15:11:15:14 | code | promisification.js:16:15:16:18 | code | provenance | | +| promisification.js:15:18:15:25 | req.body | promisification.js:15:11:15:14 | code | provenance | | +| promisification.js:16:15:16:18 | code | promisification.js:8:21:8:24 | code | provenance | | +| promisification.js:21:11:21:14 | code | promisification.js:24:22:24:25 | code | provenance | | +| promisification.js:21:18:21:25 | req.body | promisification.js:21:11:21:14 | code | provenance | | +| promisification.js:30:11:30:14 | code | promisification.js:31:24:31:27 | code | provenance | | +| promisification.js:30:18:30:25 | req.body | promisification.js:30:11:30:14 | code | provenance | | +| promisification.js:37:11:37:14 | code | promisification.js:40:21:40:24 | code | provenance | | +| promisification.js:37:11:37:14 | code | promisification.js:43:24:43:27 | code | provenance | | +| promisification.js:37:18:37:25 | req.body | promisification.js:37:11:37:14 | code | provenance | | +| promisification.js:49:11:49:14 | code | promisification.js:52:21:52:24 | code | provenance | | +| promisification.js:49:11:49:14 | code | promisification.js:55:15:55:18 | code | provenance | | +| promisification.js:49:18:49:25 | req.body | promisification.js:49:11:49:14 | code | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:65:21:65:23 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:69:20:69:22 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:74:26:74:28 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:77:24:77:26 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:78:28:78:30 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:79:25:79:27 | cmd | provenance | | +| promisification.js:61:9:61:11 | cmd | promisification.js:89:12:89:14 | cmd | provenance | | +| promisification.js:61:15:61:22 | req.body | promisification.js:61:9:61:11 | cmd | provenance | | +| promisification.js:81:34:81:37 | code | promisification.js:83:36:83:39 | code | provenance | | +| promisification.js:89:12:89:14 | cmd | promisification.js:81:34:81:37 | code | provenance | | +| promisification.js:99:11:99:14 | code | promisification.js:100:23:100:26 | code | provenance | | +| promisification.js:99:11:99:14 | code | promisification.js:101:27:101:30 | code | provenance | | +| promisification.js:99:11:99:14 | code | promisification.js:102:27:102:30 | code | provenance | | +| promisification.js:99:11:99:14 | code | promisification.js:106:24:106:27 | code | provenance | | +| promisification.js:99:11:99:14 | code | promisification.js:109:24:109:27 | code | provenance | | +| promisification.js:99:18:99:25 | req.body | promisification.js:99:11:99:14 | code | provenance | | +| promisification.js:114:11:114:14 | code | promisification.js:122:42:122:45 | code | provenance | | +| promisification.js:114:18:114:25 | req.body | promisification.js:114:11:114:14 | code | provenance | | +| promisification.js:116:32:116:34 | cmd | promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | provenance | | +| promisification.js:116:32:116:34 | cmd | promisification.js:118:21:118:23 | cmd | provenance | | +| promisification.js:118:13:118:19 | [post update] resolve [resolve-value] | promisification.js:117:29:117:35 | resolve [Return] [resolve-value] | provenance | | +| promisification.js:118:21:118:23 | cmd | promisification.js:118:13:118:19 | [post update] resolve [resolve-value] | provenance | | +| promisification.js:122:11:122:20 | cmdPromise [PromiseValue] | promisification.js:123:17:123:26 | cmdPromise [PromiseValue] | provenance | | +| promisification.js:122:24:122:46 | createE ... e(code) [PromiseValue] | promisification.js:122:11:122:20 | cmdPromise [PromiseValue] | provenance | | +| promisification.js:122:42:122:45 | code | promisification.js:116:32:116:34 | cmd | provenance | | +| promisification.js:122:42:122:45 | code | promisification.js:122:24:122:46 | createE ... e(code) [PromiseValue] | provenance | | +| promisification.js:123:5:123:27 | maybe(n ... romise) [PromiseValue] | promisification.js:123:34:123:36 | cmd | provenance | | +| promisification.js:123:17:123:26 | cmdPromise [PromiseValue] | promisification.js:123:5:123:27 | maybe(n ... romise) [PromiseValue] | provenance | | +| promisification.js:123:34:123:36 | cmd | promisification.js:124:17:124:19 | cmd | provenance | | +| promisification.js:130:11:130:14 | code | promisification.js:133:21:133:24 | code | provenance | | +| promisification.js:130:11:130:14 | code | promisification.js:136:15:136:18 | code | provenance | | +| promisification.js:130:18:130:25 | req.body | promisification.js:130:11:130:14 | code | provenance | | +| promisification.js:141:11:141:14 | code | promisification.js:144:21:144:24 | code | provenance | | +| promisification.js:141:11:141:14 | code | promisification.js:147:15:147:18 | code | provenance | | +| promisification.js:141:11:141:14 | code | promisification.js:150:24:150:27 | code | provenance | | +| promisification.js:141:11:141:14 | code | promisification.js:151:28:151:31 | code | provenance | | +| promisification.js:141:11:141:14 | code | promisification.js:152:25:152:28 | code | provenance | | +| promisification.js:141:18:141:25 | req.body | promisification.js:141:11:141:14 | code | provenance | | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | provenance | | nodes | actions.js:8:9:8:13 | title | semmle.label | title | @@ -446,6 +526,71 @@ nodes | other.js:28:27:28:29 | cmd | semmle.label | cmd | | other.js:30:33:30:35 | cmd | semmle.label | cmd | | other.js:34:44:34:46 | cmd | semmle.label | cmd | +| promisification.js:8:21:8:24 | code | semmle.label | code | +| promisification.js:9:13:9:16 | code | semmle.label | code | +| promisification.js:9:13:9:21 | code.code | semmle.label | code.code | +| promisification.js:15:11:15:14 | code | semmle.label | code | +| promisification.js:15:18:15:25 | req.body | semmle.label | req.body | +| promisification.js:16:15:16:18 | code | semmle.label | code | +| promisification.js:21:11:21:14 | code | semmle.label | code | +| promisification.js:21:18:21:25 | req.body | semmle.label | req.body | +| promisification.js:24:22:24:25 | code | semmle.label | code | +| promisification.js:30:11:30:14 | code | semmle.label | code | +| promisification.js:30:18:30:25 | req.body | semmle.label | req.body | +| promisification.js:31:24:31:27 | code | semmle.label | code | +| promisification.js:37:11:37:14 | code | semmle.label | code | +| promisification.js:37:18:37:25 | req.body | semmle.label | req.body | +| promisification.js:40:21:40:24 | code | semmle.label | code | +| promisification.js:43:24:43:27 | code | semmle.label | code | +| promisification.js:49:11:49:14 | code | semmle.label | code | +| promisification.js:49:18:49:25 | req.body | semmle.label | req.body | +| promisification.js:52:21:52:24 | code | semmle.label | code | +| promisification.js:55:15:55:18 | code | semmle.label | code | +| promisification.js:61:9:61:11 | cmd | semmle.label | cmd | +| promisification.js:61:15:61:22 | req.body | semmle.label | req.body | +| promisification.js:65:21:65:23 | cmd | semmle.label | cmd | +| promisification.js:69:20:69:22 | cmd | semmle.label | cmd | +| promisification.js:74:26:74:28 | cmd | semmle.label | cmd | +| promisification.js:77:24:77:26 | cmd | semmle.label | cmd | +| promisification.js:78:28:78:30 | cmd | semmle.label | cmd | +| promisification.js:79:25:79:27 | cmd | semmle.label | cmd | +| promisification.js:81:34:81:37 | code | semmle.label | code | +| promisification.js:83:36:83:39 | code | semmle.label | code | +| promisification.js:89:12:89:14 | cmd | semmle.label | cmd | +| promisification.js:99:11:99:14 | code | semmle.label | code | +| promisification.js:99:18:99:25 | req.body | semmle.label | req.body | +| promisification.js:100:23:100:26 | code | semmle.label | code | +| promisification.js:101:27:101:30 | code | semmle.label | code | +| promisification.js:102:27:102:30 | code | semmle.label | code | +| promisification.js:106:24:106:27 | code | semmle.label | code | +| promisification.js:109:24:109:27 | code | semmle.label | code | +| promisification.js:114:11:114:14 | code | semmle.label | code | +| promisification.js:114:18:114:25 | req.body | semmle.label | req.body | +| promisification.js:116:32:116:34 | cmd | semmle.label | cmd | +| promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | semmle.label | new Pro ... }) [PromiseValue] | +| promisification.js:117:29:117:35 | resolve [Return] [resolve-value] | semmle.label | resolve [Return] [resolve-value] | +| promisification.js:118:13:118:19 | [post update] resolve [resolve-value] | semmle.label | [post update] resolve [resolve-value] | +| promisification.js:118:21:118:23 | cmd | semmle.label | cmd | +| promisification.js:122:11:122:20 | cmdPromise [PromiseValue] | semmle.label | cmdPromise [PromiseValue] | +| promisification.js:122:24:122:46 | createE ... e(code) [PromiseValue] | semmle.label | createE ... e(code) [PromiseValue] | +| promisification.js:122:42:122:45 | code | semmle.label | code | +| promisification.js:123:5:123:27 | maybe(n ... romise) [PromiseValue] | semmle.label | maybe(n ... romise) [PromiseValue] | +| promisification.js:123:17:123:26 | cmdPromise [PromiseValue] | semmle.label | cmdPromise [PromiseValue] | +| promisification.js:123:34:123:36 | cmd | semmle.label | cmd | +| promisification.js:124:17:124:19 | cmd | semmle.label | cmd | +| promisification.js:130:11:130:14 | code | semmle.label | code | +| promisification.js:130:18:130:25 | req.body | semmle.label | req.body | +| promisification.js:133:21:133:24 | code | semmle.label | code | +| promisification.js:136:15:136:18 | code | semmle.label | code | +| promisification.js:141:11:141:14 | code | semmle.label | code | +| promisification.js:141:18:141:25 | req.body | semmle.label | req.body | +| promisification.js:144:21:144:24 | code | semmle.label | code | +| promisification.js:147:15:147:18 | code | semmle.label | code | +| promisification.js:150:24:150:27 | code | semmle.label | code | +| promisification.js:151:28:151:31 | code | semmle.label | code | +| promisification.js:152:25:152:28 | code | semmle.label | code | | third-party-command-injection.js:5:20:5:26 | command | semmle.label | command | | third-party-command-injection.js:6:21:6:27 | command | semmle.label | command | subpaths +| promisification.js:116:32:116:34 | cmd | promisification.js:118:21:118:23 | cmd | promisification.js:117:29:117:35 | resolve [Return] [resolve-value] | promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | +| promisification.js:122:42:122:45 | code | promisification.js:116:32:116:34 | cmd | promisification.js:117:16:119:10 | new Pro ... }) [PromiseValue] | promisification.js:122:24:122:46 | createE ... e(code) [PromiseValue] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/promisification.js b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/promisification.js new file mode 100644 index 000000000000..b062c8b7ee2b --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection/promisification.js @@ -0,0 +1,153 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const cp = require('child_process'); + +const app = express(); +app.use(bodyParser.json()); + +function legacyEval(code) { + cp.exec(code.code); // $ Alert +} + +app.post('/eval', async (req, res) => { + const { promisify } = require('util'); + const evalAsync = promisify(legacyEval); + const code = req.body; // $ Source + evalAsync(code); +}); + +app.post('/eval', async (req, res) => { + const directPromisify = require('util.promisify'); + const code = req.body; // $ Source + + const promisifiedExec3 = directPromisify(cp.exec); + promisifiedExec3(code); // $ Alert +}); + +app.post('/eval', async (req, res) => { + const promisify2 = require('util.promisify-all'); + const promisifiedCp = promisify2(cp); + const code = req.body; // $ Source + promisifiedCp.exec(code); // $ Alert +}); + + +app.post('/eval', async (req, res) => { + var garPromisify = require("@gar/promisify"); + const code = req.body; // $ Source + + const promisifiedExec = garPromisify(cp.exec); + promisifiedExec(code); // $ Alert + + const promisifiedCp = garPromisify(cp); + promisifiedCp.exec(code); // $ Alert +}); + +app.post('/eval', async (req, res) => { + require('util.promisify/shim')(); + const util = require('util'); + const code = req.body; // $ Source + + const promisifiedExec = util.promisify(cp.exec); + promisifiedExec(code); // $ Alert + + const execAsync = util.promisify(cp.exec.bind(cp)); + execAsync(code); // $ Alert +}); + + +app.post('/eval', async (req, res) => { + const es6Promisify = require("es6-promisify"); + let cmd = req.body; // $ Source + + // Test basic promisification + const promisifiedExec = es6Promisify(cp.exec); + promisifiedExec(cmd); // $ Alert + + // Test with method binding + const execBoundAsync = es6Promisify(cp.exec.bind(cp)); + execBoundAsync(cmd); // $ Alert + + const promisifiedExecMulti = es6Promisify(cp.exec, { + multiArgs: true + }); + promisifiedExecMulti(cmd); // $ Alert + + const promisifiedCp = es6Promisify.promisifyAll(cp); + promisifiedCp.exec(cmd); // $ Alert + promisifiedCp.execFile(cmd); // $ Alert + promisifiedCp.spawn(cmd); // $ Alert + + const lambda = es6Promisify((code, callback) => { + try { + const result = cp.exec(code); // $ Alert + callback(null, result); + } catch (err) { + callback(err); + } + }); + lambda(cmd); +}); + + +app.post('/eval', async (req, res) => { + var thenifyAll = require('thenify-all'); + var cpThenifyAll = thenifyAll(require('child_process'), {}, [ + 'exec', + 'execSync', + ]); + const code = req.body; // $ Source + cpThenifyAll.exec(code); // $ Alert + cpThenifyAll.execSync(code); // $ Alert + cpThenifyAll.execFile(code); // $ SPURIOUS: Alert - not promisified, as it is not listed in `thenifyAll`, but it should fine to flag it + + + var cpThenifyAll1 = thenifyAll.withCallback(require('child_process'), {}, ['exec']); + cpThenifyAll1.exec(code, function (err, string) {}); // $ Alert + + var cpThenifyAll2 = thenifyAll(require('child_process')); + cpThenifyAll2.exec(code); // $ Alert +}); + +app.post('/eval', async (req, res) => { + const maybe = require('call-me-maybe'); + const code = req.body; // $ Source + + function createExecPromise(cmd) { + return new Promise((resolve) => { + resolve(cmd); + }); + } + + const cmdPromise = createExecPromise(code); + maybe(null, cmdPromise).then(cmd => { + cp.exec(cmd); // $ Alert + }); +}); + +app.post('/eval', async (req, res) => { + const utilPromisify = require('util-promisify'); + const code = req.body; // $ Source + + const promisifiedExec = utilPromisify(cp.exec); + promisifiedExec(code); // $ Alert + + const execAsync = utilPromisify(cp.exec.bind(cp)); + execAsync(code); // $ Alert +}); + +app.post('/eval', async (req, res) => { + const {promisify, promisifyAll} = require('@google-cloud/promisify'); + const code = req.body; // $ Source + + const promisifiedExec = promisify(cp.exec); + promisifiedExec(code); // $ Alert + + const execAsync = promisify(cp.exec.bind(cp)); + execAsync(code); // $ Alert + + const promisifiedCp = promisifyAll(cp); + promisifiedCp.exec(code); // $ Alert + promisifiedCp.execFile(code); // $ Alert + promisifiedCp.spawn(code); // $ Alert +});