diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll index 0873472a0e7..56e9c6f4406 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NoSQL.qll @@ -11,6 +11,10 @@ module NoSql { /** Gets an expression that is interpreted as a code operator in this query. */ DataFlow::Node getACodeOperator() { none() } } + + private class QueryFromModel extends Query { + QueryFromModel() { this = ModelOutput::getASinkNode("nosql-injection").asSink() } + } } /** DEPRECATED: Alias for NoSql */ diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll index 4c6ad8b0ee9..8cb00e7dc68 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll @@ -235,4 +235,8 @@ module ClientSideUrlRedirect { this = NextJS::nextRouter().getAMemberCall(["push", "replace"]).getArgument(0) } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll index 0aa0241c780..1976ea12ed2 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll @@ -410,4 +410,8 @@ module CodeInjection { /** DEPRECATED: Alias for JsonStringifySanitizer */ deprecated class JSONStringifySanitizer = JsonStringifySanitizer; + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll index f0f6b17dce0..982441a88ff 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll @@ -50,4 +50,8 @@ module CommandInjection { class SystemCommandExecutionSink extends Sink, DataFlow::ValueNode { SystemCommandExecutionSink() { this = any(SystemCommandExecution sys).getACommandArgument() } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("command-line-injection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index 18230c92037..7b45641aefb 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -342,4 +342,8 @@ module DomBasedXss { outcome = super.getPolarity() } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll index 8343da9f5a5..a91f21f7c23 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ReflectedXssCustomizations.qll @@ -150,4 +150,8 @@ module ReflectedXss { this.(Http::RequestHeaderAccess).getAHeaderName() = "referer" } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll index 519ecd1c23f..29b015e09d6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll @@ -73,4 +73,12 @@ module RequestForgery { pred = url.getArgument(0) ) } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("request-forgery").asSink() } + + override DataFlow::Node getARequest() { result = this } + + override string getKind() { result = "endpoint" } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll index c15dbc2ecf8..55815717e98 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ServerSideUrlRedirectCustomizations.qll @@ -62,4 +62,8 @@ module ServerSideUrlRedirect { ) } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index c463b1d3ecb..67331ddb841 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -946,4 +946,8 @@ module TaintedPath { ) ) } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("path-injection").asSink() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll index 995e7e14411..01d93740a31 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll @@ -49,4 +49,8 @@ module UnsafeDeserialization { ) } } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() } + } } diff --git a/javascript/ql/test/library-tests/DataExtensions/Test.expected b/javascript/ql/test/library-tests/DataExtensions/Test.expected new file mode 100644 index 00000000000..ba7992e3143 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/Test.expected @@ -0,0 +1,7 @@ +commandInjectionSinks +| execa.example.js:2:7:2:9 | cmd | +sqlInjectionSinks +| connection.example.ts:4:20:4:20 | q | +| connection.example.ts:9:18:9:18 | q | +remoteFlowSources +| message.example.js:1:46:1:50 | event | diff --git a/javascript/ql/test/library-tests/DataExtensions/Test.ql b/javascript/ql/test/library-tests/DataExtensions/Test.ql new file mode 100644 index 00000000000..38eda6b4f78 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/Test.ql @@ -0,0 +1,11 @@ +import javascript +private import semmle.javascript.security.dataflow.CommandInjectionCustomizations +private import semmle.javascript.security.dataflow.SqlInjectionCustomizations + +query predicate commandInjectionSinks(DataFlow::Node node) { + node instanceof CommandInjection::Sink +} + +query predicate sqlInjectionSinks(DataFlow::Node node) { node instanceof SqlInjection::Sink } + +query predicate remoteFlowSources(RemoteFlowSource node) { any() } diff --git a/javascript/ql/test/library-tests/DataExtensions/connection.example.ts b/javascript/ql/test/library-tests/DataExtensions/connection.example.ts new file mode 100644 index 00000000000..ddcacd47ecc --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/connection.example.ts @@ -0,0 +1,9 @@ +import { Connection } from "@example/mysql"; + +function submit(connection: Connection, q: string) { + connection.query(q); // <-- add 'q' as a SQL injection sink +} + +import { getConnection } from "@example/db"; +let connection = getConnection(); +connection.query(q); // <-- add 'q' as a SQL injection sink diff --git a/javascript/ql/test/library-tests/DataExtensions/connection.model.yml b/javascript/ql/test/library-tests/DataExtensions/connection.model.yml new file mode 100644 index 00000000000..353decf08be --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/connection.model.yml @@ -0,0 +1,20 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - [ + "@example/mysql.Connection", + "Member[query].Argument[0]", + "sql-injection", + ] + + - addsTo: + pack: codeql/javascript-all + extensible: typeModel + data: + - [ + "@example/mysql.Connection", + "@example/db", + "Member[getConnection].ReturnValue", + ] diff --git a/javascript/ql/test/library-tests/DataExtensions/execa.example.js b/javascript/ql/test/library-tests/DataExtensions/execa.example.js new file mode 100644 index 00000000000..6804c1d3783 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/execa.example.js @@ -0,0 +1,2 @@ +import { shell } from "@example/execa"; +shell(cmd); diff --git a/javascript/ql/test/library-tests/DataExtensions/execa.model.yml b/javascript/ql/test/library-tests/DataExtensions/execa.model.yml new file mode 100644 index 00000000000..2516e1a7be8 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/execa.model.yml @@ -0,0 +1,10 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - [ + "@example/execa", + "Member[shell].Argument[0]", + "command-line-injection", + ] diff --git a/javascript/ql/test/library-tests/DataExtensions/message.example.js b/javascript/ql/test/library-tests/DataExtensions/message.example.js new file mode 100644 index 00000000000..dec004c9982 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/message.example.js @@ -0,0 +1,7 @@ +window.addEventListener("message", function (event) { + let data = event.data; // <-- add 'event.data' as a taint source +}); + +window.addEventListener("onclick", function (event) { + let data = event.data; // <-- 'event.data' should not be a taint source +}); diff --git a/javascript/ql/test/library-tests/DataExtensions/message.model.yml b/javascript/ql/test/library-tests/DataExtensions/message.model.yml new file mode 100644 index 00000000000..9c575566ce4 --- /dev/null +++ b/javascript/ql/test/library-tests/DataExtensions/message.model.yml @@ -0,0 +1,10 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - [ + "global", + "Member[addEventListener].WithStringArgument[0=message].Argument[1].Parameter[0].Member[data]", + "remote-flow", + ] diff --git a/javascript/ql/test/qlpack.yml b/javascript/ql/test/qlpack.yml index bb4fd89f43e..e5cdedb3b0e 100644 --- a/javascript/ql/test/qlpack.yml +++ b/javascript/ql/test/qlpack.yml @@ -5,3 +5,5 @@ dependencies: codeql/javascript-queries: ${workspace} extractor: javascript tests: . +dataExtensions: + - library-tests/DataExtensions/*.model.yml