diff --git a/javascript/ql/src/experimental/poi/PoI.qll b/javascript/ql/src/experimental/poi/PoI.qll new file mode 100644 index 00000000000..20eeeee9f79 --- /dev/null +++ b/javascript/ql/src/experimental/poi/PoI.qll @@ -0,0 +1,325 @@ +/** + * Provides classes and predicates for discovering points of interest + * in an unknown code base. + * + * To use this module, subclass the + * `ActivePoI` class, override *one* of its `is` predicates, and use + * `alertQuery` as a `@kind problem` query . This will present + * the desired points of interest as alerts that are easily browsable + * in a codeql IDE. By itself, this is no different from an ordinary + * query, but the strength of this module lies in its extensibility + * and standard library: + * + * - points of interest can be added, removed and mixed seamlessly + * - this module comes with a collection of standard points of interest (see `StandardPoIs`) + * + * A global configuration for the points of interest (see + * `PoIConfiguration`) can be used to easily manage multiple points of + * interests, and to restrict the points of interest to specific + * corners of the code base. + * + * Below is an example use of this module that will produce an alert + * for each route handler and route handler setup in a file named + * "server-core.js". The route setup alerts will contain a link to its + * associated route handler. + * + * ``` + * /** + * * @kind problem + * *\/ + * + * import PoI + * + * class Configuration extends PoIConfiguration { + * Configuration() { this = "Configuration" } + * + * override predicate shown(DataFlow::Node n) { n.getFile().getBaseName() = "server-core.js" } + * } + * + * class RouteHandlerPoI extends ActivePoI { + * RouteHandlerPoI() { this = "RouteHandlerPoI" } + * override predicate is(DataFlow::Node l0) { l0 instanceof Express::RouteHandler } + * } + * + * class RouteSetupAndRouteHandlerPoI extends ActivePoI { + * RouteSetupAndRouteHandlerPoI() { this = "RouteSetupAndRouteHandlerPoI" } + * + * override predicate is(DataFlow::Node l0, DataFlow::Node l1, string t1) { + * l0.asExpr().(Express::RouteSetup).getARouteHandler() = l1 and t1 = "routehandler" + * } + * } + * + * query predicate problems = alertQuery/6; + * ``` + */ + +import javascript +private import DataFlow +private import filters.ClassifyFiles +private import semmle.javascript.RestrictedLocations + +/** + * Provides often used points of interest. + * + * Note that these points of interest should not extend + * `ActivePoI`, and that they can be enabled on + * demand like this: + * + * ``` + * class MyPoI extends ServerRelatedPoI, ActivePoI {} + * ``` + */ +private module StandardPoIs { + /** + * An unpromoted route setup candidate. + */ + class UnpromotedRouteSetupPoI extends PoI { + UnpromotedRouteSetupPoI() { this = "UnpromotedRouteSetupPoI" } + + override predicate is(Node l0) { + l0 instanceof HTTP::RouteSetupCandidate and not l0.asExpr() instanceof HTTP::RouteSetup + } + } + + /** + * An unpromoted route handler candidate. + */ + class UnpromotedRouteHandlerPoI extends PoI { + UnpromotedRouteHandlerPoI() { this = "UnpromotedRouteHandlerPoI" } + + override predicate is(Node l0) { + l0 instanceof HTTP::RouteHandlerCandidate and not l0 instanceof HTTP::RouteHandler + } + } + + /** + * An unpromoted route handler candidate, with explanatory data flow information. + */ + class UnpromotedRouteHandlerWithFlowPoI extends PoI { + UnpromotedRouteHandlerWithFlowPoI() { this = "UnpromotedRouteHandlerWithFlowPoI" } + + private DataFlow::SourceNode track(HTTP::RouteHandlerCandidate cand, DataFlow::TypeTracker t) { + t.start() and + result = cand + or + exists(DataFlow::TypeTracker t2 | result = track(cand, t2).track(t2, t)) + } + + override predicate is(Node l0, Node l1, string t1) { + l0 instanceof HTTP::RouteHandlerCandidate and + not l0 instanceof HTTP::RouteHandler and + l1 = track(l0, TypeTracker::end()) and + (if l1 = l0 then t1 = "ends here" else t1 = "starts/ends here") + } + } + + /** + * A callee that is unknown. + */ + class UnknownCalleePoI extends PoI { + UnknownCalleePoI() { this = "UnknownCalleePoI" } + + override predicate is(Node l0) { + exists(InvokeNode invk | l0 = invk.getCalleeNode() and not exists(invk.getACallee())) + } + } + + /** + * A source of remote flow. + */ + class RemoteFlowSourcePoI extends PoI { + RemoteFlowSourcePoI() { this = "RemoteFlowSourcePoI" } + + override predicate is(Node l0) { l0 instanceof RemoteFlowSource } + } + + /** + * A "source" for any active configuration. + */ + class SourcePoI extends PoI { + SourcePoI() { this = "SourcePoI" } + + override predicate is(Node l0) { + exists(Configuration cfg | cfg.isSource(l0) or cfg.isSource(l0, _)) + } + } + + /** + * A "sink" for any active configuration. + */ + class SinkPoI extends PoI { + SinkPoI() { this = "SinkPoI" } + + override predicate is(Node l0) { + exists(Configuration cfg | cfg.isSink(l0) or cfg.isSink(l0, _)) + } + } + + /** + * A "barrier" for any active configuration. + */ + class BarrierPoI extends PoI { + BarrierPoI() { this = "BarrierPoI" } + + override predicate is(Node l0) { + exists(Configuration cfg | + cfg.isBarrier(l0) or + cfg.isBarrierEdge(l0, _) or + cfg.isBarrierEdge(l0, _, _) or + cfg.isLabeledBarrier(l0, _) + ) + } + } + + /** + * Provides groups of often used points of interest. + */ + module StandardPoIGroups { + /** + * A server-related point of interest. + */ + class ServerRelatedPoI extends PoI { + ServerRelatedPoI() { + this instanceof UnpromotedRouteSetupPoI or + this instanceof UnpromotedRouteHandlerPoI or + this instanceof UnpromotedRouteHandlerWithFlowPoI + } + } + + /** + * A configuration-related point of interest. + */ + class DataFlowConfigurationPoI extends PoI { + DataFlowConfigurationPoI() { + this instanceof SourcePoI or + this instanceof SinkPoI + } + } + } + + import StandardPoIGroups +} + +import StandardPoIs + +/** + * A tagging interface for a custom point of interest that should be + * enabled in the absence of an explicit + * `PoIConfiguration::enabled/1`. + */ +abstract class ActivePoI extends PoI { + bindingset[this] + ActivePoI() { any() } +} + +private module PoIConfigDefaults { + predicate enabled(PoI poi) { poi instanceof ActivePoI } + + predicate shown(Node n) { not classify(n.getFile(), _) } +} + +/** + * A configuration for the points of interest to display. + */ +abstract class PoIConfiguration extends string { + bindingset[this] + PoIConfiguration() { any() } + + /** + * Holds if the points of interest from `poi` should be shown. + */ + predicate enabled(PoI poi) { PoIConfigDefaults::enabled(poi) } + + /** + * Holds if the points of interest `n` should be shown. + */ + predicate shown(Node n) { PoIConfigDefaults::shown(n) } +} + +/** + * A class of points of interest. + * + * Note that only one of the `is/1`, `is/3`, `is/5` methods should + * be overridden, as two overrides will degrade the alert UI + * slightly. + */ +abstract class PoI extends string { + bindingset[this] + PoI() { any() } + + /** + * Holds if `l0` is a point of interest. + */ + predicate is(Node l0) { none() } + + /** + * Holds if `l0` is a point of interest, with `l1` as an auxiliary location described by `t1`. + */ + predicate is(Node l0, Node l1, string t1) { none() } + + /** + * Holds if `l0` is a point of interest, with `l1` and `l2` as auxiliary locations described by `t1` and `t2`. + */ + predicate is(Node l0, Node l1, string t1, Node l2, string t2) { none() } + + /** + * Gets the message format for the point of interest. + */ + string getFormat() { + is(_) and result = "" + or + is(_, _, _) and result = "$@" + or + is(_, _, _, _, _) and result = "$@ $@" + } +} + +/** + * An alert query for a point of interest. + * + * Should be used as: + * + * ``` + * query predicate problems = alertQuery/6; + * ``` + * + * Or alternatively: + * + * ``` + * from Locatable l1line, string msg, Node l2, string s2, Node l3, string s3 + * where alertQuery(l1line, msg, l2, s2, l3, s3) + * select l1line, msg, l2, s2, l3, s3 + * ``` + * + * Note that some points of interest do not have auxiliary + * locations, so `l2`,`l3`, `s2`, `s3` may have placeholder values. + */ +predicate alertQuery(Locatable l1line, string msg, Node l2, string s2, Node l3, string s3) { + exists(PoI poi, Node l1, string m | + l1.getAstNode().(FirstLineOf) = l1line and + ( + not exists(PoIConfiguration cfg) and + PoIConfigDefaults::enabled(poi) and + PoIConfigDefaults::shown(l1) + or + exists(PoIConfiguration cfg | + cfg.enabled(poi) and + cfg.shown(l1) + ) + ) and + m = poi.getFormat() and + if m = "" then msg = poi else msg = poi + ": " + m + | + poi.is(l1) and + l1 = l2 and + s2 = "irrelevant" and + l1 = l3 and + s3 = "irrelevant" + or + poi.is(l1, l2, s2) and + l1 = l3 and + s3 = "irrelevant" + or + poi.is(l1, l2, s2, l3, s3) + ) +} diff --git a/javascript/ql/src/filters/ClassifyFiles.ql b/javascript/ql/src/filters/ClassifyFiles.ql index 64606ef685c..fa7aad41ab4 100644 --- a/javascript/ql/src/filters/ClassifyFiles.ql +++ b/javascript/ql/src/filters/ClassifyFiles.ql @@ -7,85 +7,8 @@ * @id js/file-classifier */ -import semmle.javascript.GeneratedCode -import semmle.javascript.frameworks.Testing -import semmle.javascript.frameworks.Templating -import semmle.javascript.dependencies.FrameworkLibraries - -/** - * Holds if `e` may be caused by parsing a template file as plain HTML or JavaScript. - * - * We use two heuristics: check for the presence of a known template delimiter preceding - * the error on the same line, and check whether the file name contains `template` or - * `templates`. - */ -predicate maybeCausedByTemplate(JSParseError e) { - exists(File f | f = e.getFile() | - e.getLine().indexOf(Templating::getADelimiter()) <= e.getLocation().getStartColumn() - or - f.getAbsolutePath().regexpMatch("(?i).*\\btemplates?\\b.*") - ) -} - -/** - * Holds if `e` is an expression in the form `o.p1.p2.p3....pn`. - */ -private predicate isNestedDotExpr(DotExpr e) { - e.getBase() instanceof VarAccess or - isNestedDotExpr(e.getBase()) -} - -/** - * Holds if `tl` only contains variable declarations and field reads. - */ -private predicate looksLikeExterns(TopLevel tl) { - forex(Stmt s | s.getParent() = tl | - exists(VarDeclStmt vds | vds = s | - forall(VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit())) - ) - or - isNestedDotExpr(s.(ExprStmt).getExpr()) - ) -} - -/** - * Holds if `f` is classified as belonging to `category`. - * - * There are currently four categories: - * - `"generated"`: `f` contains generated or minified code; - * - `"test"`: `f` contains test code; - * - `"externs"`: `f` contains externs declarations; - * - `"library"`: `f` contains library code; - * - `"template"`: `f` contains template code. - */ -predicate classify(File f, string category) { - isGenerated(f.getATopLevel()) and category = "generated" - or - ( - exists(Test t | t.getFile() = f) - or - exists(string stemExt | stemExt = "test" or stemExt = "spec" | - f = getTestFile(any(File orig), stemExt) - ) - or - f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*") - ) and - category = "test" - or - (f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and - category = "externs" - or - f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library" - or - exists(JSParseError err | maybeCausedByTemplate(err) | - f = err.getFile() and category = "template" - ) - or - // Polymer templates - exists(HTML::Element elt | elt.getName() = "template" | - f = elt.getFile() and category = "template" - ) -} +import javascript +import ClassifyFiles from File f, string category where classify(f, category) diff --git a/javascript/ql/src/filters/ClassifyFiles.qll b/javascript/ql/src/filters/ClassifyFiles.qll new file mode 100644 index 00000000000..e59b13943b7 --- /dev/null +++ b/javascript/ql/src/filters/ClassifyFiles.qll @@ -0,0 +1,85 @@ +/** + * Provides classes and predicates for classifying files as containing + * generated code, test code, externs declarations, library code or + * template code. + */ + +import semmle.javascript.GeneratedCode +import semmle.javascript.frameworks.Testing +import semmle.javascript.frameworks.Templating +import semmle.javascript.dependencies.FrameworkLibraries + +/** + * Holds if `e` may be caused by parsing a template file as plain HTML or JavaScript. + * + * We use two heuristics: check for the presence of a known template delimiter preceding + * the error on the same line, and check whether the file name contains `template` or + * `templates`. + */ +predicate maybeCausedByTemplate(JSParseError e) { + exists(File f | f = e.getFile() | + e.getLine().indexOf(Templating::getADelimiter()) <= e.getLocation().getStartColumn() + or + f.getAbsolutePath().regexpMatch("(?i).*\\btemplates?\\b.*") + ) +} + +/** + * Holds if `e` is an expression in the form `o.p1.p2.p3....pn`. + */ +private predicate isNestedDotExpr(DotExpr e) { + e.getBase() instanceof VarAccess or + isNestedDotExpr(e.getBase()) +} + +/** + * Holds if `tl` only contains variable declarations and field reads. + */ +private predicate looksLikeExterns(TopLevel tl) { + forex(Stmt s | s.getParent() = tl | + exists(VarDeclStmt vds | vds = s | + forall(VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit())) + ) + or + isNestedDotExpr(s.(ExprStmt).getExpr()) + ) +} + +/** + * Holds if `f` is classified as belonging to `category`. + * + * There are currently four categories: + * - `"generated"`: `f` contains generated or minified code; + * - `"test"`: `f` contains test code; + * - `"externs"`: `f` contains externs declarations; + * - `"library"`: `f` contains library code; + * - `"template"`: `f` contains template code. + */ +predicate classify(File f, string category) { + isGenerated(f.getATopLevel()) and category = "generated" + or + ( + exists(Test t | t.getFile() = f) + or + exists(string stemExt | stemExt = "test" or stemExt = "spec" | + f = getTestFile(any(File orig), stemExt) + ) + or + f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*") + ) and + category = "test" + or + (f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and + category = "externs" + or + f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library" + or + exists(JSParseError err | maybeCausedByTemplate(err) | + f = err.getFile() and category = "template" + ) + or + // Polymer templates + exists(HTML::Element elt | elt.getName() = "template" | + f = elt.getFile() and category = "template" + ) +} diff --git a/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.expected b/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.expected new file mode 100644 index 00000000000..76ec9dc360b --- /dev/null +++ b/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.expected @@ -0,0 +1,4 @@ +| tst.js:16:15:16:25 | req.query.x | SourcePoI | tst.js:16:15:16:25 | req.query.x | irrelevant | tst.js:16:15:16:25 | req.query.x | irrelevant | +| tst.js:17:11:17:21 | req.query.x | SinkPoI | tst.js:17:11:17:21 | req.query.x | irrelevant | tst.js:17:11:17:21 | req.query.x | irrelevant | +| tst.js:17:11:17:21 | req.query.x | SourcePoI | tst.js:17:11:17:21 | req.query.x | irrelevant | tst.js:17:11:17:21 | req.query.x | irrelevant | +| tst.js:18:12:18:22 | req.query.x | SourcePoI | tst.js:18:12:18:22 | req.query.x | irrelevant | tst.js:18:12:18:22 | req.query.x | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.ql b/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.ql new file mode 100644 index 00000000000..4b7e798b226 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/CommandInjectionPoIConfiguration.ql @@ -0,0 +1,13 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI +import semmle.javascript.security.dataflow.CommandInjection +import semmle.javascript.security.dataflow.IndirectCommandInjection +import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironment + +class MyDataFlowConfigurationPoIs extends DataFlowConfigurationPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.expected b/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.expected new file mode 100644 index 00000000000..8de0092de20 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.expected @@ -0,0 +1,3 @@ +| tst.js:6:1:6:16 | (req, res) => 42 | UnpromotedRouteHandlerPoI | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | +| tst.js:6:1:6:16 | (req, res) => 42 | UnpromotedRouteHandlerWithFlowPoI: $@ | tst.js:6:1:6:16 | (req, res) => 42 | ends here | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | +| tst.js:13:1:13:36 | otherAp ... h", rh) | UnpromotedRouteSetupPoI | tst.js:13:1:13:36 | otherAp ... h", rh) | irrelevant | tst.js:13:1:13:36 | otherAp ... h", rh) | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.ql b/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.ql new file mode 100644 index 00000000000..3dcc0b17603 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/ServerPoIConfiguration.ql @@ -0,0 +1,10 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI + +class MyServerRelatedPoIs extends ServerRelatedPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/ServerPoIs.expected b/javascript/ql/test/experimental/PoI/ServerPoIs.expected new file mode 100644 index 00000000000..8de0092de20 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/ServerPoIs.expected @@ -0,0 +1,3 @@ +| tst.js:6:1:6:16 | (req, res) => 42 | UnpromotedRouteHandlerPoI | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | +| tst.js:6:1:6:16 | (req, res) => 42 | UnpromotedRouteHandlerWithFlowPoI: $@ | tst.js:6:1:6:16 | (req, res) => 42 | ends here | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | +| tst.js:13:1:13:36 | otherAp ... h", rh) | UnpromotedRouteSetupPoI | tst.js:13:1:13:36 | otherAp ... h", rh) | irrelevant | tst.js:13:1:13:36 | otherAp ... h", rh) | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/ServerPoIs.ql b/javascript/ql/test/experimental/PoI/ServerPoIs.ql new file mode 100644 index 00000000000..cf253b716ab --- /dev/null +++ b/javascript/ql/test/experimental/PoI/ServerPoIs.ql @@ -0,0 +1,10 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI + +class MyServerRelatedPoI extends ServerRelatedPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.expected b/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.expected new file mode 100644 index 00000000000..7de2d09cd54 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.expected @@ -0,0 +1,6 @@ +| tst.js:1:23:1:31 | "express" | SinkPoI | tst.js:1:23:1:31 | "express" | irrelevant | tst.js:1:23:1:31 | "express" | irrelevant | +| tst.js:2:16:2:19 | "fs" | SinkPoI | tst.js:2:16:2:19 | "fs" | irrelevant | tst.js:2:16:2:19 | "fs" | irrelevant | +| tst.js:3:16:3:30 | "child_process" | SinkPoI | tst.js:3:16:3:30 | "child_process" | irrelevant | tst.js:3:16:3:30 | "child_process" | irrelevant | +| tst.js:16:15:16:25 | req.query.x | SourcePoI | tst.js:16:15:16:25 | req.query.x | irrelevant | tst.js:16:15:16:25 | req.query.x | irrelevant | +| tst.js:17:11:17:21 | req.query.x | SourcePoI | tst.js:17:11:17:21 | req.query.x | irrelevant | tst.js:17:11:17:21 | req.query.x | irrelevant | +| tst.js:18:12:18:22 | req.query.x | SourcePoI | tst.js:18:12:18:22 | req.query.x | irrelevant | tst.js:18:12:18:22 | req.query.x | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.ql b/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.ql new file mode 100644 index 00000000000..e21bc68f68c --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TaintedPathPoIConfiguration.ql @@ -0,0 +1,11 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI +import semmle.javascript.security.dataflow.TaintedPath + +class MyDataflowRelatedPoIs extends DataFlowConfigurationPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/TestCustomPoIs.expected b/javascript/ql/test/experimental/PoI/TestCustomPoIs.expected new file mode 100644 index 00000000000..e5bba0aeb5b --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TestCustomPoIs.expected @@ -0,0 +1,9 @@ +| tst.js:8:1:8:44 | app.get ... es) {}) | RouteSetupAndRouterAndRouteHandlerPoI: $@ $@ | tst.js:4:11:4:19 | express() | router | tst.js:8:23:8:43 | functio ... res) {} | routehandler | +| tst.js:8:23:8:43 | functio ... res) {} | RouteHandlerAndSetupPoI: $@ | tst.js:8:1:8:44 | app.get ... es) {}) | setup | tst.js:8:23:8:43 | functio ... res) {} | irrelevant | +| tst.js:8:23:8:43 | functio ... res) {} | RouteHandlerPoI | tst.js:8:23:8:43 | functio ... res) {} | irrelevant | tst.js:8:23:8:43 | functio ... res) {} | irrelevant | +| tst.js:10:10:10:30 | functio ... res) {} | RouteHandlerAndSetupPoI: $@ | tst.js:11:1:11:31 | app.get ... h", rh) | setup | tst.js:10:10:10:30 | functio ... res) {} | irrelevant | +| tst.js:10:10:10:30 | functio ... res) {} | RouteHandlerPoI | tst.js:10:10:10:30 | functio ... res) {} | irrelevant | tst.js:10:10:10:30 | functio ... res) {} | irrelevant | +| tst.js:11:1:11:31 | app.get ... h", rh) | RouteSetupAndRouterAndRouteHandlerPoI: $@ $@ | tst.js:4:11:4:19 | express() | router | tst.js:10:10:10:30 | functio ... res) {} | routehandler | +| tst.js:15:1:19:2 | app.get ... .x);\\n}) | RouteSetupAndRouterAndRouteHandlerPoI: $@ $@ | tst.js:4:11:4:19 | express() | router | tst.js:15:23:19:1 | functio ... y.x);\\n} | routehandler | +| tst.js:15:23:19:1 | functio ... y.x);\\n} | RouteHandlerAndSetupPoI: $@ | tst.js:15:1:19:2 | app.get ... .x);\\n}) | setup | tst.js:15:23:19:1 | functio ... y.x);\\n} | irrelevant | +| tst.js:15:23:19:1 | functio ... y.x);\\n} | RouteHandlerPoI | tst.js:15:23:19:1 | functio ... y.x);\\n} | irrelevant | tst.js:15:23:19:1 | functio ... y.x);\\n} | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/TestCustomPoIs.ql b/javascript/ql/test/experimental/PoI/TestCustomPoIs.ql new file mode 100644 index 00000000000..5e73f8a4f87 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TestCustomPoIs.ql @@ -0,0 +1,34 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI +import DataFlow + +class RouteHandlerPoI extends ActivePoI { + RouteHandlerPoI() { this = "RouteHandlerPoI" } + + override predicate is(Node l0) { l0 instanceof Express::RouteHandler } +} + +class RouteHandlerAndSetupPoI extends ActivePoI { + RouteHandlerAndSetupPoI() { this = "RouteHandlerAndSetupPoI" } + + override predicate is(Node l0, Node l1, string t1) { + l1.asExpr().(Express::RouteSetup).getARouteHandler() = l0 and t1 = "setup" + } +} + +class RouteSetupAndRouterAndRouteHandlerPoI extends ActivePoI { + RouteSetupAndRouterAndRouteHandlerPoI() { this = "RouteSetupAndRouterAndRouteHandlerPoI" } + + override predicate is(Node l0, Node l1, string t1, Node l2, string t2) { + l0.asExpr().(Express::RouteSetup).getRouter().flow() = l1 and + t1 = "router" and + l0.asExpr().(Express::RouteSetup).getARouteHandler() = l2 and + t2 = "routehandler" + } +} + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/TestStandardPoIs.expected b/javascript/ql/test/experimental/PoI/TestStandardPoIs.expected new file mode 100644 index 00000000000..cf28ee74115 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TestStandardPoIs.expected @@ -0,0 +1 @@ +| tst.js:6:1:6:16 | (req, res) => 42 | UnpromotedRouteHandlerPoI | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | tst.js:6:1:6:16 | (req, res) => 42 | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/TestStandardPoIs.ql b/javascript/ql/test/experimental/PoI/TestStandardPoIs.ql new file mode 100644 index 00000000000..4730b451fdb --- /dev/null +++ b/javascript/ql/test/experimental/PoI/TestStandardPoIs.ql @@ -0,0 +1,10 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI + +class MyUnpromotedRouteHandlerPoIs extends UnpromotedRouteHandlerPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/XssPoIConfiguration.expected b/javascript/ql/test/experimental/PoI/XssPoIConfiguration.expected new file mode 100644 index 00000000000..dca2edaef2b --- /dev/null +++ b/javascript/ql/test/experimental/PoI/XssPoIConfiguration.expected @@ -0,0 +1,4 @@ +| tst.js:16:15:16:25 | req.query.x | SourcePoI | tst.js:16:15:16:25 | req.query.x | irrelevant | tst.js:16:15:16:25 | req.query.x | irrelevant | +| tst.js:17:11:17:21 | req.query.x | SourcePoI | tst.js:17:11:17:21 | req.query.x | irrelevant | tst.js:17:11:17:21 | req.query.x | irrelevant | +| tst.js:18:12:18:22 | req.query.x | SinkPoI | tst.js:18:12:18:22 | req.query.x | irrelevant | tst.js:18:12:18:22 | req.query.x | irrelevant | +| tst.js:18:12:18:22 | req.query.x | SourcePoI | tst.js:18:12:18:22 | req.query.x | irrelevant | tst.js:18:12:18:22 | req.query.x | irrelevant | diff --git a/javascript/ql/test/experimental/PoI/XssPoIConfiguration.ql b/javascript/ql/test/experimental/PoI/XssPoIConfiguration.ql new file mode 100644 index 00000000000..e18b2e96913 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/XssPoIConfiguration.ql @@ -0,0 +1,14 @@ +/** + * @kind problem + */ + +import javascript +import experimental.poi.PoI +import semmle.javascript.security.dataflow.ReflectedXss +import semmle.javascript.security.dataflow.StoredXss +import semmle.javascript.security.dataflow.DomBasedXss +import semmle.javascript.security.dataflow.ExceptionXss + +class MyDataFlowConfigurationPoIs extends DataFlowConfigurationPoI, ActivePoI { } + +query predicate problems = alertQuery/6; diff --git a/javascript/ql/test/experimental/PoI/tst.js b/javascript/ql/test/experimental/PoI/tst.js new file mode 100644 index 00000000000..aa7401f9b76 --- /dev/null +++ b/javascript/ql/test/experimental/PoI/tst.js @@ -0,0 +1,19 @@ +var express = require("express"), + fs = require("fs"), + cp = require("child_process"); +var app = express(); + +(req, res) => 42; + +app.get("/some/path", function(req, res) {}); + +let rh = function(req, res) {}; +app.get("/some/other/path", rh); + +otherApp.get("/some/other/path", rh); + +app.get("/some/path", function(req, res) { + fs.readFile(req.query.x); + cp.exec(req.query.x); + res.send(req.query.x); +});