Merge branch 'master' of git.semmle.com:Semmle/ql into UrlSearch

This commit is contained in:
Erik Krogh Kristensen
2020-03-27 15:59:29 +01:00
146 changed files with 2480 additions and 545 deletions

View File

@@ -1,46 +0,0 @@
/**
* Provides classes for working with [SockJS](http://sockjs.org).
*/
import javascript
/**
* A model of the `SockJS` websocket data handler (https://sockjs.org).
*/
module SockJS {
/**
* Access to user-controlled data object received from websocket
* For example:
* ```
* server.on('connection', function(conn) {
* conn.on('data', function(message) {
* ...
* });
* });
* ```
*/
class SourceFromSocketJS extends RemoteFlowSource {
SourceFromSocketJS() {
exists(
DataFlow::CallNode createServer, DataFlow::CallNode connNode,
DataFlow::CallNode dataHandlerNode
|
createServer = appCreation() and
connNode = createServer.getAMethodCall("on") and
connNode.getArgument(0).getStringValue() = "connection" and
dataHandlerNode = connNode.getCallback(1).getParameter(0).getAMethodCall("on") and
dataHandlerNode.getArgument(0).getStringValue() = "data" and
this = dataHandlerNode.getCallback(1).getParameter(0)
)
}
override string getSourceType() { result = "input from SockJS WebSocket" }
}
/**
* Gets a new SockJS server.
*/
private DataFlow::CallNode appCreation() {
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
}
}

View File

@@ -1,16 +0,0 @@
const express = require('express');
const http = require('http');
const sockjs = require('sockjs');
const app = express();
const server = http.createServer(app);
const sockjs_echo = sockjs.createServer({});
sockjs_echo.on('connection', function(conn) {
conn.on('data', function(message) {
var data = JSON.parse(message);
conn.write(JSON.stringify(eval(data.test)));
});
});
sockjs_echo.installHandlers(server, {prefix:'/echo'});
server.listen(9090, '127.0.0.1');

View File

@@ -5,6 +5,7 @@
import Customizations
import semmle.javascript.Aliases
import semmle.javascript.AMD
import semmle.javascript.Arrays
import semmle.javascript.AST
import semmle.javascript.BasicBlocks
import semmle.javascript.Base64

View File

