mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge branch 'main' into shellTrue
This commit is contained in:
@@ -87,11 +87,6 @@ private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
|
||||
or
|
||||
// re-using the collection steps for `Set`.
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
|
||||
)
|
||||
or
|
||||
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
|
||||
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
|
||||
not exists(BinaryExpr binop | binop = result.asExpr() |
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision low
|
||||
* @id js/actions/pull_request_target
|
||||
* @id js/actions/pull-request-target
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-094
|
||||
|
||||
@@ -102,6 +102,7 @@ import semmle.javascript.frameworks.Next
|
||||
import semmle.javascript.frameworks.NoSQL
|
||||
import semmle.javascript.frameworks.PkgCloud
|
||||
import semmle.javascript.frameworks.PropertyProjection
|
||||
import semmle.javascript.frameworks.Puppeteer
|
||||
import semmle.javascript.frameworks.React
|
||||
import semmle.javascript.frameworks.ReactNative
|
||||
import semmle.javascript.frameworks.Request
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
import javascript
|
||||
|
||||
from DataFlow::InvokeNode invoke, Function f
|
||||
where invoke.getACallee() = f
|
||||
select invoke, "Call to $@", f, f.describe()
|
||||
from DataFlow::Node invoke, Function f, string kind
|
||||
where
|
||||
invoke.(DataFlow::InvokeNode).getACallee() = f and kind = "Call"
|
||||
or
|
||||
invoke.(DataFlow::PropRef).getAnAccessorCallee().getFunction() = f and kind = "Accessor call"
|
||||
select invoke, kind + " to $@", f, f.describe()
|
||||
|
||||
@@ -15,17 +15,17 @@ predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
(
|
||||
TaintTracking::sharedTaintStep(pred, succ)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ)
|
||||
DataFlow::SharedFlowStep::step(pred, succ)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ, _, _)
|
||||
DataFlow::SharedFlowStep::step(pred, succ, _, _)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).loadStep(pred, succ, _)
|
||||
DataFlow::SharedFlowStep::loadStep(pred, succ, _)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).storeStep(pred, succ, _)
|
||||
DataFlow::SharedFlowStep::storeStep(pred, succ, _)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _, _)
|
||||
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _, _)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _)
|
||||
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _)
|
||||
) and
|
||||
not pred.getFile() instanceof IgnoredFile and
|
||||
not succ.getFile() instanceof IgnoredFile
|
||||
|
||||
@@ -313,10 +313,7 @@ module API {
|
||||
module Node {
|
||||
/** Gets a node whose type has the given qualified name. */
|
||||
Node ofType(string moduleName, string exportedName) {
|
||||
exists(TypeName tn |
|
||||
tn.hasQualifiedName(moduleName, exportedName) and
|
||||
result = Impl::MkCanonicalNameUse(tn).(Node).getInstance()
|
||||
)
|
||||
result = Impl::MkHasUnderlyingType(moduleName, exportedName).(Node).getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +381,8 @@ module API {
|
||||
imports(_, m)
|
||||
or
|
||||
m = any(CanonicalName n | isUsed(n)).getExternalModuleName()
|
||||
or
|
||||
any(TypeAnnotation n).hasQualifiedName(m, _)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
|
||||
MkAsyncFuncResult(DataFlow::FunctionNode f) {
|
||||
@@ -392,26 +391,13 @@ module API {
|
||||
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
|
||||
/**
|
||||
* A TypeScript canonical name that is defined somewhere, and that isn't a module root.
|
||||
* (Module roots are represented by `MkModuleExport` nodes instead.)
|
||||
*
|
||||
* For most purposes, you probably want to use the `mkCanonicalNameDef` predicate instead of
|
||||
* this constructor.
|
||||
* A TypeScript type, identified by name of the type-annotation.
|
||||
* This API node is exclusively used by `API::Node::ofType`.
|
||||
*/
|
||||
MkCanonicalNameDef(CanonicalName n) {
|
||||
not n.isRoot() and
|
||||
isDefined(n)
|
||||
} or
|
||||
/**
|
||||
* A TypeScript canonical name that is used somewhere, and that isn't a module root.
|
||||
* (Module roots are represented by `MkModuleImport` nodes instead.)
|
||||
*
|
||||
* For most purposes, you probably want to use the `mkCanonicalNameUse` predicate instead of
|
||||
* this constructor.
|
||||
*/
|
||||
MkCanonicalNameUse(CanonicalName n) {
|
||||
not n.isRoot() and
|
||||
isUsed(n)
|
||||
MkHasUnderlyingType(string moduleName, string exportName) {
|
||||
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
|
||||
or
|
||||
any(Type t).hasUnderlyingType(moduleName, exportName)
|
||||
} or
|
||||
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
|
||||
trackUseNode(src, true, bound).flowsTo(nd.getCalleeNode())
|
||||
@@ -420,10 +406,9 @@ module API {
|
||||
class TDef = MkModuleDef or TNonModuleDef;
|
||||
|
||||
class TNonModuleDef =
|
||||
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkCanonicalNameDef or
|
||||
MkSyntheticCallbackArg;
|
||||
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkSyntheticCallbackArg;
|
||||
|
||||
class TUse = MkModuleUse or MkModuleImport or MkUse or MkCanonicalNameUse;
|
||||
class TUse = MkModuleUse or MkModuleImport or MkUse or MkHasUnderlyingType;
|
||||
|
||||
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
|
||||
|
||||
@@ -460,20 +445,6 @@ module API {
|
||||
)
|
||||
}
|
||||
|
||||
/** An API-graph node representing definitions of the canonical name `cn`. */
|
||||
private TApiNode mkCanonicalNameDef(CanonicalName cn) {
|
||||
if cn.isModuleRoot()
|
||||
then result = MkModuleExport(cn.getExternalModuleName())
|
||||
else result = MkCanonicalNameDef(cn)
|
||||
}
|
||||
|
||||
/** An API-graph node representing uses of the canonical name `cn`. */
|
||||
private TApiNode mkCanonicalNameUse(CanonicalName cn) {
|
||||
if cn.isModuleRoot()
|
||||
then result = MkModuleImport(cn.getExternalModuleName())
|
||||
else result = MkCanonicalNameUse(cn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
|
||||
* incoming edge from `base` labeled `lbl` in the API graph.
|
||||
@@ -577,11 +548,6 @@ module API {
|
||||
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
|
||||
or
|
||||
nd = MkDef(rhs)
|
||||
or
|
||||
exists(CanonicalName n | nd = MkCanonicalNameDef(n) |
|
||||
rhs = n.(Namespace).getADefinition().flow() or
|
||||
rhs = n.(CanonicalFunctionName).getADefinition().flow()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,10 +599,10 @@ module API {
|
||||
ref = cls.getConstructor().getParameter(i)
|
||||
)
|
||||
or
|
||||
exists(TypeName tn |
|
||||
base = MkCanonicalNameUse(tn) and
|
||||
exists(string moduleName, string exportName |
|
||||
base = MkHasUnderlyingType(moduleName, exportName) and
|
||||
lbl = Label::instance() and
|
||||
ref = getANodeWithType(tn)
|
||||
ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::InvokeNode call |
|
||||
@@ -653,11 +619,11 @@ module API {
|
||||
cached
|
||||
predicate use(TApiNode nd, DataFlow::Node ref) {
|
||||
exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) |
|
||||
ref.(ModuleAsSourceNode).getModule() = mod
|
||||
ref.(ModuleVarNode).getModule() = mod
|
||||
)
|
||||
or
|
||||
exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) |
|
||||
ref.(ExportsAsSourceNode).getModule() = mod
|
||||
ref.(ExportsVarNode).getModule() = mod
|
||||
or
|
||||
exists(DataFlow::Node base | use(MkModuleDef(m), base) |
|
||||
ref = trackUseNode(base).getAPropertyRead("exports")
|
||||
@@ -676,8 +642,6 @@ module API {
|
||||
)
|
||||
or
|
||||
nd = MkUse(ref)
|
||||
or
|
||||
exists(CanonicalName n | nd = MkCanonicalNameUse(n) | ref.asExpr() = n.getAnAccess())
|
||||
}
|
||||
|
||||
/** Holds if module `m` exports `rhs`. */
|
||||
@@ -782,9 +746,9 @@ module API {
|
||||
or
|
||||
// additional backwards step from `require('m')` to `exports` or `module.exports` in m
|
||||
exists(Import imp | imp.getImportedModuleNode() = trackDefNode(nd, t.continue()) |
|
||||
result.(ExportsAsSourceNode).getModule() = imp.getImportedModule()
|
||||
result.(ExportsVarNode).getModule() = imp.getImportedModule()
|
||||
or
|
||||
exists(ModuleAsSourceNode mod |
|
||||
exists(ModuleVarNode mod |
|
||||
mod.getModule() = imp.getImportedModule() and
|
||||
result = mod.(DataFlow::SourceNode).getAPropertyRead("exports")
|
||||
)
|
||||
@@ -832,13 +796,6 @@ module API {
|
||||
result = awaited(call, DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getANodeWithType(TypeName tn) {
|
||||
exists(string moduleName, string typeName |
|
||||
tn.hasQualifiedName(moduleName, typeName) and
|
||||
result.hasUnderlyingType(moduleName, typeName)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||
*/
|
||||
@@ -879,11 +836,10 @@ module API {
|
||||
succ = MkClassInstance(trackDefNode(def))
|
||||
)
|
||||
or
|
||||
exists(CanonicalName cn1, string n, CanonicalName cn2 |
|
||||
pred in [mkCanonicalNameDef(cn1), mkCanonicalNameUse(cn1)] and
|
||||
cn2 = cn1.getChild(n) and
|
||||
lbl = Label::member(n) and
|
||||
succ in [mkCanonicalNameDef(cn2), mkCanonicalNameUse(cn2)]
|
||||
exists(string moduleName, string exportName |
|
||||
pred = MkModuleImport(moduleName) and
|
||||
lbl = Label::member(exportName) and
|
||||
succ = MkHasUnderlyingType(moduleName, exportName)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
|
||||
@@ -1027,31 +983,44 @@ private module Label {
|
||||
string promised() { result = "promised" }
|
||||
}
|
||||
|
||||
private class NodeModuleSourcesNodes extends DataFlow::SourceNode::Range {
|
||||
Variable v;
|
||||
|
||||
NodeModuleSourcesNodes() {
|
||||
exists(NodeModule m |
|
||||
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(v)) and
|
||||
v = [m.getModuleVariable(), m.getExportsVariable()]
|
||||
)
|
||||
}
|
||||
|
||||
Variable getVariable() { result = v }
|
||||
}
|
||||
|
||||
/**
|
||||
* A CommonJS/AMD `module` variable, considered as a source node.
|
||||
* A CommonJS/AMD `module` variable.
|
||||
*/
|
||||
private class ModuleAsSourceNode extends DataFlow::SourceNode::Range {
|
||||
private class ModuleVarNode extends DataFlow::Node {
|
||||
Module m;
|
||||
|
||||
ModuleAsSourceNode() {
|
||||
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getModuleVariable()))
|
||||
ModuleVarNode() {
|
||||
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getModuleVariable()
|
||||
or
|
||||
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getModuleParameter())
|
||||
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getModuleParameter())
|
||||
}
|
||||
|
||||
Module getModule() { result = m }
|
||||
}
|
||||
|
||||
/**
|
||||
* A CommonJS/AMD `exports` variable, considered as a source node.
|
||||
* A CommonJS/AMD `exports` variable.
|
||||
*/
|
||||
private class ExportsAsSourceNode extends DataFlow::SourceNode::Range {
|
||||
private class ExportsVarNode extends DataFlow::Node {
|
||||
Module m;
|
||||
|
||||
ExportsAsSourceNode() {
|
||||
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getExportsVariable()))
|
||||
ExportsVarNode() {
|
||||
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getExportsVariable()
|
||||
or
|
||||
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getExportsParameter())
|
||||
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getExportsParameter())
|
||||
}
|
||||
|
||||
Module getModule() { result = m }
|
||||
|
||||
@@ -84,16 +84,17 @@ private module ArrayDataFlow {
|
||||
* A step modelling the creation of an Array using the `Array.from(x)` method.
|
||||
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
|
||||
*/
|
||||
private class ArrayFrom extends DataFlow::AdditionalFlowStep, DataFlow::CallNode {
|
||||
ArrayFrom() { this = DataFlow::globalVarRef("Array").getAMemberCall("from") }
|
||||
|
||||
private class ArrayFrom extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = this.getArgument(0) and
|
||||
succ = this and
|
||||
fromProp = arrayLikeElement() and
|
||||
toProp = arrayElement()
|
||||
exists(DataFlow::CallNode call |
|
||||
call = DataFlow::globalVarRef("Array").getAMemberCall("from") and
|
||||
pred = call.getArgument(0) and
|
||||
succ = call and
|
||||
fromProp = arrayLikeElement() and
|
||||
toProp = arrayElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,55 +104,45 @@ private module ArrayDataFlow {
|
||||
*
|
||||
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
|
||||
*/
|
||||
private class ArrayCopySpread extends DataFlow::AdditionalFlowStep {
|
||||
DataFlow::Node spreadArgument; // the spread argument containing the elements to be copied.
|
||||
DataFlow::Node base; // the object where the elements should be copied to.
|
||||
|
||||
ArrayCopySpread() {
|
||||
exists(DataFlow::MethodCallNode mcn | mcn = this |
|
||||
mcn.getMethodName() = ["push", "unshift"] and
|
||||
spreadArgument = mcn.getASpreadArgument() and
|
||||
base = mcn.getReceiver().getALocalSource()
|
||||
)
|
||||
or
|
||||
spreadArgument = this.(DataFlow::ArrayCreationNode).getASpreadArgument() and
|
||||
base = this
|
||||
}
|
||||
|
||||
private class ArrayCopySpread extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = spreadArgument and
|
||||
succ = base and
|
||||
fromProp = arrayLikeElement() and
|
||||
toProp = arrayElement()
|
||||
toProp = arrayElement() and
|
||||
(
|
||||
exists(DataFlow::MethodCallNode mcn |
|
||||
mcn.getMethodName() = ["push", "unshift"] and
|
||||
pred = mcn.getASpreadArgument() and
|
||||
succ = mcn.getReceiver().getALocalSource()
|
||||
)
|
||||
or
|
||||
pred = succ.(DataFlow::ArrayCreationNode).getASpreadArgument()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
}
|
||||
|
||||
private class ArrayAppendStep extends DataFlow::SharedFlowStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
prop = arrayElement() and
|
||||
element = this.getAnArgument() and
|
||||
obj.getAMethodCall() = this
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["push", "unshift"] and
|
||||
element = call.getAnArgument() and
|
||||
obj.getAMethodCall() = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]}`.
|
||||
* A node that reads or writes an element from an array inside a for-loop.
|
||||
*/
|
||||
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
|
||||
private class ArrayIndexingAccess extends DataFlow::Node {
|
||||
DataFlow::PropRef read;
|
||||
|
||||
ArrayIndexingStep() {
|
||||
ArrayIndexingAccess() {
|
||||
read = this and
|
||||
TTNumber() =
|
||||
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
|
||||
@@ -162,17 +153,27 @@ private module ArrayDataFlow {
|
||||
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
prop = arrayElement() and
|
||||
obj = this.(DataFlow::PropRead).getBase() and
|
||||
element = this
|
||||
exists(ArrayIndexingAccess access |
|
||||
prop = arrayElement() and
|
||||
obj = access.(DataFlow::PropRead).getBase() and
|
||||
element = access
|
||||
)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
prop = arrayElement() and
|
||||
element = this.(DataFlow::PropWrite).getRhs() and
|
||||
this = obj.getAPropertyWrite()
|
||||
exists(ArrayIndexingAccess access |
|
||||
prop = arrayElement() and
|
||||
element = access.(DataFlow::PropWrite).getRhs() and
|
||||
access = obj.getAPropertyWrite()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,16 +181,14 @@ private module ArrayDataFlow {
|
||||
* 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"
|
||||
}
|
||||
|
||||
private class ArrayPopStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
prop = arrayElement() and
|
||||
obj = this.getReceiver() and
|
||||
element = this
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["pop", "shift"] and
|
||||
prop = arrayElement() and
|
||||
obj = call.getReceiver() and
|
||||
element = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +234,12 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for creating an array and storing the elements in the array.
|
||||
*/
|
||||
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::ArrayCreationNode {
|
||||
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(int i |
|
||||
element = this.getElement(i) and
|
||||
obj = this and
|
||||
if this = any(PromiseAllCreation c).getArrayNode()
|
||||
exists(DataFlow::ArrayCreationNode array, int i |
|
||||
element = array.getElement(i) and
|
||||
obj = array and
|
||||
if array = any(PromiseAllCreation c).getArrayNode()
|
||||
then prop = arrayElement(i)
|
||||
else prop = arrayElement()
|
||||
)
|
||||
@@ -251,13 +250,14 @@ private module ArrayDataFlow {
|
||||
* 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" }
|
||||
|
||||
private class ArraySpliceStep extends DataFlow::SharedFlowStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
prop = arrayElement() and
|
||||
element = getArgument(2) and
|
||||
this = obj.getAMethodCall()
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "splice" and
|
||||
prop = arrayElement() and
|
||||
element = call.getArgument(2) and
|
||||
call = obj.getAMethodCall()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,42 +265,27 @@ private module ArrayDataFlow {
|
||||
* 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" }
|
||||
|
||||
private class ArrayConcatStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = arrayElement() and
|
||||
(pred = this.getReceiver() or pred = this.getAnArgument()) and
|
||||
succ = this
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "concat" and
|
||||
prop = arrayElement() and
|
||||
(pred = call.getReceiver() or pred = call.getAnArgument()) and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
}
|
||||
|
||||
private class ArraySliceStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
prop = arrayElement() and
|
||||
pred = this.getReceiver() and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for modelling `for of` iteration on arrays.
|
||||
*/
|
||||
private class ForOfStep extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
|
||||
exists(ForOfStmt forOf |
|
||||
obj = forOf.getIterationDomain().flow() and
|
||||
e = DataFlow::lvalueNode(forOf.getLValue()) and
|
||||
prop = arrayElement()
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["slice", "splice", "filter"] and
|
||||
prop = arrayElement() and
|
||||
pred = call.getReceiver() and
|
||||
succ = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,32 +6,30 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import DataFlow::PseudoProperties
|
||||
|
||||
/**
|
||||
* A pseudo-property used in a data-flow/type-tracking step for collections.
|
||||
* DEPRECATED. Exists only to support other deprecated elements.
|
||||
*
|
||||
* By extending `TypeTrackingPseudoProperty` the class enables the use of the collection related pseudo-properties in type-tracking predicates.
|
||||
* Type-tracking now automatically determines the set of pseudo-properties to include
|
||||
* ased on which properties are contributed by `SharedTaintStep`s.
|
||||
*/
|
||||
private class PseudoProperty extends TypeTrackingPseudoProperty {
|
||||
deprecated private class PseudoProperty extends string {
|
||||
PseudoProperty() {
|
||||
this = [arrayLikeElement(), "1"] or // the "1" is required for the `ForOfStep`.
|
||||
this = any(CollectionDataFlow::MapSet step).getAPseudoProperty()
|
||||
}
|
||||
|
||||
override PseudoProperty getLoadStoreToProp() {
|
||||
exists(CollectionFlowStep step | step.loadStore(_, _, this, result))
|
||||
this =
|
||||
[
|
||||
mapValue(any(DataFlow::CallNode c | c.getCalleeName() = "set").getArgument(0)),
|
||||
mapValueAll()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `AdditionalFlowStep` used to model a data-flow step related to standard library collections.
|
||||
*
|
||||
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
|
||||
* `load`/`store`/`loadStore` can be used in the `CollectionsTypeTracking` module.
|
||||
* (Thereby avoiding naming conflicts with a "cousin" `AdditionalFlowStep` implementation.)
|
||||
* DEPRECATED. Use `SharedFlowStep` or `SharedTaintTrackingStep` instead.
|
||||
*/
|
||||
abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
abstract deprecated class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate step(
|
||||
@@ -84,27 +82,28 @@ abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides predicates and clases for type-tracking collections.
|
||||
* DEPRECATED. These steps are now included in the default type tracking steps,
|
||||
* in most cases one can simply use those instead.
|
||||
*/
|
||||
module CollectionsTypeTracking {
|
||||
deprecated module CollectionsTypeTracking {
|
||||
/**
|
||||
* Gets the result from a single step through a collection, from `pred` to `result` summarized by `summary`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::SourceNode collectionStep(DataFlow::Node pred, StepSummary summary) {
|
||||
exists(CollectionFlowStep step, PseudoProperty field |
|
||||
exists(PseudoProperty field |
|
||||
summary = LoadStep(field) and
|
||||
step.load(pred, result, field) and
|
||||
DataFlow::SharedTypeTrackingStep::loadStep(pred, result, field) and
|
||||
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
|
||||
or
|
||||
summary = StoreStep(field) and
|
||||
step.store(pred, result, field)
|
||||
DataFlow::SharedTypeTrackingStep::storeStep(pred, result, field)
|
||||
or
|
||||
summary = CopyStep(field) and
|
||||
step.loadStore(pred, result, field)
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field)
|
||||
or
|
||||
exists(PseudoProperty toField | summary = LoadStoreStep(field, toField) |
|
||||
step.loadStore(pred, result, field, toField)
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field, toField)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -129,74 +128,71 @@ private module CollectionDataFlow {
|
||||
/**
|
||||
* A step for `Set.add()` method, which adds an element to a Set.
|
||||
*/
|
||||
private class SetAdd extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
SetAdd() { this.getMethodName() = "add" }
|
||||
|
||||
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
|
||||
this = obj.getAMethodCall() and
|
||||
element = this.getArgument(0) and
|
||||
prop = setElement()
|
||||
private class SetAdd extends PreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = obj.getAMethodCall("add") and
|
||||
element = call.getArgument(0) and
|
||||
prop = setElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for the `Set` constructor, which copies any elements from the first argument into the resulting set.
|
||||
*/
|
||||
private class SetConstructor extends CollectionFlowStep, DataFlow::NewNode {
|
||||
SetConstructor() { this = DataFlow::globalVarRef("Set").getAnInstantiation() }
|
||||
|
||||
override predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
|
||||
private class SetConstructor extends PreCallGraphStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = this.getArgument(0) and
|
||||
succ = this and
|
||||
fromProp = arrayLikeElement() and
|
||||
toProp = setElement()
|
||||
exists(DataFlow::NewNode invoke |
|
||||
invoke = DataFlow::globalVarRef("Set").getAnInstantiation() and
|
||||
pred = invoke.getArgument(0) and
|
||||
succ = invoke and
|
||||
fromProp = arrayLikeElement() and
|
||||
toProp = setElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for a `for of` statement on a Map, Set, or Iterator.
|
||||
* For Sets and iterators the l-value are the elements of the set/iterator.
|
||||
* For Maps the l-value is a tuple containing a key and a value.
|
||||
* A step for modelling `for of` iteration on arrays, maps, sets, and iterators.
|
||||
*
|
||||
* For sets and iterators the l-value are the elements of the set/iterator.
|
||||
* For maps the l-value is a tuple containing a key and a value.
|
||||
*/
|
||||
// This is partially duplicated behavior with the `for of` step for Arrays (`ArrayDataFlow::ForOfStep`).
|
||||
// This duplication is required for the type-tracking steps defined in `CollectionsTypeTracking`.
|
||||
private class ForOfStep extends CollectionFlowStep, DataFlow::ValueNode {
|
||||
ForOfStmt forOf;
|
||||
DataFlow::Node element;
|
||||
|
||||
ForOfStep() {
|
||||
this.asExpr() = forOf.getIterationDomain() and
|
||||
element = DataFlow::lvalueNode(forOf.getLValue())
|
||||
private class ForOfStep extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
|
||||
exists(ForOfStmt forOf |
|
||||
obj = forOf.getIterationDomain().flow() and
|
||||
e = DataFlow::lvalueNode(forOf.getLValue()) and
|
||||
prop = arrayLikeElement()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate load(DataFlow::Node obj, DataFlow::Node e, PseudoProperty prop) {
|
||||
obj = this and
|
||||
e = element and
|
||||
prop = arrayLikeElement()
|
||||
}
|
||||
|
||||
override predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = this and
|
||||
succ = element and
|
||||
fromProp = mapValueAll() and
|
||||
toProp = "1"
|
||||
exists(ForOfStmt forOf |
|
||||
pred = forOf.getIterationDomain().flow() and
|
||||
succ = DataFlow::lvalueNode(forOf.getLValue()) and
|
||||
fromProp = mapValueAll() and
|
||||
toProp = "1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for a call to `forEach` on a Set or Map.
|
||||
*/
|
||||
private class SetMapForEach extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
SetMapForEach() { this.getMethodName() = "forEach" }
|
||||
|
||||
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
|
||||
obj = this.getReceiver() and
|
||||
element = this.getCallback(0).getParameter(0) and
|
||||
prop = [setElement(), mapValueAll()]
|
||||
private class SetMapForEach extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "forEach" and
|
||||
obj = call.getReceiver() and
|
||||
element = call.getCallback(0).getParameter(0) and
|
||||
prop = [setElement(), mapValueAll()]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,14 +200,15 @@ private module CollectionDataFlow {
|
||||
* A call to the `get` method on a Map.
|
||||
* If the key of the call to `get` has a known string value, then only the value corresponding to that key will be retrieved. (The known string value is encoded as part of the pseudo-property)
|
||||
*/
|
||||
private class MapGet extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
MapGet() { this.getMethodName() = "get" }
|
||||
|
||||
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
|
||||
obj = this.getReceiver() and
|
||||
element = this and
|
||||
// reading the join of known and unknown values
|
||||
(prop = mapValue(this.getArgument(0)) or prop = mapValueUnknownKey())
|
||||
private class MapGet extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "get" and
|
||||
obj = call.getReceiver() and
|
||||
element = call and
|
||||
// reading the join of known and unknown values
|
||||
(prop = mapValue(call.getArgument(0)) or prop = mapValueUnknownKey())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,56 +220,47 @@ private module CollectionDataFlow {
|
||||
* Otherwise the value will be stored into a pseudo-property corresponding to values with unknown keys.
|
||||
* The value will additionally be stored into a pseudo-property corresponding to all values.
|
||||
*/
|
||||
class MapSet extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
MapSet() { this.getMethodName() = "set" }
|
||||
|
||||
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
|
||||
this = obj.getAMethodCall() and
|
||||
element = this.getArgument(1) and
|
||||
prop = getAPseudoProperty()
|
||||
class MapSet extends PreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = obj.getAMethodCall("set") and
|
||||
element = call.getArgument(1) and
|
||||
prop = [mapValue(call.getArgument(0)), mapValueAll()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pseudo-property used to store an element in a map.
|
||||
* The pseudo-property represents both values where the key is a known string value (which is encoded in the pseudo-property),
|
||||
* and values where the key is unknown.
|
||||
*
|
||||
* Additionally, all elements are stored into the pseudo-property `mapValueAll()`.
|
||||
*
|
||||
* The return-type is `string` as this predicate is used to define which pseudo-properties exist.
|
||||
*/
|
||||
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueAll()] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for a call to `values` on a Map or a Set.
|
||||
*/
|
||||
private class MapAndSetValues extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
MapAndSetValues() { this.getMethodName() = "values" }
|
||||
|
||||
override predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
|
||||
private class MapAndSetValues extends PreCallGraphStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = this.getReceiver() and
|
||||
succ = this and
|
||||
fromProp = [mapValueAll(), setElement()] and
|
||||
toProp = iteratorElement()
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "values" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call and
|
||||
fromProp = [mapValueAll(), setElement()] and
|
||||
toProp = iteratorElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for a call to `keys` on a Set.
|
||||
*/
|
||||
private class SetKeys extends CollectionFlowStep, DataFlow::MethodCallNode {
|
||||
SetKeys() { this.getMethodName() = "keys" }
|
||||
|
||||
override predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
|
||||
private class SetKeys extends PreCallGraphStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
|
||||
) {
|
||||
pred = this.getReceiver() and
|
||||
succ = this and
|
||||
fromProp = setElement() and
|
||||
toProp = iteratorElement()
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "keys" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call and
|
||||
fromProp = setElement() and
|
||||
toProp = iteratorElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,11 @@ module DOM {
|
||||
)
|
||||
}
|
||||
|
||||
private InferredType getArgumentTypeFromJQueryMethodGet(JQuery::MethodCall call) {
|
||||
call.getMethodName() = "get" and
|
||||
result = call.getArgument(0).analyze().getAType()
|
||||
}
|
||||
|
||||
private class DefaultRange extends Range {
|
||||
DefaultRange() {
|
||||
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
|
||||
@@ -344,7 +349,7 @@ module DOM {
|
||||
or
|
||||
exists(JQuery::MethodCall call | this = call and call.getMethodName() = "get" |
|
||||
call.getNumArgument() = 1 and
|
||||
unique(InferredType t | t = call.getArgument(0).analyze().getAType()) = TTNumber()
|
||||
unique(InferredType t | t = getArgumentTypeFromJQueryMethodGet(call)) = TTNumber()
|
||||
)
|
||||
or
|
||||
// A `this` node from a callback given to a `$().each(callback)` call.
|
||||
|
||||
@@ -305,6 +305,7 @@ module AccessPath {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAReferenceTo(Root root, string path) {
|
||||
path = fromReference(result, root) and
|
||||
not root.isGlobal()
|
||||
@@ -327,6 +328,7 @@ module AccessPath {
|
||||
* })(NS = NS || {});
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAReferenceTo(string path) {
|
||||
path = fromReference(result, DataFlow::globalAccessPathRootPseudoNode())
|
||||
}
|
||||
@@ -347,6 +349,7 @@ module AccessPath {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAnAssignmentTo(Root root, string path) {
|
||||
path = fromRhs(result, root) and
|
||||
not root.isGlobal()
|
||||
@@ -367,6 +370,7 @@ module AccessPath {
|
||||
* })(foo = foo || {});
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAnAssignmentTo(string path) {
|
||||
path = fromRhs(result, DataFlow::globalAccessPathRootPseudoNode())
|
||||
}
|
||||
@@ -376,6 +380,7 @@ module AccessPath {
|
||||
*
|
||||
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAReferenceOrAssignmentTo(string path) {
|
||||
result = getAReferenceTo(path)
|
||||
or
|
||||
@@ -387,6 +392,7 @@ module AccessPath {
|
||||
*
|
||||
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAReferenceOrAssignmentTo(Root root, string path) {
|
||||
result = getAReferenceTo(root, path)
|
||||
or
|
||||
|
||||
@@ -16,51 +16,53 @@ abstract class HtmlSanitizerCall extends DataFlow::CallNode {
|
||||
abstract DataFlow::Node getInput();
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private DataFlow::SourceNode htmlSanitizerFunction() {
|
||||
result = DataFlow::moduleMember("ent", "encode")
|
||||
or
|
||||
result = DataFlow::moduleMember("entities", "encodeHTML")
|
||||
or
|
||||
result = DataFlow::moduleMember("entities", "encodeXML")
|
||||
or
|
||||
result = DataFlow::moduleMember("escape-goat", "escape")
|
||||
or
|
||||
result = DataFlow::moduleMember("he", "encode")
|
||||
or
|
||||
result = DataFlow::moduleMember("he", "escape")
|
||||
or
|
||||
result = DataFlow::moduleImport("sanitize-html")
|
||||
or
|
||||
result = DataFlow::moduleMember("sanitizer", "escape")
|
||||
or
|
||||
result = DataFlow::moduleMember("sanitizer", "sanitize")
|
||||
or
|
||||
result = DataFlow::moduleMember("validator", "escape")
|
||||
or
|
||||
result = DataFlow::moduleImport("xss")
|
||||
or
|
||||
result = DataFlow::moduleMember("xss-filters", _)
|
||||
or
|
||||
result = LodashUnderscore::member("escape")
|
||||
or
|
||||
exists(DataFlow::PropRead read | read = result |
|
||||
read.getPropertyName() = "sanitize" and
|
||||
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
|
||||
)
|
||||
or
|
||||
exists(string name | name = "encode" or name = "encodeNonUTF" |
|
||||
result = DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
|
||||
result = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
|
||||
)
|
||||
or
|
||||
result = Closure::moduleImport("goog.string.htmlEscape")
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches HTML sanitizers from known NPM packages as well as home-made sanitizers (matched by name).
|
||||
*/
|
||||
private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall {
|
||||
DefaultHtmlSanitizerCall() {
|
||||
exists(DataFlow::SourceNode callee | this = callee.getACall() |
|
||||
callee = DataFlow::moduleMember("ent", "encode")
|
||||
or
|
||||
callee = DataFlow::moduleMember("entities", "encodeHTML")
|
||||
or
|
||||
callee = DataFlow::moduleMember("entities", "encodeXML")
|
||||
or
|
||||
callee = DataFlow::moduleMember("escape-goat", "escape")
|
||||
or
|
||||
callee = DataFlow::moduleMember("he", "encode")
|
||||
or
|
||||
callee = DataFlow::moduleMember("he", "escape")
|
||||
or
|
||||
callee = DataFlow::moduleImport("sanitize-html")
|
||||
or
|
||||
callee = DataFlow::moduleMember("sanitizer", "escape")
|
||||
or
|
||||
callee = DataFlow::moduleMember("sanitizer", "sanitize")
|
||||
or
|
||||
callee = DataFlow::moduleMember("validator", "escape")
|
||||
or
|
||||
callee = DataFlow::moduleImport("xss")
|
||||
or
|
||||
callee = DataFlow::moduleMember("xss-filters", _)
|
||||
or
|
||||
callee = LodashUnderscore::member("escape")
|
||||
or
|
||||
exists(DataFlow::PropRead read | read = callee |
|
||||
read.getPropertyName() = "sanitize" and
|
||||
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
|
||||
)
|
||||
or
|
||||
exists(string name | name = "encode" or name = "encodeNonUTF" |
|
||||
callee =
|
||||
DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
|
||||
callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
|
||||
)
|
||||
or
|
||||
callee = Closure::moduleImport("goog.string.htmlEscape")
|
||||
)
|
||||
this = htmlSanitizerFunction().getACall()
|
||||
or
|
||||
// Match home-made sanitizers by name.
|
||||
exists(string calleeName | calleeName = getCalleeName() |
|
||||
|
||||
@@ -70,6 +70,58 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
result = cla.getAStaticMethod() or
|
||||
result = cla.getConstructor()
|
||||
)
|
||||
or
|
||||
// *****
|
||||
// Common styles of transforming exported objects.
|
||||
// *****
|
||||
//
|
||||
// Object.defineProperties
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call = DataFlow::globalVarRef("Object").getAMethodCall("defineProperties") and
|
||||
[call, call.getArgument(0)] = getAValueExportedByPackage() and
|
||||
result = call.getArgument(any(int i | i > 0))
|
||||
)
|
||||
or
|
||||
// Object.defineProperty
|
||||
exists(CallToObjectDefineProperty call |
|
||||
[call, call.getBaseObject()] = getAValueExportedByPackage()
|
||||
|
|
||||
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
|
||||
or
|
||||
result =
|
||||
call.getPropertyDescriptor()
|
||||
.getALocalSource()
|
||||
.getAPropertyReference("get")
|
||||
.(DataFlow::FunctionNode)
|
||||
.getAReturn()
|
||||
)
|
||||
or
|
||||
// Object.assign and friends
|
||||
exists(ExtendCall assign |
|
||||
getAValueExportedByPackage() = [assign, assign.getDestinationOperand()] and
|
||||
result = assign.getASourceOperand()
|
||||
)
|
||||
or
|
||||
// Array.prototype.{map, reduce, entries, values}
|
||||
exists(DataFlow::MethodCallNode map |
|
||||
map.getMethodName() = ["map", "reduce", "entries", "values"] and
|
||||
map = getAValueExportedByPackage()
|
||||
|
|
||||
result = map.getArgument(0).getABoundFunctionValue(_).getAReturn()
|
||||
or
|
||||
// assuming that the receiver of the call is somehow exported
|
||||
result = map.getReceiver()
|
||||
)
|
||||
or
|
||||
// Object.{fromEntries, freeze, seal, entries, values}
|
||||
exists(DataFlow::MethodCallNode freeze |
|
||||
freeze =
|
||||
DataFlow::globalVarRef("Object")
|
||||
.getAMethodCall(["fromEntries", "freeze", "seal", "entries", "values"])
|
||||
|
|
||||
freeze = getAValueExportedByPackage() and
|
||||
result = freeze.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,29 +141,7 @@ abstract class PathString extends string {
|
||||
* components of this path refers to when resolved relative to the
|
||||
* given `root` folder.
|
||||
*/
|
||||
Path resolveUpTo(int n, Folder root) {
|
||||
n = 0 and result.getContainer() = root and root = getARootFolder()
|
||||
or
|
||||
exists(Path base, string next | next = getComponent(this, n - 1, base, root) |
|
||||
// handle empty components and the special "." folder
|
||||
(next = "" or next = ".") and
|
||||
result = base
|
||||
or
|
||||
// handle the special ".." folder
|
||||
next = ".." and result = base.(ConsPath).getParent()
|
||||
or
|
||||
// special handling for Windows drive letters when resolving absolute path:
|
||||
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
|
||||
n = 1 and
|
||||
next.regexpMatch("[A-Za-z]:") and
|
||||
root.getBaseName() = "" and
|
||||
root.toString() = next.toUpperCase() + "/" and
|
||||
result = base
|
||||
or
|
||||
// default case
|
||||
result = TConsPath(base, next)
|
||||
)
|
||||
}
|
||||
Path resolveUpTo(int n, Folder root) { result = resolveUpTo(this, n, root, _) }
|
||||
|
||||
/**
|
||||
* Gets the absolute path that this path refers to when resolved relative to
|
||||
@@ -172,16 +150,57 @@ abstract class PathString extends string {
|
||||
Path resolve(Folder root) { result = resolveUpTo(getNumComponent(), root) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the absolute path that the sub-path consisting of the first `n`
|
||||
* components of this path refers to when resolved relative to the
|
||||
* given `root` folder.
|
||||
*/
|
||||
private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
n = 0 and result.getContainer() = root and root = p.getARootFolder() and inTS = false
|
||||
or
|
||||
exists(Path base, string next | next = getComponent(p, n - 1, base, root, inTS) |
|
||||
// handle empty components and the special "." folder
|
||||
(next = "" or next = ".") and
|
||||
result = base
|
||||
or
|
||||
// handle the special ".." folder
|
||||
next = ".." and result = base.(ConsPath).getParent()
|
||||
or
|
||||
// special handling for Windows drive letters when resolving absolute path:
|
||||
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
|
||||
n = 1 and
|
||||
next.regexpMatch("[A-Za-z]:") and
|
||||
root.getBaseName() = "" and
|
||||
root.toString() = next.toUpperCase() + "/" and
|
||||
result = base
|
||||
or
|
||||
// default case
|
||||
result = TConsPath(base, next)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Supports that the root directory might be compiled output from TypeScript.
|
||||
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
|
||||
*/
|
||||
private string getComponent(PathString str, int n, Path base, Folder root) {
|
||||
base = str.resolveUpTo(n, root) and
|
||||
(
|
||||
result = str.getComponent(n)
|
||||
or
|
||||
result = TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
|
||||
private string getComponent(PathString str, int n, Path base, Folder root, boolean inTS) {
|
||||
exists(boolean prevTS |
|
||||
base = resolveUpTo(str, n, root, prevTS) and
|
||||
(
|
||||
result = str.getComponent(n) and prevTS = inTS
|
||||
or
|
||||
// If we are in a TypeScript source folder, try to replace file endings with ".ts" or ".tsx"
|
||||
n = str.getNumComponent() - 1 and
|
||||
prevTS = true and
|
||||
inTS = prevTS and
|
||||
result = str.getComponent(n).regexpCapture("^(.*)\\.js$", 1) + "." + ["ts", "tsx"]
|
||||
or
|
||||
prevTS = false and
|
||||
inTS = true and
|
||||
result =
|
||||
TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -194,21 +213,35 @@ private module TypeScriptOutDir {
|
||||
*/
|
||||
string getOriginalTypeScriptFolder(string outdir, Folder parent) {
|
||||
exists(JSONObject tsconfig |
|
||||
tsconfig.getFile().getBaseName() = "tsconfig.json" and
|
||||
tsconfig.isTopLevel() and
|
||||
tsconfig.getFile().getParentContainer() = parent
|
||||
|
|
||||
outdir =
|
||||
tsconfig
|
||||
.getPropValue("compilerOptions")
|
||||
.(JSONObject)
|
||||
.getPropValue("outDir")
|
||||
.(JSONString)
|
||||
.getValue() and
|
||||
result = getEffectiveRootDirFromTSConfig(tsconfig)
|
||||
outdir = removeLeadingSlash(getOutDir(tsconfig, parent)) and
|
||||
result = removeLeadingSlash(getEffectiveRootDirFromTSConfig(tsconfig))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a leading dot and/or slash from `raw`.
|
||||
*/
|
||||
bindingset[raw]
|
||||
private string removeLeadingSlash(string raw) {
|
||||
result = raw.regexpCapture("^\\.?/?([\\w.\\-]+)$", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
|
||||
*/
|
||||
private string getOutDir(JSONObject tsconfig, Folder parent) {
|
||||
tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and
|
||||
tsconfig.isTopLevel() and
|
||||
tsconfig.getFile().getParentContainer() = parent and
|
||||
result =
|
||||
tsconfig
|
||||
.getPropValue("compilerOptions")
|
||||
.(JSONObject)
|
||||
.getPropValue("outDir")
|
||||
.(JSONString)
|
||||
.getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory that contains the TypeScript source files.
|
||||
* Based on the tsconfig.json file `tsconfig`.
|
||||
|
||||
@@ -214,13 +214,6 @@ module PromiseTypeTracking {
|
||||
result = PromiseTypeTracking::promiseStep(mid, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
|
||||
*/
|
||||
private class ResolveFieldAsTypeTrackingProperty extends TypeTrackingPseudoProperty {
|
||||
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
|
||||
}
|
||||
}
|
||||
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
@@ -596,6 +589,7 @@ private module ClosurePromise {
|
||||
* A promise created by a call `goog.Promise.resolve(value)`.
|
||||
*/
|
||||
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
|
||||
pragma[noinline]
|
||||
ResolvedClosurePromiseDefinition() {
|
||||
this = Closure::moduleImport("goog.Promise.resolve").getACall()
|
||||
}
|
||||
|
||||
@@ -74,23 +74,13 @@ private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInv
|
||||
* A flow step propagating the exception thrown from a callback to a method whose name coincides
|
||||
* a built-in Array iteration method, such as `forEach` or `map`.
|
||||
*/
|
||||
private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::AdditionalFlowStep {
|
||||
IteratorExceptionStep() {
|
||||
exists(string name | name = getMethodName() |
|
||||
name = "forEach" or
|
||||
name = "each" or
|
||||
name = "map" or
|
||||
name = "filter" or
|
||||
name = "some" or
|
||||
name = "every" or
|
||||
name = "fold" or
|
||||
name = "reduce"
|
||||
)
|
||||
}
|
||||
|
||||
private class IteratorExceptionStep extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
||||
succ = this.getExceptionalReturn()
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["forEach", "each", "map", "filter", "some", "every", "fold", "reduce"] and
|
||||
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
||||
succ = call.getExceptionalReturn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,12 +55,8 @@ module StringConcatenation {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
node = call and
|
||||
call.getMethodName() = "concat" and
|
||||
not (
|
||||
exists(DataFlow::ArrayCreationNode array |
|
||||
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
|
||||
)
|
||||
or
|
||||
DataFlow::reflectiveCallNode(_) = call
|
||||
not exists(DataFlow::ArrayCreationNode array |
|
||||
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
|
||||
) and
|
||||
(
|
||||
n = 0 and
|
||||
|
||||
@@ -544,6 +544,12 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Subclasses should extend `SharedFlowStep` instead, unless the subclass
|
||||
* is part of a query, in which case it should be moved into the `isAdditionalFlowStep` predicate
|
||||
* of the relevant data-flow configuration.
|
||||
* Other uses of the predicate in this class should instead reference the predicates in the
|
||||
* `SharedFlowStep::` module, such as `SharedFlowStep::step`.
|
||||
*
|
||||
* A data flow edge that should be added to all data flow configurations in
|
||||
* addition to standard data flow edges.
|
||||
*
|
||||
@@ -551,19 +557,19 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
|
||||
* of the standard library. Override `Configuration::isAdditionalFlowStep`
|
||||
* for analysis-specific flow steps.
|
||||
*/
|
||||
cached
|
||||
abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
deprecated class AdditionalFlowStep = LegacyAdditionalFlowStep;
|
||||
|
||||
// Internal version of AdditionalFlowStep that we can reference without deprecation warnings.
|
||||
abstract private class LegacyAdditionalFlowStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge
|
||||
* transforming values with label `predlbl` to have label `succlbl`.
|
||||
*/
|
||||
cached
|
||||
predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
@@ -577,7 +583,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
|
||||
*/
|
||||
cached
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
@@ -585,7 +590,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
*
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
@@ -593,7 +597,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
*
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
@@ -601,12 +604,10 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
|
||||
*
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
loadProp = storeProp and
|
||||
loadStoreStep(pred, succ, loadProp)
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,28 +637,133 @@ class SharedFlowStep extends Unit {
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
|
||||
*/
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contributes subclasses of `SharedFlowStep` to `AdditionalFlowStep`.
|
||||
*
|
||||
* This is a placeholder until we migrate to the `SharedFlowStep` class and deprecate `AdditionalFlowStep`.
|
||||
* Contains predicates for accessing the steps contributed by `SharedFlowStep` subclasses.
|
||||
*/
|
||||
private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
|
||||
SharedStepAsAdditionalFlowStep() {
|
||||
any(SharedFlowStep st).step(_, this) or
|
||||
any(SharedFlowStep st).step(_, this, _, _)
|
||||
cached
|
||||
module SharedFlowStep {
|
||||
cached
|
||||
private module Internal {
|
||||
// Forces this to be part of the `FlowSteps` stage.
|
||||
// We use a public predicate in a private module to avoid warnings about this being unused.
|
||||
cached
|
||||
predicate forceStage() { Stages::FlowSteps::ref() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedFlowStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge
|
||||
* transforming values with label `predlbl` to have label `succlbl`.
|
||||
*/
|
||||
cached
|
||||
predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
any(SharedFlowStep s).step(pred, succ, predlbl, succlbl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
|
||||
*/
|
||||
cached
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(SharedFlowStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(SharedFlowStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(SharedFlowStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
cached
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
any(SharedFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contributes subclasses of `AdditionalFlowStep` to `SharedFlowStep`.
|
||||
*/
|
||||
private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedFlowStep st).step(pred, succ) and succ = this
|
||||
any(LegacyAdditionalFlowStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
any(SharedFlowStep st).step(pred, succ, predlbl, succlbl) and succ = this
|
||||
any(LegacyAdditionalFlowStep s).step(pred, succ, predlbl, succlbl)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,7 +772,7 @@ private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
|
||||
*
|
||||
* A pseudo-property represents the location where some value is stored in an object.
|
||||
*
|
||||
* For use with load/store steps in `DataFlow::AdditionalFlowStep` and TypeTracking.
|
||||
* For use with load/store steps in `DataFlow::SharedFlowStep` and TypeTracking.
|
||||
*/
|
||||
module PseudoProperties {
|
||||
bindingset[s]
|
||||
@@ -782,13 +888,12 @@ abstract class AdditionalSink extends DataFlow::Node {
|
||||
* Additional flow step to model flow from import specifiers into the SSA variable
|
||||
* corresponding to the imported variable.
|
||||
*/
|
||||
private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueNode {
|
||||
override ImportSpecifier astNode;
|
||||
|
||||
private class FlowStepThroughImport extends SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
Stages::FlowSteps::ref() and
|
||||
pred = this and
|
||||
succ = DataFlow::ssaDefinitionNode(SSA::definition(astNode))
|
||||
exists(ImportSpecifier specifier |
|
||||
pred = DataFlow::valueNode(specifier) and
|
||||
succ = DataFlow::ssaDefinitionNode(SSA::definition(specifier))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,7 +1307,7 @@ private predicate reachesReturn(
|
||||
private predicate isAdditionalLoadStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
|
||||
) {
|
||||
any(AdditionalFlowStep s).loadStep(pred, succ, prop)
|
||||
SharedFlowStep::loadStep(pred, succ, prop)
|
||||
or
|
||||
cfg.isAdditionalLoadStep(pred, succ, prop)
|
||||
}
|
||||
@@ -1213,7 +1318,7 @@ private predicate isAdditionalLoadStep(
|
||||
private predicate isAdditionalStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
|
||||
) {
|
||||
any(AdditionalFlowStep s).storeStep(pred, succ, prop)
|
||||
SharedFlowStep::storeStep(pred, succ, prop)
|
||||
or
|
||||
cfg.isAdditionalStoreStep(pred, succ, prop)
|
||||
}
|
||||
@@ -1225,13 +1330,13 @@ private predicate isAdditionalLoadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp,
|
||||
DataFlow::Configuration cfg
|
||||
) {
|
||||
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
SharedFlowStep::loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
or
|
||||
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp, storeProp)
|
||||
or
|
||||
loadProp = storeProp and
|
||||
(
|
||||
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp)
|
||||
SharedFlowStep::loadStoreStep(pred, succ, loadProp)
|
||||
or
|
||||
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp)
|
||||
)
|
||||
@@ -1356,19 +1461,20 @@ private predicate summarizedHigherOrderCall(
|
||||
DataFlow::Node arg, DataFlow::Node cb, int i, DataFlow::Configuration cfg, PathSummary summary
|
||||
) {
|
||||
exists(
|
||||
Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
|
||||
DataFlow::Node innerArg, DataFlow::SourceNode cbParm, PathSummary oldSummary
|
||||
Function f, DataFlow::InvokeNode inner, int j, DataFlow::Node innerArg,
|
||||
DataFlow::SourceNode cbParm, PathSummary oldSummary
|
||||
|
|
||||
// Captured flow does not need to be summarized - it is handled by the local case in `higherOrderCall`.
|
||||
not arg = DataFlow::capturedVariableNode(_) and
|
||||
summarizedHigherOrderCallAux(f, outer, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb)
|
||||
not arg = DataFlow::capturedVariableNode(_)
|
||||
|
|
||||
// direct higher-order call
|
||||
cbParm.flowsTo(inner.getCalleeNode()) and
|
||||
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
|
||||
inner = cbParm.getAnInvocation() and
|
||||
i = j and
|
||||
summary = oldSummary
|
||||
or
|
||||
// indirect higher-order call
|
||||
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
|
||||
exists(DataFlow::Node cbArg, PathSummary newSummary |
|
||||
cbParm.flowsTo(cbArg) and
|
||||
summarizedHigherOrderCall(innerArg, cbArg, i, cfg, newSummary) and
|
||||
@@ -1382,14 +1488,17 @@ private predicate summarizedHigherOrderCall(
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate summarizedHigherOrderCallAux(
|
||||
Function f, DataFlow::InvokeNode outer, DataFlow::Node arg, DataFlow::Node innerArg,
|
||||
DataFlow::Configuration cfg, PathSummary oldSummary, DataFlow::SourceNode cbParm,
|
||||
DataFlow::InvokeNode inner, int j, DataFlow::Node cb
|
||||
Function f, DataFlow::Node arg, DataFlow::Node innerArg, DataFlow::Configuration cfg,
|
||||
PathSummary oldSummary, DataFlow::SourceNode cbParm, DataFlow::InvokeNode inner, int j,
|
||||
DataFlow::Node cb
|
||||
) {
|
||||
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
|
||||
// Only track actual parameter flow.
|
||||
argumentPassing(outer, cb, f, cbParm) and
|
||||
innerArg = inner.getArgument(j)
|
||||
exists(DataFlow::Node outer1, DataFlow::Node outer2 |
|
||||
reachableFromInput(f, outer1, arg, innerArg, cfg, oldSummary) and
|
||||
outer1 = pragma[only_bind_into](outer2) and
|
||||
// Only track actual parameter flow.
|
||||
argumentPassing(outer2, cb, f, cbParm) and
|
||||
innerArg = inner.getArgument(j)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -531,6 +531,13 @@ module DataFlow {
|
||||
predicate isPrivateField() {
|
||||
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessor (`get` or `set` method) that may be invoked by this property reference.
|
||||
*/
|
||||
final DataFlow::FunctionNode getAnAccessorCallee() {
|
||||
result = CallGraph::getAnAccessorCallee(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,18 +59,16 @@ class ParameterNode extends DataFlow::SourceNode {
|
||||
* ```
|
||||
*/
|
||||
class InvokeNode extends DataFlow::SourceNode {
|
||||
DataFlow::Impl::InvokeNodeDef impl;
|
||||
|
||||
InvokeNode() { this = impl }
|
||||
InvokeNode() { this instanceof DataFlow::Impl::InvokeNodeDef }
|
||||
|
||||
/** Gets the syntactic invoke expression underlying this function invocation. */
|
||||
InvokeExpr getInvokeExpr() { result = impl.getInvokeExpr() }
|
||||
InvokeExpr getInvokeExpr() { result = this.(DataFlow::Impl::InvokeNodeDef).getInvokeExpr() }
|
||||
|
||||
/** Gets the name of the function or method being invoked, if it can be determined. */
|
||||
string getCalleeName() { result = impl.getCalleeName() }
|
||||
string getCalleeName() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeName() }
|
||||
|
||||
/** Gets the data flow node specifying the function to be called. */
|
||||
DataFlow::Node getCalleeNode() { result = impl.getCalleeNode() }
|
||||
DataFlow::Node getCalleeNode() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeNode() }
|
||||
|
||||
/**
|
||||
* Gets the data flow node corresponding to the `i`th argument of this invocation.
|
||||
@@ -91,10 +89,10 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* but the position of `z` cannot be determined, hence there are no first and second
|
||||
* argument nodes.
|
||||
*/
|
||||
DataFlow::Node getArgument(int i) { result = impl.getArgument(i) }
|
||||
DataFlow::Node getArgument(int i) { result = this.(DataFlow::Impl::InvokeNodeDef).getArgument(i) }
|
||||
|
||||
/** Gets the data flow node corresponding to an argument of this invocation. */
|
||||
DataFlow::Node getAnArgument() { result = impl.getAnArgument() }
|
||||
DataFlow::Node getAnArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getAnArgument() }
|
||||
|
||||
/** Gets the data flow node corresponding to the last argument of this invocation. */
|
||||
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
|
||||
@@ -111,10 +109,12 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* ```
|
||||
* .
|
||||
*/
|
||||
DataFlow::Node getASpreadArgument() { result = impl.getASpreadArgument() }
|
||||
DataFlow::Node getASpreadArgument() {
|
||||
result = this.(DataFlow::Impl::InvokeNodeDef).getASpreadArgument()
|
||||
}
|
||||
|
||||
/** Gets the number of arguments of this invocation, if it can be determined. */
|
||||
int getNumArgument() { result = impl.getNumArgument() }
|
||||
int getNumArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getNumArgument() }
|
||||
|
||||
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
|
||||
|
||||
@@ -256,14 +256,14 @@ class InvokeNode extends DataFlow::SourceNode {
|
||||
* ```
|
||||
*/
|
||||
class CallNode extends InvokeNode {
|
||||
override DataFlow::Impl::CallNodeDef impl;
|
||||
CallNode() { this instanceof DataFlow::Impl::CallNodeDef }
|
||||
|
||||
/**
|
||||
* Gets the data flow node corresponding to the receiver expression of this method call.
|
||||
*
|
||||
* For example, the receiver of `x.m()` is `x`.
|
||||
*/
|
||||
DataFlow::Node getReceiver() { result = impl.getReceiver() }
|
||||
DataFlow::Node getReceiver() { result = this.(DataFlow::Impl::CallNodeDef).getReceiver() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,10 +277,10 @@ class CallNode extends InvokeNode {
|
||||
* ```
|
||||
*/
|
||||
class MethodCallNode extends CallNode {
|
||||
override DataFlow::Impl::MethodCallNodeDef impl;
|
||||
MethodCallNode() { this instanceof DataFlow::Impl::MethodCallNodeDef }
|
||||
|
||||
/** Gets the name of the invoked method, if it can be determined. */
|
||||
string getMethodName() { result = impl.getMethodName() }
|
||||
string getMethodName() { result = this.(DataFlow::Impl::MethodCallNodeDef).getMethodName() }
|
||||
|
||||
/**
|
||||
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
|
||||
@@ -300,7 +300,7 @@ class MethodCallNode extends CallNode {
|
||||
* ```
|
||||
*/
|
||||
class NewNode extends InvokeNode {
|
||||
override DataFlow::Impl::NewNodeDef impl;
|
||||
NewNode() { this instanceof DataFlow::Impl::NewNodeDef }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -546,6 +546,16 @@ class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
DataFlow::Node getASpreadProperty() {
|
||||
result = astNode.getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand().flow()
|
||||
}
|
||||
|
||||
/** Gets the property getter of the given name, installed on this object literal. */
|
||||
DataFlow::FunctionNode getPropertyGetter(string name) {
|
||||
result = astNode.getPropertyByName(name).(PropertyGetter).getInit().flow()
|
||||
}
|
||||
|
||||
/** Gets the property setter of the given name, installed on this object literal. */
|
||||
DataFlow::FunctionNode getPropertySetter(string name) {
|
||||
result = astNode.getPropertyByName(name).(PropertySetter).getInit().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -828,13 +828,13 @@ module TaintTracking {
|
||||
/**
|
||||
* A taint propagating data flow edge arising from URL parameter parsing.
|
||||
*/
|
||||
private class UrlSearchParamsTaintStep extends DataFlow::AdditionalFlowStep, DataFlow::ValueNode {
|
||||
private class UrlSearchParamsTaintStep extends DataFlow::SharedFlowStep {
|
||||
/**
|
||||
* Holds if `succ` is a `URLSearchParams` providing access to the
|
||||
* parameters encoded in `pred`.
|
||||
*/
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isUrlSearchParams(succ, pred) and succ = this
|
||||
isUrlSearchParams(succ, pred)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -847,17 +847,14 @@ module TaintTracking {
|
||||
* which can be accessed using a `get` or `getAll` call. (See getableUrlPseudoProperty())
|
||||
*/
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
succ = this and
|
||||
(
|
||||
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
|
||||
exists(DataFlow::NewNode newUrl | succ = newUrl |
|
||||
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
|
||||
pred = newUrl.getArgument(0)
|
||||
)
|
||||
or
|
||||
prop = getableUrlPseudoProperty() and
|
||||
isUrlSearchParams(succ, pred)
|
||||
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
|
||||
exists(DataFlow::NewNode newUrl | succ = newUrl |
|
||||
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
|
||||
pred = newUrl.getArgument(0)
|
||||
)
|
||||
or
|
||||
prop = getableUrlPseudoProperty() and
|
||||
isUrlSearchParams(succ, pred)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -869,7 +866,6 @@ module TaintTracking {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
succ = this and
|
||||
loadProp = hiddenUrlPseudoProperty() and
|
||||
storeProp = getableUrlPseudoProperty() and
|
||||
exists(DataFlow::PropRead read | read = succ |
|
||||
@@ -884,7 +880,6 @@ module TaintTracking {
|
||||
* This step is used to load the value stored in the pseudo-property `getableUrlPseudoProperty()`.
|
||||
*/
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
succ = this and
|
||||
prop = getableUrlPseudoProperty() and
|
||||
// this is a call to `get` or `getAll` on a `URLSearchParams` object
|
||||
exists(string m, DataFlow::MethodCallNode call | call = succ |
|
||||
|
||||
@@ -102,7 +102,7 @@ private module NodeTracking {
|
||||
predicate localFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = succ.getAPredecessor()
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep afs).step(pred, succ)
|
||||
DataFlow::SharedFlowStep::step(pred, succ)
|
||||
or
|
||||
localExceptionStep(pred, succ)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
private import javascript
|
||||
private import internal.FlowSteps
|
||||
private import internal.StepSummary
|
||||
private import internal.Unit
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalPropertyName prop)
|
||||
@@ -252,6 +253,12 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
*/
|
||||
predicate start() { hasReturn = false and prop = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type backtracking, and the value is in the property named `propName`.
|
||||
* The type tracking only ends after the property has been stored.
|
||||
*/
|
||||
predicate isInProp(PropertyName propName) { hasReturn = false and prop = propName }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
@@ -328,6 +335,89 @@ module TypeBackTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow edge that should be followed by type tracking.
|
||||
*
|
||||
* Unlike `SharedFlowStep`, this type of edge does not affect
|
||||
* the local data flow graph, and is not used by data-flow configurations.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. For query-specific steps, consider including the
|
||||
* custom steps in the type-tracking predicate itself.
|
||||
*/
|
||||
class SharedTypeTrackingStep extends Unit {
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ`.
|
||||
*/
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
|
||||
*/
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
|
||||
*/
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
|
||||
module SharedTypeTrackingStep {
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ`.
|
||||
*/
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(SharedTypeTrackingStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
|
||||
*/
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(SharedTypeTrackingStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
|
||||
*/
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(SharedTypeTrackingStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
|
||||
) {
|
||||
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `SharedTypeTrackingStep` instead.
|
||||
*
|
||||
* A data flow edge that should be followed by type tracking.
|
||||
*
|
||||
* Unlike `AdditionalFlowStep`, this type of edge does not affect
|
||||
@@ -337,7 +427,10 @@ module TypeBackTracker {
|
||||
* of the standard library. For query-specific steps, consider including the
|
||||
* custom steps in the type-tracking predicate itself.
|
||||
*/
|
||||
abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
|
||||
deprecated class AdditionalTypeTrackingStep = LegacyTypeTrackingStep;
|
||||
|
||||
// Internal version of AdditionalTypeTrackingStep that we can reference without deprecation warnings.
|
||||
abstract private class LegacyTypeTrackingStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ`.
|
||||
*/
|
||||
@@ -358,3 +451,21 @@ abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
}
|
||||
|
||||
private class LegacyStepAsSharedTypeTrackingStep extends SharedTypeTrackingStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(LegacyTypeTrackingStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,4 +162,46 @@ module CallGraph {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a property setter named `name` exists in a class. */
|
||||
private predicate isSetterName(string name) {
|
||||
exists(any(DataFlow::ClassNode cls).getInstanceMember(name, DataFlow::MemberKind::setter()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property write that assigns to the property `name` on an instance of this class,
|
||||
* and `name` is the name of a property setter.
|
||||
*/
|
||||
private DataFlow::PropWrite getAnInstanceMemberAssignment(DataFlow::ClassNode cls, string name) {
|
||||
isSetterName(name) and // restrict size of predicate
|
||||
result = cls.getAnInstanceReference().getAPropertyWrite(name)
|
||||
or
|
||||
exists(DataFlow::ClassNode subclass |
|
||||
result = getAnInstanceMemberAssignment(subclass, name) and
|
||||
not exists(subclass.getInstanceMember(name, DataFlow::MemberKind::setter())) and
|
||||
cls = subclass.getADirectSuperClass()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a getter or setter invoked as a result of the given property access.
|
||||
*/
|
||||
cached
|
||||
DataFlow::FunctionNode getAnAccessorCallee(DataFlow::PropRef ref) {
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
ref = cls.getAnInstanceMemberAccess(name) and
|
||||
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
|
||||
or
|
||||
ref = getAnInstanceMemberAssignment(cls, name) and
|
||||
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ObjectLiteralNode object, string name |
|
||||
ref = object.getAPropertyRead(name) and
|
||||
result = object.getPropertyGetter(name)
|
||||
or
|
||||
ref = object.getAPropertyWrite(name) and
|
||||
result = object.getPropertySetter(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ predicate shouldTrackProperties(AbstractValue obj) {
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate returnExpr(Function f, DataFlow::Node source, DataFlow::Node sink) {
|
||||
sink.asExpr() = f.getAReturnedExpr() and source = sink
|
||||
sink.asExpr() = f.getAReturnedExpr() and
|
||||
source = sink and
|
||||
not f = any(SetterMethodDeclaration decl).getBody()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,9 +41,9 @@ predicate localFlowStep(
|
||||
) {
|
||||
pred = succ.getAPredecessor() and predlbl = succlbl
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep afs).step(pred, succ) and predlbl = succlbl
|
||||
DataFlow::SharedFlowStep::step(pred, succ) and predlbl = succlbl
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep afs).step(pred, succ, predlbl, succlbl)
|
||||
DataFlow::SharedFlowStep::step(pred, succ, predlbl, succlbl)
|
||||
or
|
||||
exists(boolean vp | configuration.isAdditionalFlowStep(pred, succ, vp) |
|
||||
vp = true and
|
||||
@@ -120,7 +122,11 @@ private module CachedSteps {
|
||||
* Holds if `invk` may invoke `f`.
|
||||
*/
|
||||
cached
|
||||
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
|
||||
predicate calls(DataFlow::SourceNode invk, Function f) {
|
||||
f = invk.(DataFlow::InvokeNode).getACallee(0)
|
||||
or
|
||||
f = invk.(DataFlow::PropRef).getAnAccessorCallee().getFunction()
|
||||
}
|
||||
|
||||
private predicate callsBoundInternal(
|
||||
DataFlow::InvokeNode invk, Function f, int boundArgs, boolean contextDependent
|
||||
@@ -177,11 +183,11 @@ private module CachedSteps {
|
||||
*/
|
||||
cached
|
||||
predicate argumentPassing(
|
||||
DataFlow::InvokeNode invk, DataFlow::ValueNode arg, Function f, DataFlow::SourceNode parm
|
||||
DataFlow::SourceNode invk, DataFlow::Node arg, Function f, DataFlow::SourceNode parm
|
||||
) {
|
||||
calls(invk, f) and
|
||||
(
|
||||
exists(int i | arg = invk.getArgument(i) |
|
||||
exists(int i | arg = invk.(DataFlow::InvokeNode).getArgument(i) |
|
||||
exists(Parameter p |
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
@@ -193,12 +199,19 @@ private module CachedSteps {
|
||||
or
|
||||
arg = invk.(DataFlow::CallNode).getReceiver() and
|
||||
parm = DataFlow::thisNode(f)
|
||||
or
|
||||
arg = invk.(DataFlow::PropRef).getBase() and
|
||||
parm = DataFlow::thisNode(f)
|
||||
or
|
||||
arg = invk.(DataFlow::PropWrite).getRhs() and
|
||||
parm = DataFlow::parameterNode(f.getParameter(0))
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node callback, int i, Parameter p |
|
||||
exists(DataFlow::Node callback, int i, Parameter p, Function target |
|
||||
invk.(DataFlow::PartialInvokeNode).isPartialArgument(callback, arg, i) and
|
||||
partiallyCalls(invk, callback, f) and
|
||||
f.getParameter(i) = p and
|
||||
f = pragma[only_bind_into](target) and
|
||||
target.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
)
|
||||
@@ -213,7 +226,7 @@ private module CachedSteps {
|
||||
callsBound(invk, f, boundArgs) and
|
||||
f.getParameter(boundArgs + i) = p and
|
||||
not p.isRestParameter() and
|
||||
arg = invk.getArgument(i) and
|
||||
arg = invk.(DataFlow::InvokeNode).getArgument(i) and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ private class Unit extends TUnit {
|
||||
* Internal extension point for adding flow edges prior to call graph construction
|
||||
* and type tracking.
|
||||
*
|
||||
* Steps added here will be added to both `AdditionalFlowStep` and `AdditionalTypeTrackingStep`.
|
||||
* Steps added here will be added to both `SharedFlowStep` and `SharedTypeTrackingStep`.
|
||||
*
|
||||
* Contributing steps that rely on type tracking will lead to negative recursion.
|
||||
*/
|
||||
@@ -40,6 +40,15 @@ class PreCallGraphStep extends Unit {
|
||||
* Holds if there is a step from the `prop` property of `pred` to the same property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
module PreCallGraphStep {
|
||||
@@ -75,62 +84,61 @@ module PreCallGraphStep {
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(PreCallGraphStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
}
|
||||
|
||||
private class NodeWithPreCallGraphStep extends DataFlow::Node {
|
||||
NodeWithPreCallGraphStep() {
|
||||
PreCallGraphStep::step(this, _)
|
||||
or
|
||||
PreCallGraphStep::storeStep(this, _, _)
|
||||
or
|
||||
PreCallGraphStep::loadStep(this, _, _)
|
||||
or
|
||||
PreCallGraphStep::loadStoreStep(this, _, _)
|
||||
/**
|
||||
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
|
||||
) {
|
||||
any(PreCallGraphStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
private class AdditionalFlowStepFromPreCallGraph extends NodeWithPreCallGraphStep,
|
||||
DataFlow::AdditionalFlowStep {
|
||||
private class SharedFlowStepFromPreCallGraph extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
PreCallGraphStep::step(this, succ)
|
||||
PreCallGraphStep::step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::storeStep(this, succ, prop)
|
||||
PreCallGraphStep::storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::loadStep(this, succ, prop)
|
||||
PreCallGraphStep::loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::loadStoreStep(this, succ, prop)
|
||||
PreCallGraphStep::loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
private class AdditionalTypeTrackingStepFromPreCallGraph extends NodeWithPreCallGraphStep,
|
||||
DataFlow::AdditionalTypeTrackingStep {
|
||||
private class SharedTypeTrackingStepFromPreCallGraph extends DataFlow::SharedTypeTrackingStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = this and
|
||||
PreCallGraphStep::step(this, succ)
|
||||
PreCallGraphStep::step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::storeStep(this, succ, prop)
|
||||
PreCallGraphStep::storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::loadStep(this, succ, prop)
|
||||
PreCallGraphStep::loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
pred = this and
|
||||
PreCallGraphStep::loadStoreStep(this, succ, prop)
|
||||
PreCallGraphStep::loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
|
||||
) {
|
||||
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.TypeTracking
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import FlowSteps
|
||||
|
||||
class PropertyName extends string {
|
||||
PropertyName() {
|
||||
this = any(DataFlow::PropRef pr).getPropertyName()
|
||||
or
|
||||
AccessPath::isAssignedInUniqueFile(this)
|
||||
or
|
||||
exists(AccessPath::getAnAssignmentTo(_, this))
|
||||
or
|
||||
this instanceof TypeTrackingPseudoProperty
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
module Public {
|
||||
cached
|
||||
predicate forceStage() { Stages::TypeTracking::ref() }
|
||||
|
||||
cached
|
||||
class PropertyName extends string {
|
||||
cached
|
||||
PropertyName() {
|
||||
this = any(DataFlow::PropRef pr).getPropertyName()
|
||||
or
|
||||
AccessPath::isAssignedInUniqueFile(this)
|
||||
or
|
||||
exists(AccessPath::getAnAssignmentTo(_, this))
|
||||
or
|
||||
SharedTypeTrackingStep::loadStep(_, _, this)
|
||||
or
|
||||
SharedTypeTrackingStep::storeStep(_, _, this)
|
||||
or
|
||||
SharedTypeTrackingStep::loadStoreStep(_, _, this, _)
|
||||
or
|
||||
SharedTypeTrackingStep::loadStoreStep(_, _, _, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
cached
|
||||
newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(PropertyName prop) or
|
||||
LoadStep(PropertyName prop) or
|
||||
CopyStep(PropertyName prop) or
|
||||
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
|
||||
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | pred.flowsTo(mid) | StepSummary::smallstep(mid, succ, summary))
|
||||
}
|
||||
}
|
||||
|
||||
import Cached::Public
|
||||
|
||||
class OptionalPropertyName extends string {
|
||||
OptionalPropertyName() { this instanceof PropertyName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A pseudo-property that can be used in type-tracking.
|
||||
*/
|
||||
abstract class TypeTrackingPseudoProperty extends string {
|
||||
bindingset[this]
|
||||
TypeTrackingPseudoProperty() { any() }
|
||||
|
||||
/**
|
||||
* Gets a property name that `this` can be copied to in a `LoadStoreStep(this, result)`.
|
||||
*/
|
||||
string getLoadStoreToProp() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(PropertyName prop) or
|
||||
LoadStep(PropertyName prop) or
|
||||
CopyStep(PropertyName prop) or
|
||||
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
|
||||
exists(TypeTrackingPseudoProperty prop | fromProp = prop and toProp = prop.getLoadStoreToProp())
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
@@ -75,10 +91,7 @@ module StepSummary {
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
cached
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
|
||||
}
|
||||
predicate step = Cached::step/3;
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeBackTracker.smallstep()` instead.
|
||||
@@ -111,17 +124,22 @@ module StepSummary {
|
||||
basicLoadStep(pred, succ, prop) and
|
||||
summary = LoadStep(prop)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).storeStep(pred, succ, prop) and
|
||||
SharedTypeTrackingStep::storeStep(pred, succ, prop) and
|
||||
summary = StoreStep(prop)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).loadStep(pred, succ, prop) and
|
||||
SharedTypeTrackingStep::loadStep(pred, succ, prop) and
|
||||
summary = LoadStep(prop)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).loadStoreStep(pred, succ, prop) and
|
||||
SharedTypeTrackingStep::loadStoreStep(pred, succ, prop) and
|
||||
summary = CopyStep(prop)
|
||||
)
|
||||
or
|
||||
any(AdditionalTypeTrackingStep st).step(pred, succ) and
|
||||
exists(string fromProp, string toProp |
|
||||
SharedTypeTrackingStep::loadStoreStep(pred, succ, fromProp, toProp) and
|
||||
summary = LoadStoreStep(fromProp, toProp)
|
||||
)
|
||||
or
|
||||
SharedTypeTrackingStep::step(pred, succ) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
// Store to global access path
|
||||
|
||||
@@ -194,26 +194,22 @@ module EventDispatch {
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-step that models data-flow between event handlers and event dispatchers.
|
||||
* A flow-step that models data-flow between event handlers and event dispatchers.
|
||||
*/
|
||||
private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep {
|
||||
EventRegistration reg;
|
||||
EventDispatch dispatch;
|
||||
|
||||
EventEmitterTaintStep() {
|
||||
this = dispatch and
|
||||
reg = dispatch.getAReceiver() and
|
||||
not dispatch.getChannel() != reg.getChannel()
|
||||
}
|
||||
|
||||
private class EventEmitterFlowStep extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(int i | i >= 0 |
|
||||
pred = dispatch.getSentItem(i) and
|
||||
succ = reg.getReceivedItem(i)
|
||||
exists(EventRegistration reg, EventDispatch dispatch |
|
||||
reg = dispatch.getAReceiver() and
|
||||
not dispatch.getChannel() != reg.getChannel()
|
||||
|
|
||||
exists(int i | i >= 0 |
|
||||
pred = dispatch.getSentItem(i) and
|
||||
succ = reg.getReceivedItem(i)
|
||||
)
|
||||
or
|
||||
dispatch = reg.getAReturnDispatch() and
|
||||
pred = reg.getAReturnedValue() and
|
||||
succ = dispatch
|
||||
)
|
||||
or
|
||||
dispatch = reg.getAReturnDispatch() and
|
||||
pred = reg.getAReturnedValue() and
|
||||
succ = dispatch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javascript
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.CallGraphs
|
||||
private import DataFlow::PseudoProperties as PseudoProperties
|
||||
|
||||
module HTTP {
|
||||
/**
|
||||
@@ -689,33 +690,30 @@ module HTTP {
|
||||
isDecoratedCall(result, candidate)
|
||||
}
|
||||
|
||||
private string mapValueProp() {
|
||||
result = [PseudoProperties::mapValueAll(), PseudoProperties::mapValueUnknownKey()]
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection that contains one or more route potential handlers.
|
||||
*/
|
||||
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range {
|
||||
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range,
|
||||
DataFlow::NewNode {
|
||||
ContainerCollection() {
|
||||
this = DataFlow::globalVarRef("Map").getAnInstantiation() and // restrict to Map for now
|
||||
exists(
|
||||
CollectionFlowStep store, DataFlow::Node storeTo, DataFlow::Node input,
|
||||
RouteHandlerCandidate candidate
|
||||
|
|
||||
this.flowsTo(storeTo) and
|
||||
store.store(input, storeTo, _) and
|
||||
candidate.flowsTo(input)
|
||||
exists(DataFlow::Node use |
|
||||
DataFlow::SharedTypeTrackingStep::storeStep(use, this, mapValueProp()) and
|
||||
use.getALocalSource() instanceof RouteHandlerCandidate
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
|
||||
result instanceof RouteHandlerCandidate and
|
||||
exists(
|
||||
DataFlow::Node input, TypeTrackingPseudoProperty key, CollectionFlowStep store,
|
||||
CollectionFlowStep load, DataFlow::Node storeTo, DataFlow::Node loadFrom
|
||||
|
|
||||
this.flowsTo(storeTo) and
|
||||
store.store(input, storeTo, key) and
|
||||
exists(DataFlow::Node input, string key, DataFlow::Node loadFrom |
|
||||
getAPossiblyDecoratedHandler(result).flowsTo(input) and
|
||||
DataFlow::SharedTypeTrackingStep::storeStep(input, this, key) and
|
||||
ref(this).flowsTo(loadFrom) and
|
||||
load.load(loadFrom, access, key)
|
||||
DataFlow::SharedTypeTrackingStep::loadStep(loadFrom, access,
|
||||
[key, PseudoProperties::mapValueAll()])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,22 +164,15 @@ private module Immutable {
|
||||
/**
|
||||
* A dataflow step for an immutable collection.
|
||||
*/
|
||||
class ImmutableConstructionStep extends DataFlow::AdditionalFlowStep {
|
||||
ImmutableConstructionStep() { this = [loadStep(_, _), storeStep(_, _), step(_)] }
|
||||
|
||||
class ImmutableConstructionStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this = loadStep(pred, prop) and
|
||||
succ = this
|
||||
succ = loadStep(pred, prop)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
this = storeStep(pred, prop) and
|
||||
succ = this
|
||||
succ = storeStep(pred, prop)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
this = step(pred) and
|
||||
succ = this
|
||||
}
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = step(pred) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,18 +36,11 @@ module Koa {
|
||||
/**
|
||||
* A Koa route handler.
|
||||
*/
|
||||
class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
|
||||
Function function;
|
||||
|
||||
RouteHandler() {
|
||||
function = astNode and
|
||||
any(RouteSetup setup).getARouteHandler() = this
|
||||
}
|
||||
|
||||
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::SourceNode {
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the context object.
|
||||
*/
|
||||
Parameter getContextParameter() { result = function.getParameter(0) }
|
||||
Parameter getContextParameter() { result = getAFunctionValue().getFunction().getParameter(0) }
|
||||
|
||||
/**
|
||||
* Gets an expression that contains the "context" object of
|
||||
@@ -70,6 +63,35 @@ module Koa {
|
||||
* object of a route handler invocation.
|
||||
*/
|
||||
Expr getARequestOrContextExpr() { result = getARequestExpr() or result = getAContextExpr() }
|
||||
|
||||
/**
|
||||
* Gets a reference to a request parameter defined by this route handler.
|
||||
*/
|
||||
DataFlow::Node getARequestParameterAccess() {
|
||||
none() // overriden in subclasses.
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dataflow node that can be given to a `RouteSetup` to register the handler.
|
||||
*/
|
||||
abstract DataFlow::SourceNode getARouteHandlerRegistrationObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* A koa route handler registered directly with a route-setup.
|
||||
* Like:
|
||||
* ```JavaScript
|
||||
* var route = require('koa-route');
|
||||
* var app = new Koa();
|
||||
* app.use((context, next) => {
|
||||
* ...
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
private class StandardRouteHandler extends RouteHandler {
|
||||
StandardRouteHandler() { any(RouteSetup setup).getARouteHandler() = this }
|
||||
|
||||
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +122,77 @@ module Koa {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Koa route handler registered using a routing library.
|
||||
*
|
||||
* Example of what that could look like:
|
||||
* ```JavaScript
|
||||
* const router = require('koa-router')();
|
||||
* const Koa = require('koa');
|
||||
* const app = new Koa();
|
||||
* router.get('/', async (ctx, next) => {
|
||||
* // route handler stuff
|
||||
* });
|
||||
* app.use(router.routes());
|
||||
* ```
|
||||
*/
|
||||
private class RoutedRouteHandler extends RouteHandler {
|
||||
DataFlow::InvokeNode router;
|
||||
DataFlow::MethodCallNode call;
|
||||
|
||||
RoutedRouteHandler() {
|
||||
router = DataFlow::moduleImport(["@koa/router", "koa-router"]).getAnInvocation() and
|
||||
call =
|
||||
router
|
||||
.getAChainedMethodCall([
|
||||
"use", "get", "post", "put", "link", "unlink", "delete", "del", "head", "options",
|
||||
"patch", "all"
|
||||
]) and
|
||||
this.flowsTo(call.getArgument(any(int i | i >= 1)))
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandlerRegistrationObject() {
|
||||
result = call
|
||||
or
|
||||
result = router.getAMethodCall("routes")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A route handler registered using the `koa-route` library.
|
||||
*
|
||||
* Example of how `koa-route` can be used:
|
||||
* ```JavaScript
|
||||
* var route = require('koa-route');
|
||||
* var Koa = require('koa');
|
||||
* var app = new Koa();
|
||||
*
|
||||
* app.use(route.get('/pets', (context, param1, param2, param3, ...params) => {
|
||||
* // route handler stuff
|
||||
* }));
|
||||
*/
|
||||
class KoaRouteHandler extends RouteHandler {
|
||||
DataFlow::CallNode call;
|
||||
|
||||
KoaRouteHandler() {
|
||||
call =
|
||||
DataFlow::moduleMember("koa-route",
|
||||
[
|
||||
"all", "acl", "bind", "checkout", "connect", "copy", "delete", "del", "get", "head",
|
||||
"link", "lock", "msearch", "merge", "mkactivity", "mkcalendar", "mkcol", "move",
|
||||
"notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "rebind",
|
||||
"report", "search", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"
|
||||
]).getACall() and
|
||||
this.flowsTo(call.getArgument(1))
|
||||
}
|
||||
|
||||
override DataFlow::Node getARequestParameterAccess() {
|
||||
result = call.getABoundCallbackParameter(1, any(int i | i >= 1))
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = call }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Koa request source, that is, an access to the `request` property
|
||||
* of a context object.
|
||||
@@ -189,6 +282,9 @@ module Koa {
|
||||
kind = "parameter" and
|
||||
this = getAQueryParameterAccess(rh)
|
||||
or
|
||||
kind = "parameter" and
|
||||
this = rh.getARequestParameterAccess()
|
||||
or
|
||||
exists(Expr e | rh.getARequestOrContextExpr() = e |
|
||||
// `ctx.request.url`, `ctx.request.originalUrl`, or `ctx.request.href`
|
||||
exists(string propName |
|
||||
@@ -202,6 +298,10 @@ module Koa {
|
||||
propName = "href"
|
||||
)
|
||||
or
|
||||
// params, when handler is registered by `koa-router` or similar.
|
||||
kind = "parameter" and
|
||||
this.asExpr().(PropAccess).accesses(e, "params")
|
||||
or
|
||||
// `ctx.request.body`
|
||||
e instanceof RequestExpr and
|
||||
kind = "body" and
|
||||
@@ -285,7 +385,13 @@ module Koa {
|
||||
getMethodName() = "use"
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() { result.flowsToExpr(getArgument(0)) }
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
// `StandardRouteHandler` uses this predicate in it's charpred, so making this predicate return a `RouteHandler` would give an empty recursion.
|
||||
result.flowsToExpr(getArgument(0))
|
||||
or
|
||||
// For the route-handlers that does not depend on this predicate in their charpred.
|
||||
result.(RouteHandler).getARouteHandlerRegistrationObject().flowsToExpr(getArgument(0))
|
||||
}
|
||||
|
||||
override Expr getServer() { result = server }
|
||||
}
|
||||
|
||||
@@ -360,9 +360,9 @@ module LodashUnderscore {
|
||||
/**
|
||||
* A data flow step propagating an exception thrown from a callback to a Lodash/Underscore function.
|
||||
*/
|
||||
private class ExceptionStep extends DataFlow::CallNode, DataFlow::AdditionalFlowStep {
|
||||
ExceptionStep() {
|
||||
exists(string name | this = member(name).getACall() |
|
||||
private class ExceptionStep extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call, string name |
|
||||
// Members ending with By, With, or While indicate that they are a variant of
|
||||
// another function that takes a callback.
|
||||
name.matches("%By") or
|
||||
@@ -386,13 +386,12 @@ module LodashUnderscore {
|
||||
name = "replace" or
|
||||
name = "some" or
|
||||
name = "transform"
|
||||
|
|
||||
call = member(name).getACall() and
|
||||
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
||||
succ = call.getExceptionalReturn()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
||||
succ = this.getExceptionalReturn()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,37 +126,28 @@ module NextJS {
|
||||
/**
|
||||
* A step modelling the flow from the server-computed props object to the default exported function that renders the page.
|
||||
*/
|
||||
class NextJSStaticPropsStep extends DataFlow::AdditionalFlowStep, DataFlow::FunctionNode {
|
||||
Module pageModule;
|
||||
|
||||
NextJSStaticPropsStep() {
|
||||
pageModule = getAPagesModule() and
|
||||
this = pageModule.getAnExportedValue("default").getAFunctionValue()
|
||||
}
|
||||
|
||||
class NextJSStaticPropsStep extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getAPropsSource(pageModule) and
|
||||
succ = this.getParameter(0)
|
||||
exists(Module pageModule, DataFlow::FunctionNode function |
|
||||
pageModule = getAPagesModule() and
|
||||
function = pageModule.getAnExportedValue("default").getAFunctionValue() and
|
||||
pred = getAPropsSource(pageModule) and
|
||||
succ = function.getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step modelling the flow from the server-computed props object to the default exported React component that renders the page.
|
||||
*/
|
||||
class NextJSStaticReactComponentPropsStep extends DataFlow::AdditionalFlowStep,
|
||||
DataFlow::ValueNode {
|
||||
Module pageModule;
|
||||
ReactComponent component;
|
||||
|
||||
NextJSStaticReactComponentPropsStep() {
|
||||
pageModule = getAPagesModule() and
|
||||
this.getAstNode() = component and
|
||||
this = pageModule.getAnExportedValue("default").getALocalSource()
|
||||
}
|
||||
|
||||
class NextJSStaticReactComponentPropsStep extends DataFlow::SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = getAPropsSource(pageModule) and
|
||||
succ = component.getADirectPropsAccess()
|
||||
exists(Module pageModule, ReactComponent component |
|
||||
pageModule = getAPagesModule() and
|
||||
pageModule.getAnExportedValue("default").getALocalSource() = DataFlow::valueNode(component) and
|
||||
pred = getAPropsSource(pageModule) and
|
||||
succ = component.getADirectPropsAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal file
95
javascript/ql/src/semmle/javascript/frameworks/Puppeteer.qll
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about [puppeteer](https://www.npmjs.com/package/puppeteer).
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates modelling the [puppeteer](https://www.npmjs.com/package/puppeteer) library.
|
||||
*/
|
||||
module Puppeteer {
|
||||
/**
|
||||
* A reference to a module import of puppeteer.
|
||||
*/
|
||||
private API::Node puppeteer() { result = API::moduleImport(["puppeteer", "puppeteer-core"]) }
|
||||
|
||||
/**
|
||||
* A reference to a `Browser` from puppeteer.
|
||||
*/
|
||||
private API::Node browser() {
|
||||
result = API::Node::ofType("puppeteer", "Browser")
|
||||
or
|
||||
result = puppeteer().getMember(["launch", "connect"]).getReturn().getPromised()
|
||||
or
|
||||
result = [page(), context(), target()].getMember("browser").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `Page` from puppeteer.
|
||||
*/
|
||||
API::Node page() {
|
||||
result = API::Node::ofType("puppeteer", "Page")
|
||||
or
|
||||
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
|
||||
or
|
||||
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
|
||||
or
|
||||
result = target().getMember("page").getReturn().getPromised()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `Target` from puppeteer.
|
||||
*/
|
||||
private API::Node target() {
|
||||
result = API::Node::ofType("puppeteer", "Target")
|
||||
or
|
||||
result = [page(), browser()].getMember("target").getReturn()
|
||||
or
|
||||
result = context().getMember("targets").getReturn().getUnknownMember()
|
||||
or
|
||||
result = target().getMember("opener").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `BrowserContext` from puppeteer.
|
||||
*/
|
||||
private API::Node context() {
|
||||
result = API::Node::ofType("puppeteer", "BrowserContext")
|
||||
or
|
||||
result = [page(), target()].getMember("browserContext").getReturn()
|
||||
or
|
||||
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
|
||||
or
|
||||
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
|
||||
or
|
||||
result = browser().getMember("defaultBrowserContext").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A call requesting a `Page` to navigate to some url, seen as a `ClientRequest`.
|
||||
*/
|
||||
private class PuppeteerGotoCall extends ClientRequest::Range, API::InvokeNode {
|
||||
PuppeteerGotoCall() { this = page().getMember("goto").getACall() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call requesting a `Page` to load a stylesheet or script, seen as a `ClientRequest`.
|
||||
*/
|
||||
private class PuppeteerLoadResourceCall extends ClientRequest::Range, API::InvokeNode {
|
||||
PuppeteerLoadResourceCall() {
|
||||
this = page().getMember(["addStyleTag", "addScriptTag"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").getARhs() }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
}
|
||||
}
|
||||
@@ -268,9 +268,11 @@ module SocketIO {
|
||||
|
||||
/** Gets the `i`th parameter through which data is received from a client. */
|
||||
override DataFlow::SourceNode getReceivedItem(int i) {
|
||||
exists(DataFlow::FunctionNode cb | cb = getListener() and result = cb.getParameter(i) |
|
||||
exists(DataFlow::FunctionNode cb |
|
||||
cb = getListener() and
|
||||
result = cb.getParameter(i) and
|
||||
// exclude last parameter if it looks like a callback
|
||||
result != cb.getLastParameter() or not exists(result.getAnInvocation())
|
||||
not (result = cb.getLastParameter() and exists(result.getAnInvocation()))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,15 @@ private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell)
|
||||
)
|
||||
or
|
||||
shell = true and
|
||||
mod = "exec" and
|
||||
optionsArg = -2 and
|
||||
cmdArg = 0
|
||||
(
|
||||
mod = "exec" and
|
||||
optionsArg = -2 and
|
||||
cmdArg = 0
|
||||
or
|
||||
mod = "async-execute" and
|
||||
optionsArg = 1 and
|
||||
cmdArg = 0
|
||||
)
|
||||
}
|
||||
|
||||
private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode {
|
||||
|
||||
@@ -219,7 +219,7 @@ module Stages {
|
||||
or
|
||||
AccessPath::DominatingPaths::hasDominatingWrite(_)
|
||||
or
|
||||
any(DataFlow::AdditionalFlowStep s).step(_, _)
|
||||
DataFlow::SharedFlowStep::step(_, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,12 @@ private module PersistentWebStorage {
|
||||
result = DataFlow::globalVarRef(kind)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
WriteAccess getAWriteByName(string name, string kind) {
|
||||
result.getKey() = name and
|
||||
result.getKind() = kind
|
||||
}
|
||||
|
||||
/**
|
||||
* A read access.
|
||||
*/
|
||||
@@ -165,8 +171,10 @@ private module PersistentWebStorage {
|
||||
ReadAccess() { this = webStorage(kind).getAMethodCall("getItem") }
|
||||
|
||||
override PersistentWriteAccess getAWrite() {
|
||||
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey()) and
|
||||
result.(WriteAccess).getKind() = kind
|
||||
exists(string name |
|
||||
getArgument(0).mayHaveStringValue(name) and
|
||||
result = getAWriteByName(name, kind)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,15 +255,12 @@ module ExternalAPIUsedWithUntrustedData {
|
||||
not exists(DataFlow::Node arg |
|
||||
arg = this.getAnArgument() and not arg instanceof DeepObjectSink
|
||||
|
|
||||
TaintTracking::sharedTaintStep(arg, _)
|
||||
or
|
||||
exists(DataFlow::AdditionalFlowStep s |
|
||||
s.step(arg, _) or
|
||||
s.step(arg, _, _, _) or
|
||||
s.loadStep(arg, _, _) or
|
||||
s.storeStep(arg, _, _) or
|
||||
s.loadStoreStep(arg, _, _)
|
||||
)
|
||||
TaintTracking::sharedTaintStep(arg, _) or
|
||||
DataFlow::SharedFlowStep::step(arg, _) or
|
||||
DataFlow::SharedFlowStep::step(arg, _, _, _) or
|
||||
DataFlow::SharedFlowStep::loadStep(arg, _, _) or
|
||||
DataFlow::SharedFlowStep::storeStep(arg, _, _) or
|
||||
DataFlow::SharedFlowStep::loadStoreStep(arg, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,10 +100,10 @@ module PrototypePollutingAssignment {
|
||||
// users wouldn't bother to call Object.create in that case.
|
||||
result = DataFlow::globalVarRef("Object").getAMemberCall("create")
|
||||
or
|
||||
// Allow use of AdditionalFlowSteps to track a bit further
|
||||
// Allow use of SharedFlowSteps to track a bit further
|
||||
exists(DataFlow::Node mid |
|
||||
prototypeLessObject(t.continue()).flowsTo(mid) and
|
||||
any(DataFlow::AdditionalFlowStep s).step(mid, result)
|
||||
DataFlow::SharedFlowStep::step(mid, result)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = prototypeLessObject(t2).track(t2, t))
|
||||
|
||||
@@ -636,6 +636,20 @@ module TaintedPath {
|
||||
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A path argument given to a `Page` in puppeteer, specifying where a pdf/screenshot should be saved.
|
||||
*/
|
||||
private class PuppeteerPath extends TaintedPath::Sink {
|
||||
PuppeteerPath() {
|
||||
this =
|
||||
Puppeteer::page()
|
||||
.getMember(["pdf", "screenshot"])
|
||||
.getParameter(0)
|
||||
.getMember("path")
|
||||
.getARhs()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user