@@ -0,0 +1,263 @@
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* Classes and predicates for modelling TaintTracking steps for arrays.
*/
module ArrayTaintTracking {
/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
private class ArrayFunctionTaintStep extends TaintTracking::AdditionalTaintStep,
DataFlow::CallNode {
ArrayFunctionTaintStep() { arrayFunctionTaintStep(_, _, this) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
arrayFunctionTaintStep(pred, succ, this)
}
}
/**
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
*/
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach`
exists(string name, Function f, int i |
(name = "map" or name = "forEach") and
(i = 0 or i = 2) and
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = name and
pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter(i))
)
or
// `array.map` with tainted return value in callback
exists(DataFlow::FunctionNode f |
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
pred = f.getAReturn() and
succ = call
)
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = name
)
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
exists(string name | name = "splice" |
pred = call.getArgument(2) and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
exists(string name |
name = "pop" or
name = "shift" or
name = "slice" or
name = "splice"
|
call.(DataFlow::MethodCallNode).calls(pred, name) and
succ = call
)
or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
succ = call and
pred = call.getAnArgument()
}
}
/**
* Classes and predicates for modelling data-flow for arrays.
*/
private module ArrayDataFlow {
/**
* Gets a pseudo-field representing an element inside an array.
*/
private string arrayElement() { result = "$arrayElement$" }
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayAppendStep() {
this.getMethodName() = "push" or
this.getMethodName() = "unshift"
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
(element = this.getAnArgument() or element = this.getASpreadArgument()) and
obj = this.getReceiver().getALocalSource()
}
}
/**
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
*/
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
DataFlow::PropRef read;
ArrayIndexingStep() {
read = this and
forex(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType() |
type = TTNumber()
) and
exists(VarAccess i, ExprOrVarDecl init |
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
|
i.getVariable().getADefinition() = init or
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
)
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.(DataFlow::PropRead).getBase() and
element = this
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::PropWrite).getRhs() and
this = obj.(DataFlow::SourceNode).getAPropertyWrite()
}
}
/**
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
* E.g. `array.pop()`.
*/
private class ArrayPopStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayPopStep() {
getMethodName() = "pop" or
getMethodName() = "shift"
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = this
}
}
/**
* A step for iterating an array using `map` or `forEach`.
*
* Array elements can be loaded from the array `arr` to `e` in e.g: `arr.forEach(e => ...)`.
*
* And array elements can be stored into a resulting array using `map(...)`.
* E.g. in `arr.map(e => foo)`, the resulting array (`arr.map(e => foo)`) will contain the element `foo`.
*
* And the second parameter in the callback is the array ifself, so there is a `loadStoreStep` from the array to that second parameter.
*/
private class ArrayIteration extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayIteration() {
this.getMethodName() = "map" or
this.getMethodName() = "forEach"
}
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = getCallback(0).getParameter(0)
}
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
this.getMethodName() = "map" and
prop = arrayElement() and
element = this.getCallback(0).getAReturn() and
obj = this
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = getCallback(0).getParameter(2)
}
}
/**
* A step for creating an array and storing the elements in the array.
*/
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
ArrayCreationStep() { this instanceof DataFlow::ArrayCreationNode }
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::ArrayCreationNode).getAnElement() and
obj = this
}
}
/**
* A step modelling that `splice` can insert elements into an array.
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
*/
private class ArraySpliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySpliceStep() { this.getMethodName() = "splice" }
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
prop = arrayElement() and
element = getArgument(2) and
obj = this.getReceiver().getALocalSource()
}
}
/**
* A step for modelling `concat`.
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
*/
private class ArrayConcatStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayConcatStep() { this.getMethodName() = "concat" }
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
(pred = this.getReceiver() or pred = this.getAnArgument()) and
succ = this
}
}
/**
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySliceStep() {
this.getMethodName() = "slice" or
this.getMethodName() = "splice" or
this.getMethodName() = "filter"
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = this
}
}
}

View File

@@ -117,7 +117,14 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
ArgumentsVariable getArgumentsVariable() { result.getFunction() = this }
/** Holds if the body of this function refers to the function's `arguments` variable. */
predicate usesArgumentsObject() { exists(getArgumentsVariable().getAnAccess()) }
predicate usesArgumentsObject() {
exists(getArgumentsVariable().getAnAccess())
or
exists(PropAccess read |
read.getBase() = getVariable().getAnAccess() and
read.getPropertyName() = "arguments"
)
}
/**
* Holds if this function declares a parameter or local variable named `arguments`.

View File

@@ -167,6 +167,7 @@ module PromiseTypeTracking {
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
*/
pragma[inline]
DataFlow::SourceNode promiseStep(DataFlow::SourceNode pred, StepSummary summary) {
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
summary = LoadStep(field) and

View File

@@ -633,32 +633,28 @@ pragma[inline]
private predicate basicFlowStepNoBarrier(
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
) {
isLive() and
isRelevantForward(pred, cfg) and
(
// Local flow
exists(FlowLabel predlbl, FlowLabel succlbl |
localFlowStep(pred, succ, cfg, predlbl, succlbl) and
not cfg.isBarrierEdge(pred, succ) and
summary = MkPathSummary(false, false, predlbl, succlbl)
)
or
// Flow through properties of objects
propertyFlowStep(pred, succ) and
summary = PathSummary::level()
or
// Flow through global variables
globalFlowStep(pred, succ) and
summary = PathSummary::level()
or
// Flow into function
callStep(pred, succ) and
summary = PathSummary::call()
or
// Flow out of function
returnStep(pred, succ) and
summary = PathSummary::return()
// Local flow
exists(FlowLabel predlbl, FlowLabel succlbl |
localFlowStep(pred, succ, cfg, predlbl, succlbl) and
not cfg.isBarrierEdge(pred, succ) and
summary = MkPathSummary(false, false, predlbl, succlbl)
)
or
// Flow through properties of objects
propertyFlowStep(pred, succ) and
summary = PathSummary::level()
or
// Flow through global variables
globalFlowStep(pred, succ) and
summary = PathSummary::level()
or
// Flow into function
callStep(pred, succ) and
summary = PathSummary::call()
or
// Flow out of function
returnStep(pred, succ) and
summary = PathSummary::return()
}
/**
@@ -671,6 +667,7 @@ private predicate basicFlowStep(
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg
) {
basicFlowStepNoBarrier(pred, succ, summary, cfg) and
isRelevant(pred, cfg) and
not isLabeledBarrierEdge(cfg, pred, succ, summary.getStartLabel()) and
not isBarrierEdge(cfg, pred, succ)
}
@@ -685,17 +682,21 @@ private predicate basicFlowStep(
private predicate exploratoryFlowStep(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg
) {
basicFlowStepNoBarrier(pred, succ, _, cfg) or
basicStoreStep(pred, succ, _) or
basicLoadStep(pred, succ, _) or
isAdditionalStoreStep(pred, succ, _, cfg) or
isAdditionalLoadStep(pred, succ, _, cfg) or
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
// the following three disjuncts taken together over-approximate flow through
// higher-order calls
callback(pred, succ) or
succ = pred.(DataFlow::FunctionNode).getAParameter() or
exploratoryBoundInvokeStep(pred, succ)
isRelevantForward(pred, cfg) and
isLive() and
(
basicFlowStepNoBarrier(pred, succ, _, cfg) or
basicStoreStep(pred, succ, _) or
basicLoadStep(pred, succ, _) or
isAdditionalStoreStep(pred, succ, _, cfg) or
isAdditionalLoadStep(pred, succ, _, cfg) or
isAdditionalLoadStoreStep(pred, succ, _, _, cfg) or
// the following three disjuncts taken together over-approximate flow through
// higher-order calls
callback(pred, succ) or
succ = pred.(DataFlow::FunctionNode).getAParameter() or
exploratoryBoundInvokeStep(pred, succ)
)
}
/**
@@ -850,9 +851,11 @@ private predicate storeStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg,
PathSummary summary
) {
isRelevant(pred, cfg) and
basicStoreStep(pred, succ, prop) and
summary = PathSummary::level()
or
isRelevant(pred, cfg) and
isAdditionalStoreStep(pred, succ, prop, cfg) and
summary = PathSummary::level()
or
@@ -957,9 +960,11 @@ private predicate loadStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg,
PathSummary summary
) {
isRelevant(pred, cfg) and
basicLoadStep(pred, succ, prop) and
summary = PathSummary::level()
or
isRelevant(pred, cfg) and
isAdditionalLoadStep(pred, succ, prop, cfg) and
summary = PathSummary::level()
or
@@ -1184,7 +1189,6 @@ private predicate flowStep(
// Flow into higher-order call
flowIntoHigherOrderCall(pred, succ, cfg, summary)
) and
isRelevant(succ, cfg) and
not cfg.isBarrier(succ) and
not isBarrierEdge(cfg, pred, succ) and
not isLabeledBarrierEdge(cfg, pred, succ, summary.getEndLabel()) and
@@ -1238,12 +1242,25 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg, PathSum
not cfg.isLabeledBarrier(nd, summary.getEndLabel())
or
exists(DataFlow::Node mid, PathSummary stepSummary |
reachableFromSource(nd, cfg, summary) and
flowStep(nd, id(cfg), mid, stepSummary) and
onPathStep(nd, cfg, summary, stepSummary, mid) and
onPath(mid, id(cfg), summary.append(stepSummary))
)
}
/**
* Holds if `nd` can be reached from a source under `cfg`,
* and there is a flowStep from `nd` (with summary `summary`) to `mid` (with summary `stepSummary`).
*
* This predicate has been outlined from `onPath` to give the optimizer a hint about join-ordering.
*/
private predicate onPathStep(
DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary, PathSummary stepSummary,
DataFlow::Node mid
) {
reachableFromSource(nd, cfg, summary) and
flowStep(nd, id(cfg), mid, stepSummary)
}
/**
* Holds if there is a configuration that has at least one source and at least one sink.
*/

View File

@@ -587,8 +587,8 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
}
/**
* A data flow node corresponding to the creation or a new array, either through an array literal
* or an invocation of the `Array` constructor.
* A data flow node corresponding to the creation or a new array, either through an array literal,
* an invocation of the `Array` constructor, or the `Array.from` method.
*
*
* Examples:

View File

@@ -229,45 +229,45 @@ module TaintTracking {
* promises.
*/
private class HeapTaintStep extends AdditionalTaintStep {
HeapTaintStep() {
this = DataFlow::valueNode(_) or
this = DataFlow::parameterNode(_) or
this instanceof DataFlow::PropRead
}
HeapTaintStep() { heapStep(_, this) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
exists(Expr e, Expr f | e = this.asExpr() and f = pred.asExpr() |
// arrays with tainted elements and objects with tainted property names are tainted
e.(ArrayExpr).getAnElement() = f
or
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
prop.isComputed() and f = prop.getNameExpr()
)
or
// awaiting a tainted expression gives a tainted result
e.(AwaitExpr).getOperand() = f
or
// spreading a tainted object into an object literal gives a tainted object
e.(ObjectExpr).getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand() = f
or
// spreading a tainted value into an array literal gives a tainted array
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
)
or
// reading from a tainted object yields a tainted result
this = succ and
succ.(DataFlow::PropRead).getBase() = pred
or
// iterating over a tainted iterator taints the loop variable
exists(ForOfStmt fos |
this = DataFlow::valueNode(fos.getIterationDomain()) and
pred = this and
succ = DataFlow::lvalueNode(fos.getLValue())
)
heapStep(pred, succ) and succ = this
}
}
/**
* Holds if there is taint propagation through the heap from `pred` to `succ`.
*/
private predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Expr e, Expr f | e = succ.asExpr() and f = pred.asExpr() |
// arrays with tainted elements and objects with tainted property names are tainted
e.(ArrayExpr).getAnElement() = f
or
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
prop.isComputed() and f = prop.getNameExpr()
)
or
// awaiting a tainted expression gives a tainted result
e.(AwaitExpr).getOperand() = f
or
// spreading a tainted object into an object literal gives a tainted object
e.(ObjectExpr).getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand() = f
or
// spreading a tainted value into an array literal gives a tainted array
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
)
or
// reading from a tainted object yields a tainted result
succ.(DataFlow::PropRead).getBase() = pred
or
// iterating over a tainted iterator taints the loop variable
exists(ForOfStmt fos |
pred = DataFlow::valueNode(fos.getIterationDomain()) and
succ = DataFlow::lvalueNode(fos.getLValue())
)
}
/**
* A taint propagating data flow edge through persistent storage.
*/
@@ -282,92 +282,7 @@ module TaintTracking {
}
}
/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
private class ArrayFunctionTaintStep extends AdditionalTaintStep {
DataFlow::CallNode call;
ArrayFunctionTaintStep() { this = call }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
arrayFunctionTaintStep(pred, succ, call)
}
}
/**
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
*/
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach`
exists(string name, Function f, int i |
(name = "map" or name = "forEach") and
(i = 0 or i = 2) and
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = name and
pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter(i))
)
or
// `array.map` with tainted return value in callback
exists(DataFlow::FunctionNode f |
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
pred = f.getAReturn() and
succ = call
)
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
exists(string name |
name = "push" or
name = "unshift"
|
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = name
)
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
exists(string name | name = "splice" |
pred = call.getArgument(2) and
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
)
or
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
exists(string name |
name = "pop" or
name = "shift" or
name = "slice" or
name = "splice"
|
call.(DataFlow::MethodCallNode).calls(pred, name) and
succ = call
)
or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
succ = call and
pred = call.getAnArgument()
}
predicate arrayFunctionTaintStep = ArrayTaintTracking::arrayFunctionTaintStep/3;
/**
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where
@@ -473,86 +388,93 @@ module TaintTracking {
* functions defined in the standard library.
*/
private class StringManipulationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode {
StringManipulationTaintStep() { stringManipulationStep(_, this) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
(
// string operations that propagate taint
exists(string name | name = astNode.(MethodCallExpr).getMethodName() |
pred.asExpr() = astNode.(MethodCallExpr).getReceiver() and
(
// sorted, interesting, properties of String.prototype
name = "anchor" or
name = "big" or
name = "blink" or
name = "bold" or
name = "concat" or
name = "fixed" or
name = "fontcolor" or
name = "fontsize" or
name = "italics" or
name = "link" or
name = "padEnd" or
name = "padStart" or
name = "repeat" or
name = "replace" or
name = "slice" or
name = "small" or
name = "split" or
name = "strike" or
name = "sub" or
name = "substr" or
name = "substring" or
name = "sup" or
name = "toLocaleLowerCase" or
name = "toLocaleUpperCase" or
name = "toLowerCase" or
name = "toUpperCase" or
name = "trim" or
name = "trimLeft" or
name = "trimRight" or
// sorted, interesting, properties of Object.prototype
name = "toString" or
name = "valueOf" or
// sorted, interesting, properties of Array.prototype
name = "join"
)
or
exists(int i | pred.asExpr() = astNode.(MethodCallExpr).getArgument(i) |
name = "concat"
or
name = "replace" and i = 1
)
)
or
// standard library constructors that propagate taint: `RegExp` and `String`
exists(DataFlow::InvokeNode invk, string gv | gv = "RegExp" or gv = "String" |
this = invk and
invk = DataFlow::globalVarRef(gv).getAnInvocation() and
pred = invk.getArgument(0)
)
or
// String.fromCharCode and String.fromCodePoint
exists(int i, MethodCallExpr mce |
mce = astNode and
pred.asExpr() = mce.getArgument(i) and
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
)
or
// `(encode|decode)URI(Component)?` propagate taint
exists(DataFlow::CallNode c, string name |
this = c and
c = DataFlow::globalVarRef(name).getACall() and
pred = c.getArgument(0)
|
name = "encodeURI" or
name = "decodeURI" or
name = "encodeURIComponent" or
name = "decodeURIComponent"
)
)
stringManipulationStep(pred, succ)
}
}
/**
* Holds if taint can propagate from `pred` to `succ` with a step related to string manipulation.
*/
private predicate stringManipulationStep(DataFlow::Node pred, DataFlow::ValueNode succ) {
// string operations that propagate taint
exists(string name | name = succ.getAstNode().(MethodCallExpr).getMethodName() |
pred.asExpr() = succ.getAstNode().(MethodCallExpr).getReceiver() and
(
// sorted, interesting, properties of String.prototype
name = "anchor" or
name = "big" or
name = "blink" or
name = "bold" or
name = "concat" or
name = "fixed" or
name = "fontcolor" or
name = "fontsize" or
name = "italics" or
name = "link" or
name = "padEnd" or
name = "padStart" or
name = "repeat" or
name = "replace" or
name = "slice" or
name = "small" or
name = "split" or
name = "strike" or
name = "sub" or
name = "substr" or
name = "substring" or
name = "sup" or
name = "toLocaleLowerCase" or
name = "toLocaleUpperCase" or
name = "toLowerCase" or
name = "toUpperCase" or
name = "trim" or
name = "trimLeft" or
name = "trimRight" or
// sorted, interesting, properties of Object.prototype
name = "toString" or
name = "valueOf" or
// sorted, interesting, properties of Array.prototype
name = "join"
)
or
exists(int i | pred.asExpr() = succ.getAstNode().(MethodCallExpr).getArgument(i) |
name = "concat"
or
name = "replace" and i = 1
)
)
or
// standard library constructors that propagate taint: `RegExp` and `String`
exists(DataFlow::InvokeNode invk, string gv | gv = "RegExp" or gv = "String" |
succ = invk and
invk = DataFlow::globalVarRef(gv).getAnInvocation() and
pred = invk.getArgument(0)
)
or
// String.fromCharCode and String.fromCodePoint
exists(int i, MethodCallExpr mce |
mce = succ.getAstNode() and
pred.asExpr() = mce.getArgument(i) and
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
)
or
// `(encode|decode)URI(Component)?` propagate taint
exists(DataFlow::CallNode c, string name |
succ = c and
c = DataFlow::globalVarRef(name).getACall() and
pred = c.getArgument(0)
|
name = "encodeURI" or
name = "decodeURI" or
name = "encodeURIComponent" or
name = "decodeURIComponent"
)
}
/**
* A taint propagating data flow edge arising from string formatting.
*/

View File

@@ -201,7 +201,18 @@ private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, DataF
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getAFileName() { result = getCallback([1 .. 2]).getParameter(1) }
override DataFlow::Node getAFileName() { result = trackFileSource(DataFlow::TypeTracker::end()) }
private DataFlow::SourceNode trackFileSource(DataFlow::TypeTracker t) {
t.start() and result = getCallback([1 .. 2]).getParameter(1)
or
t.startInPromise() and not exists(getCallback([1 .. 2])) and result = this
or
// Tracking out of a promise
exists(DataFlow::TypeTracker t2 |
result = PromiseTypeTracking::promiseStep(trackFileSource(t2), t, t2)
)
}
}
/**
@@ -220,10 +231,24 @@ private module JSONFile {
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getADataNode() {
this.getCalleeName() = "readFile" and result = getCallback([1 .. 2]).getParameter(1)
override DataFlow::Node getADataNode() { result = trackRead(DataFlow::TypeTracker::end()) }
private DataFlow::SourceNode trackRead(DataFlow::TypeTracker t) {
this.getCalleeName() = "readFile" and
(
t.start() and result = getCallback([1 .. 2]).getParameter(1)
or
t.startInPromise() and not exists(getCallback([1 .. 2])) and result = this
)
or
this.getCalleeName() = "readFileSync" and result = this
t.start() and
this.getCalleeName() = "readFileSync" and
result = this
or
// Tracking out of a promise
exists(DataFlow::TypeTracker t2 |
result = PromiseTypeTracking::promiseStep(trackRead(t2), t, t2)
)
}
}
@@ -243,6 +268,122 @@ private module JSONFile {
}
}
/**
* A call to the library `load-json-file`.
*/
private class LoadJsonFile extends FileSystemReadAccess, DataFlow::CallNode {
LoadJsonFile() {
this = DataFlow::moduleImport("load-json-file").getACall()
or
this = DataFlow::moduleMember("load-json-file", "sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getADataNode() { result = trackRead(DataFlow::TypeTracker::end()) }
private DataFlow::SourceNode trackRead(DataFlow::TypeTracker t) {
this.getCalleeName() = "sync" and t.start() and result = this
or
not this.getCalleeName() = "sync" and t.startInPromise() and result = this
or
// Tracking out of a promise
exists(DataFlow::TypeTracker t2 |
result = PromiseTypeTracking::promiseStep(trackRead(t2), t, t2)
)
}
}
/**
* A call to the library `write-json-file`.
*/
private class WriteJsonFile extends FileSystemWriteAccess, DataFlow::CallNode {
WriteJsonFile() {
this = DataFlow::moduleImport("write-json-file").getACall()
or
this = DataFlow::moduleMember("write-json-file", "sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getADataNode() { result = getArgument(1) }
}
/**
* A call to the library `walkdir`.
*/
private class WalkDir extends FileNameProducer, FileSystemAccess, DataFlow::CallNode {
WalkDir() {
this = DataFlow::moduleImport("walkdir").getACall()
or
this = DataFlow::moduleMember("walkdir", "sync").getACall()
or
this = DataFlow::moduleMember("walkdir", "async").getACall()
}
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
override DataFlow::Node getAFileName() { result = trackFileSource(DataFlow::TypeTracker::end()) }
private DataFlow::SourceNode trackFileSource(DataFlow::TypeTracker t) {
not this.getCalleeName() = any(string s | s = "sync" or s = "async") and
t.start() and
(
result = getCallback(getNumArgument() - 1).getParameter(0)
or
result = getAMethodCall(EventEmitter::on()).getCallback(1).getParameter(0)
)
or
t.start() and this.getCalleeName() = "sync" and result = this
or
t.startInPromise() and this.getCalleeName() = "async" and result = this
or
// Tracking out of a promise
exists(DataFlow::TypeTracker t2 |
result = PromiseTypeTracking::promiseStep(trackFileSource(t2), t, t2)
)
}
}
/**
* A call to the library `globule`.
*/
private class Globule extends FileNameProducer, FileSystemAccess, DataFlow::CallNode {
Globule() {
this = DataFlow::moduleMember("globule", "find").getACall()
or
this = DataFlow::moduleMember("globule", "match").getACall()
or
this = DataFlow::moduleMember("globule", "isMatch").getACall()
or
this = DataFlow::moduleMember("globule", "mapping").getACall()
or
this = DataFlow::moduleMember("globule", "findMapping").getACall()
}
override DataFlow::Node getAPathArgument() {
(this.getCalleeName() = "match" or this.getCalleeName() = "isMatch") and
result = getArgument(1)
or
this.getCalleeName() = "mapping" and
(
result = getAnArgument() and not exists(result.getALocalSource().getAPropertyWrite("src"))
or
result = getAnArgument().getALocalSource().getAPropertyWrite("src").getRhs()
)
}
override DataFlow::Node getAFileName() {
result = this and
(
this.getCalleeName() = "find" or
this.getCalleeName() = "match" or
this.getCalleeName() = "findMapping" or
this.getCalleeName() = "mapping"
)
}
}
/**
* A file system access made by a NodeJS library.
* This class models multiple NodeJS libraries that access files.
@@ -257,6 +398,10 @@ private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
or
this = DataFlow::moduleImport("rimraf").getACall()
or
this = DataFlow::moduleImport("readdirp").getACall()
or
this = DataFlow::moduleImport("walker").getACall()
or
this =
DataFlow::moduleMember("node-dir",
any(string s |

View File

@@ -132,15 +132,22 @@ private module Postgres {
result = DataFlow::moduleImport("pg-pool").getAnInstantiation()
}
private DataFlow::SourceNode clientOrPool(DataFlow::TypeTracker t) {
t.start() and
(result = client() or result = newPool())
or
exists(DataFlow::TypeTracker t2 | result = clientOrPool(t2).track(t2, t))
}
private DataFlow::SourceNode clientOrPool() {
result = clientOrPool(DataFlow::TypeTracker::end())
}
/** A call to the Postgres `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
override MethodCallExpr astNode;
QueryCall() {
exists(DataFlow::SourceNode recv | recv = client() or recv = newPool() |
this = recv.getAMethodCall("query")
)
}
QueryCall() { this = clientOrPool().getAMethodCall("query") }
override DataFlow::Node getAQueryArgument() {
result = DataFlow::valueNode(astNode.getArgument(0))

View File

@@ -1,5 +1,5 @@
/**
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [ws](https://github.com/websockets/ws).
* Provides classes for working with [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), [ws](https://github.com/websockets/ws), and [SockJS](http://sockjs.org).
*
* The model is based on the EventEmitter model, and there is therefore a
* data-flow step from where a WebSocket event is sent to where the message
@@ -18,26 +18,64 @@ import javascript
*/
private string channelName() { result = "message" }
/**
* The names of the libraries modelled in this file.
*/
private module LibraryNames {
string sockjs() { result = "SockJS" }
string websocket() { result = "WebSocket" }
string ws() { result = "ws" }
class LibraryName extends string {
LibraryName() { this = sockjs() or this = websocket() or this = ws() }
}
}
/**
* Holds if the websocket library named `client` can send a message to the library named `server`.
* Both `client` and `server` are library names defined in `LibraryNames`.
*/
private predicate areLibrariesCompatible(
LibraryNames::LibraryName client, LibraryNames::LibraryName server
) {
// sockjs is a WebSocket emulating library, but not actually an implementation of WebSockets.
client = LibraryNames::sockjs() and server = LibraryNames::sockjs()
or
server = LibraryNames::ws() and
(client = LibraryNames::ws() or client = LibraryNames::websocket())
}
/**
* Provides classes that model WebSockets clients.
*/
module ClientWebSocket {
private import LibraryNames
/**
* A class that can be used to instantiate a WebSocket instance.
*/
class SocketClass extends DataFlow::SourceNode {
boolean isNode;
LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
SocketClass() {
this = DataFlow::globalVarRef("WebSocket") and isNode = false
this = DataFlow::globalVarRef("WebSocket") and library = websocket()
or
this = DataFlow::moduleImport("ws") and isNode = true
this = DataFlow::moduleImport("ws") and library = ws()
or
// the sockjs-client library:https://www.npmjs.com/package/sockjs-client
library = sockjs() and
(
this = DataFlow::moduleImport("sockjs-client") or
this = DataFlow::globalVarRef("SockJS")
)
}
/**
* Holds if this class is an import of the "ws" module.
* Gets the WebSocket library name.
*/
predicate isNode() { isNode = true }
LibraryName getLibrary() { result = library }
}
/**
@@ -49,11 +87,9 @@ module ClientWebSocket {
ClientSocket() { this = socketClass.getAnInstantiation() }
/**
* Holds if this ClientSocket is created from the "ws" module.
*
* The predicate is used to differentiate where the behavior of the "ws" module differs from the native WebSocket in browsers.
* Gets the WebSocket library name.
*/
predicate isNode() { socketClass.isNode() }
LibraryName getLibrary() { result = socketClass.getLibrary() }
}
/**
@@ -68,7 +104,10 @@ module ClientWebSocket {
override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(0) }
override ServerWebSocket::ReceiveNode getAReceiver() { any() }
override ServerWebSocket::ReceiveNode getAReceiver() {
areLibrariesCompatible(emitter.getLibrary(),
result.getEmitter().(ServerWebSocket::ServerSocket).getLibrary())
}
}
/**
@@ -116,7 +155,7 @@ module ClientWebSocket {
*/
private class WSReceiveNode extends ClientWebSocket::ReceiveNode {
WSReceiveNode() {
emitter.isNode() and
emitter.getLibrary() = ws() and
this = getAMessageHandler(emitter, EventEmitter::on())
}
@@ -128,21 +167,38 @@ module ClientWebSocket {
* Provides classes that model WebSocket servers.
*/
module ServerWebSocket {
private import LibraryNames
/**
* Gets a server created by a library named `library`.
*/
DataFlow::SourceNode getAServer(LibraryName library) {
library = ws() and
result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server")
or
library = sockjs() and
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
}
/**
* A server WebSocket instance.
*/
class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode {
LibraryName library;
ServerSocket() {
exists(DataFlow::CallNode onCall |
onCall =
DataFlow::moduleImport("ws")
.getAConstructorInvocation("Server")
.getAMemberCall(EventEmitter::on()) and
onCall = getAServer(library).getAMemberCall(EventEmitter::on()) and
onCall.getArgument(0).mayHaveStringValue("connection")
|
this = onCall.getCallback(1).getParameter(0)
)
}
/**
* Gets the name of the library that created this server socket.
*/
LibraryName getLibrary() { result = library }
}
/**
@@ -151,7 +207,13 @@ module ServerWebSocket {
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
override ServerSocket emitter;
SendNode() { this = emitter.getAMemberCall("send") }
SendNode() {
emitter.getLibrary() = ws() and
this = emitter.getAMemberCall("send")
or
emitter.getLibrary() = sockjs() and
this = emitter.getAMemberCall("write")
}
override string getChannel() { result = channelName() }
@@ -160,7 +222,10 @@ module ServerWebSocket {
result = getArgument(0)
}
override ClientWebSocket::ReceiveNode getAReceiver() { any() }
override ClientWebSocket::ReceiveNode getAReceiver() {
areLibrariesCompatible(result.getEmitter().(ClientWebSocket::ClientSocket).getLibrary(),
emitter.getLibrary())
}
}
/**
@@ -170,8 +235,14 @@ module ServerWebSocket {
override ServerSocket emitter;
ReceiveNode() {
this = emitter.getAMemberCall(EventEmitter::on()) and
this.getArgument(0).mayHaveStringValue("message")
exists(string eventName |
emitter.getLibrary() = ws() and eventName = "message"
or
emitter.getLibrary() = sockjs() and eventName = "data"
|
this = emitter.getAMemberCall(EventEmitter::on()) and
this.getArgument(0).mayHaveStringValue(eventName)
)
}
override string getChannel() { result = channelName() }