mirror of
https://github.com/github/codeql.git
synced 2026-05-10 17:29:26 +02:00
Merge branch 'master' into js/membershiptest
This commit is contained in:
@@ -259,8 +259,7 @@ private class AmdDependencyImport extends Import {
|
||||
* Gets the module whose absolute path matches this import, if there is only a single such module.
|
||||
*/
|
||||
private Module resolveByAbsolutePath() {
|
||||
count(guessTarget()) = 1 and
|
||||
result.getFile() = guessTarget()
|
||||
result.getFile() = unique(File file | file = guessTarget())
|
||||
}
|
||||
|
||||
override Module getImportedModule() {
|
||||
@@ -291,7 +290,7 @@ private class AmdDependencyImport extends Import {
|
||||
*/
|
||||
class AmdModule extends Module {
|
||||
cached
|
||||
AmdModule() { strictcount(AmdModuleDefinition def | amdModuleTopLevel(def, this)) = 1 }
|
||||
AmdModule() { exists(unique(AmdModuleDefinition def | amdModuleTopLevel(def, this))) }
|
||||
|
||||
/** Gets the definition of this module. */
|
||||
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
|
||||
|
||||
@@ -447,9 +447,9 @@ class StmtContainer extends @stmt_container, ASTNode {
|
||||
*/
|
||||
module AST {
|
||||
/**
|
||||
* A program element that evaluates to a value at runtime. This includes expressions,
|
||||
* but also function and class declaration statements, as well as TypeScript
|
||||
* namespace and enum declarations.
|
||||
* A program element that evaluates to a value or destructures a value at runtime.
|
||||
* This includes expressions and destructuring patterns, but also function and
|
||||
* class declaration statements, as well as TypeScript namespace and enum declarations.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
|
||||
@@ -251,13 +251,15 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for creating an array and storing the elements in the array.
|
||||
*/
|
||||
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
|
||||
ArrayCreationStep() { this instanceof DataFlow::ArrayCreationNode }
|
||||
|
||||
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::ArrayCreationNode {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
prop = arrayElement() and
|
||||
element = this.(DataFlow::ArrayCreationNode).getAnElement() and
|
||||
obj = this
|
||||
exists(int i |
|
||||
element = this.getElement(i) and
|
||||
obj = this and
|
||||
if this = any(PromiseAllCreation c).getArrayNode()
|
||||
then prop = arrayElement(i)
|
||||
else prop = arrayElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -299,11 +299,19 @@ class ControlFlowNode extends @cfg_node, Locatable, NodeInStmtContainer {
|
||||
*/
|
||||
predicate isStart() { this = any(StmtContainer sc).getStart() }
|
||||
|
||||
/**
|
||||
* Holds if this is a final node of `container`, that is, a CFG node where execution
|
||||
* of that toplevel or function terminates.
|
||||
*/
|
||||
predicate isAFinalNodeOfContainer(StmtContainer container) {
|
||||
getASuccessor().(SyntheticControlFlowNode).isAFinalNodeOfContainer(container)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is a final node, that is, a CFG node where execution of a
|
||||
* toplevel or function terminates.
|
||||
*/
|
||||
predicate isAFinalNode() { getASuccessor().(SyntheticControlFlowNode).isAFinalNode() }
|
||||
final predicate isAFinalNode() { isAFinalNodeOfContainer(_) }
|
||||
|
||||
/**
|
||||
* Holds if this node is unreachable, that is, it has no predecessors in the CFG.
|
||||
@@ -361,7 +369,9 @@ class ControlFlowEntryNode extends SyntheticControlFlowNode, @entry_node {
|
||||
|
||||
/** A synthetic CFG node marking the exit of a function or toplevel script. */
|
||||
class ControlFlowExitNode extends SyntheticControlFlowNode, @exit_node {
|
||||
override predicate isAFinalNode() { any() }
|
||||
override predicate isAFinalNodeOfContainer(StmtContainer container) {
|
||||
exit_cfg_node(this, container)
|
||||
}
|
||||
|
||||
override string toString() { result = "exit node of " + getContainer().toString() }
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ private predicate defn(ControlFlowNode def, Expr lhs, AST::ValueNode rhs) {
|
||||
exists(EnumMember member | def = member.getIdentifier() |
|
||||
lhs = def and rhs = member.getInitializer()
|
||||
)
|
||||
or
|
||||
lhs = def and def.(Parameter).getDefault() = rhs
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,6 +108,7 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
|
||||
int getIntValue() { none() }
|
||||
|
||||
/** Gets the constant string value this expression evaluates to, if any. */
|
||||
cached
|
||||
string getStringValue() { none() }
|
||||
|
||||
/** Holds if this expression is impure, that is, its evaluation could have side effects. */
|
||||
|
||||
@@ -48,6 +48,11 @@ private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall {
|
||||
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
|
||||
|
||||
@@ -66,10 +66,8 @@ module InclusionTest {
|
||||
Function callee;
|
||||
|
||||
IndirectInclusionTest() {
|
||||
inner.getEnclosingExpr() = callee.getAReturnedExpr() and
|
||||
this.getACallee() = callee and
|
||||
count(this.getACallee()) = 1 and
|
||||
count(callee.getAReturnedExpr()) = 1 and
|
||||
inner.getEnclosingExpr() = unique(Expr ret | ret = callee.getAReturnedExpr()) and
|
||||
callee = unique(Function f | f = this.getACallee()) and
|
||||
not this.isImprecise() and
|
||||
inner.getContainedNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter()) and
|
||||
inner.getContainerNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter())
|
||||
|
||||
@@ -196,7 +196,7 @@ class JSXName extends Expr {
|
||||
)
|
||||
or
|
||||
exists(JSXQualifiedName qual | qual = this |
|
||||
result = qual.getNamespace() + ":" + qual.getName()
|
||||
result = qual.getNamespace().getName() + ":" + qual.getName().getName()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,18 @@ private class RequireVariable extends Variable {
|
||||
*/
|
||||
private predicate moduleInFile(Module m, File f) { m.getFile() = f }
|
||||
|
||||
/**
|
||||
* Holds if `nd` may refer to `require`, either directly or modulo local data flow.
|
||||
*/
|
||||
cached
|
||||
private predicate isRequire(DataFlow::Node nd) {
|
||||
nd.asExpr() = any(RequireVariable req).getAnAccess() and
|
||||
// `mjs` files explicitly disallow `require`
|
||||
not nd.getFile().getExtension() = "mjs"
|
||||
or
|
||||
isRequire(nd.getAPredecessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* A `require` import.
|
||||
*
|
||||
@@ -162,12 +174,7 @@ private predicate moduleInFile(Module m, File f) { m.getFile() = f }
|
||||
* ```
|
||||
*/
|
||||
class Require extends CallExpr, Import {
|
||||
cached
|
||||
Require() {
|
||||
any(RequireVariable req).getAnAccess() = getCallee() and
|
||||
// `mjs` files explicitly disallow `require`
|
||||
not getFile().getExtension() = "mjs"
|
||||
}
|
||||
Require() { isRequire(getCallee().flow()) }
|
||||
|
||||
override PathExpr getImportedPath() { result = getArgument(0) }
|
||||
|
||||
@@ -257,8 +264,8 @@ private class RequirePath extends PathExprCandidate {
|
||||
RequirePath() {
|
||||
this = any(Require req).getArgument(0)
|
||||
or
|
||||
exists(RequireVariable req, MethodCallExpr reqres |
|
||||
reqres.getReceiver() = req.getAnAccess() and
|
||||
exists(MethodCallExpr reqres |
|
||||
isRequire(reqres.getReceiver().flow()) and
|
||||
reqres.getMethodName() = "resolve" and
|
||||
this = reqres.getArgument(0)
|
||||
)
|
||||
|
||||
71
javascript/ql/src/semmle/javascript/PackageExports.qll
Normal file
71
javascript/ql/src/semmle/javascript/PackageExports.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Provides predicates for working with values exported from a package.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Gets the number of occurrences of "/" in `path`.
|
||||
*/
|
||||
bindingset[path]
|
||||
private int countSlashes(string path) { result = count(path.splitAt("/")) - 1 }
|
||||
|
||||
/**
|
||||
* Gets the topmost package.json that appears in the project.
|
||||
*
|
||||
* There can be multiple results if the there exists multiple package.json that are equally deeply nested in the folder structure.
|
||||
* Results are limited to package.json files that are at most nested 2 directories deep.
|
||||
*/
|
||||
PackageJSON getTopmostPackageJSON() {
|
||||
result =
|
||||
min(PackageJSON j |
|
||||
countSlashes(j.getFile().getRelativePath()) <= 3
|
||||
|
|
||||
j order by countSlashes(j.getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value exported by the main module from the package.json `packageJSON`.
|
||||
* The value is either directly the `module.exports` value, a nested property of `module.exports`, or a method on an exported class.
|
||||
*/
|
||||
DataFlow::Node getAValueExportedBy(PackageJSON packageJSON) {
|
||||
result = getAnExportFromModule(packageJSON.getMainModule())
|
||||
or
|
||||
result = getAValueExportedBy(packageJSON).(DataFlow::PropWrite).getRhs()
|
||||
or
|
||||
exists(DataFlow::SourceNode callee |
|
||||
callee = getAValueExportedBy(packageJSON).(DataFlow::NewNode).getCalleeNode().getALocalSource()
|
||||
|
|
||||
result = callee.getAPropertyRead("prototype").getAPropertyWrite()
|
||||
or
|
||||
result = callee.(DataFlow::ClassNode).getAnInstanceMethod()
|
||||
)
|
||||
or
|
||||
result = getAValueExportedBy(packageJSON).getALocalSource()
|
||||
or
|
||||
result = getAValueExportedBy(packageJSON).(DataFlow::SourceNode).getAPropertyReference()
|
||||
or
|
||||
exists(Module mod |
|
||||
mod = getAValueExportedBy(packageJSON).getEnclosingExpr().(Import).getImportedModule()
|
||||
|
|
||||
result = getAnExportFromModule(mod)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ClassNode cla | cla = getAValueExportedBy(packageJSON) |
|
||||
result = cla.getAnInstanceMethod() or
|
||||
result = cla.getAStaticMethod() or
|
||||
result = cla.getConstructor()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an exported node from the module `mod`.
|
||||
*/
|
||||
private DataFlow::Node getAnExportFromModule(Module mod) {
|
||||
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
|
||||
or
|
||||
exists(ASTNode export | result.getEnclosingExpr() = export | mod.exports(_, export))
|
||||
}
|
||||
@@ -85,7 +85,7 @@ private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNo
|
||||
*/
|
||||
abstract class PromiseCreationCall extends DataFlow::CallNode {
|
||||
/**
|
||||
* Gets the value this promise is resolved with.
|
||||
* Gets a value this promise is resolved with.
|
||||
*/
|
||||
abstract DataFlow::Node getValue();
|
||||
}
|
||||
@@ -95,6 +95,16 @@ abstract class PromiseCreationCall extends DataFlow::CallNode {
|
||||
*/
|
||||
abstract class ResolvedPromiseDefinition extends PromiseCreationCall { }
|
||||
|
||||
/**
|
||||
* A promise that is created using a `Promise.all(array)` call.
|
||||
*/
|
||||
abstract class PromiseAllCreation extends PromiseCreationCall {
|
||||
/**
|
||||
* Gets a node for the array of values given to the `Promise.all(array)` call.
|
||||
*/
|
||||
abstract DataFlow::Node getArrayNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function.
|
||||
*/
|
||||
@@ -121,6 +131,15 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An aggregated promise created using `Promise.all()`.
|
||||
*/
|
||||
class ES2015PromiseAllDefinition extends AggregateES2015PromiseDefinition, PromiseAllCreation {
|
||||
ES2015PromiseAllDefinition() { this.getCalleeName() = "all" }
|
||||
|
||||
override DataFlow::Node getArrayNode() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Common predicates shared between type-tracking and data-flow for promises.
|
||||
*/
|
||||
@@ -303,16 +322,27 @@ private module PromiseFlow {
|
||||
CreationStep() { this = promise }
|
||||
|
||||
override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
not promise instanceof PromiseAllCreation and
|
||||
prop = valueProp() and
|
||||
pred = promise.getValue() and
|
||||
succ = this
|
||||
or
|
||||
prop = valueProp() and
|
||||
pred = promise.(PromiseAllCreation).getArrayNode() and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
// Copy the value of a resolved promise to the value of this promise.
|
||||
not promise instanceof PromiseAllCreation and
|
||||
prop = valueProp() and
|
||||
pred = promise.getValue() and
|
||||
succ = this
|
||||
or
|
||||
promise instanceof PromiseAllCreation and
|
||||
prop = valueProp() and
|
||||
pred = promise.(PromiseAllCreation).getArrayNode() and
|
||||
succ = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +476,11 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
|
||||
or
|
||||
// from `x` to `Promise.resolve(x)`
|
||||
pred = succ.(PromiseCreationCall).getValue()
|
||||
pred = succ.(PromiseCreationCall).getValue() and
|
||||
not succ instanceof PromiseAllCreation
|
||||
or
|
||||
// from `arr` to `Promise.all(arr)`
|
||||
pred = succ.(PromiseAllCreation).getArrayNode()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" |
|
||||
// from `p` to `x` in `p.then(x => ...)`
|
||||
@@ -533,6 +567,15 @@ module Bluebird {
|
||||
result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise created using `Promise.all`:
|
||||
*/
|
||||
class BluebirdPromiseAllDefinition extends AggregateBluebirdPromiseDefinition, PromiseAllCreation {
|
||||
BluebirdPromiseAllDefinition() { this.getCalleeName() = "all" }
|
||||
|
||||
override DataFlow::Node getArrayNode() { result = getArgument(0) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,10 +64,8 @@ module StringOps {
|
||||
Function callee;
|
||||
|
||||
IndirectStartsWith() {
|
||||
inner.getEnclosingExpr() = callee.getAReturnedExpr() and
|
||||
this.getACallee() = callee and
|
||||
count(this.getACallee()) = 1 and
|
||||
count(callee.getAReturnedExpr()) = 1 and
|
||||
inner.getEnclosingExpr() = unique(Expr ret | ret = callee.getAReturnedExpr()) and
|
||||
callee = unique(Function f | f = this.getACallee()) and
|
||||
not this.isImprecise() and
|
||||
inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and
|
||||
inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter()
|
||||
@@ -295,10 +293,8 @@ module StringOps {
|
||||
Function callee;
|
||||
|
||||
IndirectEndsWith() {
|
||||
inner.getEnclosingExpr() = callee.getAReturnedExpr() and
|
||||
this.getACallee() = callee and
|
||||
count(this.getACallee()) = 1 and
|
||||
count(callee.getAReturnedExpr()) = 1 and
|
||||
inner.getEnclosingExpr() = unique(Expr ret | ret = callee.getAReturnedExpr()) and
|
||||
callee = unique(Function f | f = this.getACallee()) and
|
||||
not this.isImprecise() and
|
||||
inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and
|
||||
inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter()
|
||||
|
||||
@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = XMLParent.super.toString() }
|
||||
override string toString() { result = getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -438,7 +438,10 @@ private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node
|
||||
barrierGuardIsRelevant(guard) and
|
||||
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
|
||||
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
|
||||
guard.getEnclosingExpr() = cond.getTest() and
|
||||
(
|
||||
guard.getEnclosingExpr() = cond.getTest() or
|
||||
guard = cond.getTest().flow().getImmediatePredecessor+()
|
||||
) and
|
||||
outcome = cond.getOutcome() and
|
||||
barrierGuardBlocksAccessPath(guard, outcome, p, label) and
|
||||
cond.dominates(bb)
|
||||
@@ -607,6 +610,16 @@ module PseudoProperties {
|
||||
*/
|
||||
string arrayElement() { result = pseudoProperty("arrayElement") }
|
||||
|
||||
/**
|
||||
* Gets a pseudo-property for the location of the `i`th element in an `Array`.
|
||||
*/
|
||||
bindingset[i]
|
||||
string arrayElement(int i) {
|
||||
i < 5 and result = i.toString()
|
||||
or
|
||||
result = arrayElement()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pseudo-property for the location of elements in some array-like object. (Set, Array, or Iterator).
|
||||
*/
|
||||
@@ -1601,6 +1614,9 @@ class MidPathNode extends PathNode, MkMidNode {
|
||||
nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof
|
||||
SsaImplicitDefinition
|
||||
or
|
||||
// Skip SSA definition of parameter as its location coincides with the parameter node
|
||||
nd = DataFlow::ssaDefinitionNode(SSA::definition(any(SimpleParameter p)))
|
||||
or
|
||||
// Skip to the top of big left-leaning string concatenation trees.
|
||||
nd = any(AddExpr add).flow() and
|
||||
nd = any(AddExpr add).getAnOperand().flow()
|
||||
|
||||
@@ -21,32 +21,10 @@
|
||||
import javascript
|
||||
private import internal.CallGraphs
|
||||
private import internal.FlowSteps as FlowSteps
|
||||
private import internal.DataFlowNode
|
||||
private import internal.AnalyzedParameters
|
||||
|
||||
module DataFlow {
|
||||
cached
|
||||
private newtype TNode =
|
||||
TValueNode(AST::ValueNode nd) or
|
||||
TSsaDefNode(SsaDefinition d) or
|
||||
TCapturedVariableNode(LocalVariable v) { v.isCaptured() } or
|
||||
TPropNode(@property p) or
|
||||
TRestPatternNode(DestructuringPattern dp, Expr rest) { rest = dp.getRest() } or
|
||||
TDestructuringPatternNode(DestructuringPattern dp) or
|
||||
TElementPatternNode(ArrayPattern ap, Expr p) { p = ap.getElement(_) } or
|
||||
TElementNode(ArrayExpr arr, Expr e) { e = arr.getAnElement() } or
|
||||
TReflectiveCallNode(MethodCallExpr ce, string kind) {
|
||||
ce.getMethodName() = kind and
|
||||
(kind = "call" or kind = "apply")
|
||||
} or
|
||||
TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel } or
|
||||
TUnusedParameterNode(SimpleParameter p) { not exists(SSA::definition(p)) } or
|
||||
TDestructuredModuleImportNode(ImportDeclaration decl) {
|
||||
exists(decl.getASpecifier().getImportedName())
|
||||
} or
|
||||
THtmlAttributeNode(HTML::Attribute attr) or
|
||||
TExceptionalFunctionReturnNode(Function f) or
|
||||
TExceptionalInvocationReturnNode(InvokeExpr e) or
|
||||
TGlobalAccessPathRoot()
|
||||
|
||||
/**
|
||||
* A node in the data flow graph.
|
||||
*/
|
||||
@@ -90,13 +68,11 @@ module DataFlow {
|
||||
/**
|
||||
* Gets the expression enclosing this data flow node.
|
||||
* In most cases the result is the same as `asExpr()`, however this method
|
||||
* additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter`
|
||||
* for a `DataFlow::ParameterNode`.
|
||||
* additionally includes the `InvokeExpr` corresponding to reflective calls.
|
||||
*/
|
||||
Expr getEnclosingExpr() {
|
||||
result = asExpr() or
|
||||
this = DataFlow::reflectiveCallNode(result) or
|
||||
result = this.(ParameterNode).getParameter()
|
||||
this = DataFlow::reflectiveCallNode(result)
|
||||
}
|
||||
|
||||
/** Gets the AST node corresponding to this data flow node, if any. */
|
||||
@@ -218,8 +194,7 @@ module DataFlow {
|
||||
// IIFE call -> return value of IIFE
|
||||
exists(Function fun |
|
||||
localCall(this.asExpr(), fun) and
|
||||
result = fun.getAReturnedExpr().flow() and
|
||||
strictcount(fun.getAReturnedExpr()) = 1 and
|
||||
result = unique(Expr ret | ret = fun.getAReturnedExpr()).flow() and
|
||||
not fun.getExit().isJoin() // can only reach exit by the return statement
|
||||
)
|
||||
}
|
||||
@@ -252,7 +227,7 @@ module DataFlow {
|
||||
*/
|
||||
private JSDocTypeExpr getFallbackTypeAnnotation() {
|
||||
exists(BindingPattern pattern |
|
||||
this = lvalueNode(pattern) and
|
||||
this = valueNode(pattern) and
|
||||
not ast_node_type(pattern, _) and
|
||||
result = pattern.getTypeAnnotation()
|
||||
)
|
||||
@@ -282,8 +257,8 @@ module DataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression or a declaration of a function, class, namespace or enum,
|
||||
* viewed as a node in the data flow graph.
|
||||
* A node in the data flow graph which corresponds to an expression,
|
||||
* destructuring pattern, or declaration of a function, class, namespace, or enum.
|
||||
*
|
||||
* Examples:
|
||||
* ```js
|
||||
@@ -391,30 +366,6 @@ module DataFlow {
|
||||
override ASTNode getAstNode() { result = rest }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node in the data flow graph which corresponds to the value destructured by an
|
||||
* object or array pattern.
|
||||
*/
|
||||
private class DestructuringPatternNode extends Node, TDestructuringPatternNode {
|
||||
DestructuringPattern pattern;
|
||||
|
||||
DestructuringPatternNode() { this = TDestructuringPatternNode(pattern) }
|
||||
|
||||
override BasicBlock getBasicBlock() { result = pattern.getBasicBlock() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
pattern.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override string toString() { result = pattern.toString() }
|
||||
|
||||
override File getFile() { result = pattern.getFile() }
|
||||
|
||||
override ASTNode getAstNode() { result = pattern }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node in the data flow graph which corresponds to an element pattern of an
|
||||
* array pattern.
|
||||
@@ -760,10 +711,6 @@ module DataFlow {
|
||||
parameterNode(paramNode, param)
|
||||
|
|
||||
result = paramNode
|
||||
or
|
||||
// special case: there is no SSA flow step for unused parameters
|
||||
paramNode instanceof UnusedParameterNode and
|
||||
result = param.getDefault().flow()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -851,7 +798,7 @@ module DataFlow {
|
||||
/** Gets the value pattern of this property pattern. */
|
||||
Expr getValuePattern() { result = prop.getValuePattern() }
|
||||
|
||||
override Node getBase() { result = TDestructuringPatternNode(prop.getObjectPattern()) }
|
||||
override Node getBase() { result = TValueNode(prop.getObjectPattern()) }
|
||||
|
||||
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
|
||||
|
||||
@@ -864,7 +811,7 @@ module DataFlow {
|
||||
* for `[ ...elts ] = arr`.
|
||||
*/
|
||||
private class RestPatternAsPropRead extends PropRead, RestPatternNode {
|
||||
override Node getBase() { result = TDestructuringPatternNode(pattern) }
|
||||
override Node getBase() { result = TValueNode(pattern) }
|
||||
|
||||
override Expr getPropertyNameExpr() { none() }
|
||||
|
||||
@@ -877,7 +824,7 @@ module DataFlow {
|
||||
* for `y`.
|
||||
*/
|
||||
private class ElementPatternAsPropRead extends PropRead, ElementPatternNode {
|
||||
override Node getBase() { result = TDestructuringPatternNode(pattern) }
|
||||
override Node getBase() { result = TValueNode(pattern) }
|
||||
|
||||
override Expr getPropertyNameExpr() { none() }
|
||||
|
||||
@@ -924,32 +871,6 @@ module DataFlow {
|
||||
override string getPropertyName() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node representing an unused parameter.
|
||||
*
|
||||
* This case exists to ensure all parameters have a corresponding data-flow node.
|
||||
* In most cases, parameters are represented by SSA definitions or destructuring pattern nodes.
|
||||
*/
|
||||
private class UnusedParameterNode extends DataFlow::Node, TUnusedParameterNode {
|
||||
SimpleParameter p;
|
||||
|
||||
UnusedParameterNode() { this = TUnusedParameterNode(p) }
|
||||
|
||||
override string toString() { result = p.toString() }
|
||||
|
||||
override ASTNode getAstNode() { result = p }
|
||||
|
||||
override BasicBlock getBasicBlock() { result = p.getBasicBlock() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
p.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override File getFile() { result = p.getFile() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node representing an HTML attribute.
|
||||
*/
|
||||
@@ -1303,7 +1224,7 @@ module DataFlow {
|
||||
/**
|
||||
* INTERNAL: Use `parameterNode(Parameter)` instead.
|
||||
*/
|
||||
predicate parameterNode(DataFlow::Node nd, Parameter p) { nd = lvalueNode(p) }
|
||||
predicate parameterNode(DataFlow::Node nd, Parameter p) { nd = valueNode(p) }
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `thisNode(StmtContainer container)` instead.
|
||||
@@ -1355,9 +1276,7 @@ module DataFlow {
|
||||
result = TSsaDefNode(ssa)
|
||||
)
|
||||
or
|
||||
result = TDestructuringPatternNode(lvalue)
|
||||
or
|
||||
result = TUnusedParameterNode(lvalue)
|
||||
result = TValueNode(lvalue.(DestructuringPattern))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1412,6 +1331,17 @@ module DataFlow {
|
||||
succ = lvalueNode(def.getTarget())
|
||||
)
|
||||
or
|
||||
exists(SimpleParameter param |
|
||||
pred = valueNode(param) and // The value node represents the incoming argument
|
||||
succ = lvalueNode(param) // The SSA node represents the parameters's local variable
|
||||
)
|
||||
or
|
||||
exists(Expr arg, Parameter param |
|
||||
localArgumentPassing(arg, param) and
|
||||
pred = valueNode(arg) and
|
||||
succ = valueNode(param)
|
||||
)
|
||||
or
|
||||
exists(PropertyPattern pattern |
|
||||
pred = TPropNode(pattern) and
|
||||
succ = lvalueNode(pattern.getValuePattern())
|
||||
@@ -1547,8 +1477,7 @@ module DataFlow {
|
||||
*/
|
||||
private AST::ValueNode defSourceNode(VarDef def) {
|
||||
result = def.getSource() or
|
||||
result = def.getDestructuringSource() or
|
||||
localArgumentPassing(result, def)
|
||||
result = def.getDestructuringSource()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1594,8 +1523,15 @@ module DataFlow {
|
||||
e instanceof FunctionBindExpr
|
||||
or
|
||||
e instanceof TaggedTemplateExpr
|
||||
or
|
||||
e instanceof Parameter and
|
||||
not localArgumentPassing(_, e) and
|
||||
not isAnalyzedParameter(e) and
|
||||
not e.(Parameter).isRestParameter()
|
||||
)
|
||||
or
|
||||
nd.(AnalyzedNode).hasAdditionalIncompleteness(cause)
|
||||
or
|
||||
nd.asExpr() instanceof ExternalModuleReference and
|
||||
cause = "import"
|
||||
or
|
||||
@@ -1623,18 +1559,12 @@ module DataFlow {
|
||||
exists(PropertyPattern p | nd = TPropNode(p)) and cause = "heap"
|
||||
or
|
||||
nd instanceof TElementPatternNode and cause = "heap"
|
||||
or
|
||||
nd instanceof UnusedParameterNode and cause = "call"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if definition `def` cannot be completely analyzed due to `cause`.
|
||||
*/
|
||||
private predicate defIsIncomplete(VarDef def, Incompleteness cause) {
|
||||
def instanceof Parameter and
|
||||
not localArgumentPassing(_, def) and
|
||||
cause = "call"
|
||||
or
|
||||
def instanceof ImportSpecifier and
|
||||
cause = "import"
|
||||
or
|
||||
|
||||
@@ -12,6 +12,8 @@ private predicate isEscape(DataFlow::Node escape, string cause) {
|
||||
or
|
||||
escape = any(DataFlow::FunctionNode fun).getAReturn() and cause = "return"
|
||||
or
|
||||
escape = any(YieldExpr yield).getOperand().flow() and cause = "yield"
|
||||
or
|
||||
escape = any(ThrowStmt t).getExpr().flow() and cause = "throw"
|
||||
or
|
||||
escape = any(GlobalVariable v).getAnAssignedExpr().flow() and cause = "global"
|
||||
|
||||
@@ -241,9 +241,6 @@ module TaintTracking {
|
||||
*/
|
||||
private predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Expr e, Expr f | e = succ.asExpr() and f = pred.asExpr() |
|
||||
// arrays with tainted elements and objects with tainted property names are tainted
|
||||
e.(ArrayExpr).getAnElement() = f
|
||||
or
|
||||
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
|
||||
prop.isComputed() and f = prop.getNameExpr()
|
||||
)
|
||||
@@ -258,6 +255,10 @@ module TaintTracking {
|
||||
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
|
||||
)
|
||||
or
|
||||
// arrays with tainted elements and objects with tainted property names are tainted
|
||||
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
|
||||
not any(PromiseAllCreation call).getArrayNode() = succ
|
||||
or
|
||||
// reading from a tainted object yields a tainted result
|
||||
succ.(DataFlow::PropRead).getBase() = pred
|
||||
or
|
||||
@@ -778,7 +779,8 @@ module TaintTracking {
|
||||
*/
|
||||
class AdHocWhitelistCheckSanitizer extends SanitizerGuardNode, DataFlow::CallNode {
|
||||
AdHocWhitelistCheckSanitizer() {
|
||||
getCalleeName().regexpMatch("(?i).*((?<!un)safe|whitelist|allow|(?<!un)auth(?!or\\b)).*") and
|
||||
getCalleeName()
|
||||
.regexpMatch("(?i).*((?<!un)safe|whitelist|(?<!in)valid|allow|(?<!un)auth(?!or\\b)).*") and
|
||||
getNumArgument() = 1
|
||||
}
|
||||
|
||||
@@ -828,6 +830,28 @@ module TaintTracking {
|
||||
/** DEPRECATED. This class has been renamed to `MembershipTestSanitizer`. */
|
||||
deprecated class StringInclusionSanitizer = MembershipTestSanitizer;
|
||||
|
||||
/**
|
||||
* A test of form `x.length === "0"`, preventing `x` from being tainted.
|
||||
*/
|
||||
class IsEmptyGuard extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {
|
||||
override EqualityTest astNode;
|
||||
boolean polarity;
|
||||
Expr operand;
|
||||
|
||||
IsEmptyGuard() {
|
||||
astNode.getPolarity() = polarity and
|
||||
astNode.getAnOperand().(ConstantExpr).getIntValue() = 0 and
|
||||
exists(DataFlow::PropRead read | read.asExpr() = astNode.getAnOperand() |
|
||||
read.getBase().asExpr() = operand and
|
||||
read.getPropertyName() = "length"
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) { polarity = outcome and e = operand }
|
||||
|
||||
override predicate appliesTo(Configuration cfg) { any() }
|
||||
}
|
||||
|
||||
/** DEPRECATED. This class has been renamed to `MembershipTestSanitizer`. */
|
||||
deprecated class InclusionSanitizer = MembershipTestSanitizer;
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class AnalyzedNode extends DataFlow::Node {
|
||||
boolean getTheBooleanValue() { forex(boolean bv | bv = getABooleanValue() | result = bv) }
|
||||
|
||||
/** Gets the unique type inferred for this node, if any. */
|
||||
InferredType getTheType() { count(getAType()) = 1 and result = getAType() }
|
||||
InferredType getTheType() { result = unique(InferredType t | t = getAType()) }
|
||||
|
||||
/**
|
||||
* Gets a pretty-printed representation of all types inferred for this node
|
||||
@@ -156,6 +156,14 @@ class AnalyzedNode extends DataFlow::Node {
|
||||
|
||||
/** Holds if the flow analysis can infer at least one abstract value for this node. */
|
||||
predicate hasFlow() { exists(getAValue()) }
|
||||
|
||||
/**
|
||||
* INTERNAL. Use `isIncomplete()` instead.
|
||||
*
|
||||
* Subclasses may override this to contribute additional incompleteness to this node
|
||||
* without overriding `isIncomplete()`.
|
||||
*/
|
||||
predicate hasAdditionalIncompleteness(DataFlow::Incompleteness cause) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,14 +263,14 @@ class AnalyzedFunction extends DataFlow::AnalyzedValueNode {
|
||||
* of functions that cannot actually complete normally, since it does not
|
||||
* account for `finally` blocks and does not check reachability.
|
||||
*/
|
||||
private predicate mayReturnImplicitly() {
|
||||
exists(ConcreteControlFlowNode final |
|
||||
final.getContainer() = astNode and
|
||||
final.isAFinalNode() and
|
||||
not final instanceof ReturnStmt and
|
||||
not final instanceof ThrowStmt
|
||||
)
|
||||
}
|
||||
private predicate mayReturnImplicitly() { terminalNode(astNode, any(ExprOrStmt st)) }
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate terminalNode(Function f, ControlFlowNode final) {
|
||||
final.isAFinalNodeOfContainer(f) and
|
||||
not final instanceof ReturnStmt and
|
||||
not final instanceof ThrowStmt
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
private import javascript
|
||||
private import VariableTypeInference
|
||||
|
||||
/**
|
||||
* Holds if `p` is analyzed precisely by the type inference.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate isAnalyzedParameter(Parameter p) {
|
||||
exists(FunctionWithAnalyzedParameters f, int parmIdx | p = f.getParameter(parmIdx) |
|
||||
// we cannot track flow into rest parameters
|
||||
not p.(Parameter).isRestParameter()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter whose value is propagated interprocedurally.
|
||||
*/
|
||||
class AnalyzedParameter extends AnalyzedValueNode {
|
||||
override Parameter astNode;
|
||||
|
||||
AnalyzedParameter() { isAnalyzedParameter(astNode) }
|
||||
|
||||
FunctionWithAnalyzedParameters getFunction() { astNode = result.getAParameter() }
|
||||
|
||||
override AbstractValue getALocalValue() {
|
||||
exists(DataFlow::AnalyzedNode pred |
|
||||
getFunction().argumentPassing(astNode, pred.asExpr()) and
|
||||
result = pred.getALocalValue()
|
||||
)
|
||||
or
|
||||
not getFunction().mayReceiveArgument(astNode) and
|
||||
result = TAbstractUndefined()
|
||||
or
|
||||
result = astNode.getDefault().analyze().getALocalValue()
|
||||
}
|
||||
|
||||
override predicate hasAdditionalIncompleteness(DataFlow::Incompleteness cause) {
|
||||
getFunction().isIncomplete(cause)
|
||||
or
|
||||
not getFunction().argumentPassing(astNode, _) and
|
||||
getFunction().mayReceiveArgument(astNode) and
|
||||
cause = "call"
|
||||
}
|
||||
}
|
||||
@@ -239,39 +239,41 @@ private class AnalyzedBinaryExpr extends DataFlow::AnalyzedValueNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a primitive type to which the local value of `e` can be coerced.
|
||||
* Gets the `n`th operand of the given `+` or `+=` expression.
|
||||
*/
|
||||
private PrimitiveType getALocalPrimitiveType(Expr e) {
|
||||
result = e.analyze().getALocalValue().toPrimitive().getType()
|
||||
pragma[nomagic]
|
||||
private DataFlow::AnalyzedValueNode getAddOperand(Expr e, int n) {
|
||||
(e instanceof AddExpr or e instanceof AssignAddExpr) and
|
||||
result = DataFlow::valueNode(e.getChildExpr(n))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` may hold a string value.
|
||||
* Gets a primitive type of the `n`th operand of the given `+` or `+=` expression.
|
||||
*/
|
||||
private predicate maybeString(Expr e) { getALocalPrimitiveType(e) = TTString() }
|
||||
|
||||
/**
|
||||
* Holds if `e` may hold a non-string value.
|
||||
*/
|
||||
private predicate maybeNonString(Expr e) { getALocalPrimitiveType(e) != TTString() }
|
||||
pragma[noopt]
|
||||
private PrimitiveType getAnAddOperandPrimitiveType(Expr e, int n) {
|
||||
exists(DataFlow::AnalyzedValueNode operand, AbstractValue value, AbstractValue prim |
|
||||
operand = getAddOperand(e, n) and
|
||||
value = operand.getALocalValue() and
|
||||
prim = value.toPrimitive() and
|
||||
result = prim.getType() and
|
||||
result instanceof PrimitiveType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is a `+` or `+=` expression that could be interpreted as a string append
|
||||
* (as opposed to a numeric addition) at runtime.
|
||||
*/
|
||||
private predicate isStringAppend(Expr e) {
|
||||
(e instanceof AddExpr or e instanceof AssignAddExpr) and
|
||||
maybeString(e.getAChildExpr())
|
||||
}
|
||||
private predicate isStringAppend(Expr e) { getAnAddOperandPrimitiveType(e, _) = TTString() }
|
||||
|
||||
/**
|
||||
* Holds if `e` is a `+` or `+=` expression that could be interpreted as a numeric addition
|
||||
* (as opposed to a string append) at runtime.
|
||||
*/
|
||||
private predicate isAddition(Expr e) {
|
||||
(e instanceof AddExpr or e instanceof AssignAddExpr) and
|
||||
maybeNonString(e.getChildExpr(0)) and
|
||||
maybeNonString(e.getChildExpr(1))
|
||||
getAnAddOperandPrimitiveType(e, 0) != TTString() and
|
||||
getAnAddOperandPrimitiveType(e, 1) != TTString()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* INTERNAL: Do not use outside the data flow library.
|
||||
*
|
||||
* Contains the raw data type underlying `DataFlow::Node`.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
/**
|
||||
* The raw data type underlying `DataFlow::Node`.
|
||||
*/
|
||||
cached
|
||||
newtype TNode =
|
||||
TValueNode(AST::ValueNode nd) or
|
||||
TSsaDefNode(SsaDefinition d) or
|
||||
TCapturedVariableNode(LocalVariable v) { v.isCaptured() } or
|
||||
TPropNode(@property p) or
|
||||
TRestPatternNode(DestructuringPattern dp, Expr rest) { rest = dp.getRest() } or
|
||||
TElementPatternNode(ArrayPattern ap, Expr p) { p = ap.getElement(_) } or
|
||||
TElementNode(ArrayExpr arr, Expr e) { e = arr.getAnElement() } or
|
||||
TReflectiveCallNode(MethodCallExpr ce, string kind) {
|
||||
ce.getMethodName() = kind and
|
||||
(kind = "call" or kind = "apply")
|
||||
} or
|
||||
TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel } or
|
||||
TDestructuredModuleImportNode(ImportDeclaration decl) {
|
||||
exists(decl.getASpecifier().getImportedName())
|
||||
} or
|
||||
THtmlAttributeNode(HTML::Attribute attr) or
|
||||
TExceptionalFunctionReturnNode(Function f) or
|
||||
TExceptionalInvocationReturnNode(InvokeExpr e) or
|
||||
TGlobalAccessPathRoot()
|
||||
@@ -92,6 +92,13 @@ private module CachedSteps {
|
||||
cached
|
||||
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
|
||||
|
||||
private predicate callsBoundInternal(
|
||||
DataFlow::InvokeNode invk, Function f, int boundArgs, boolean contextDependent
|
||||
) {
|
||||
CallGraph::getABoundFunctionReference(f.flow(), boundArgs, contextDependent)
|
||||
.flowsTo(invk.getCalleeNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invk` may invoke a bound version of `f` with `boundArgs` already bound.
|
||||
*
|
||||
@@ -101,7 +108,7 @@ private module CachedSteps {
|
||||
*/
|
||||
cached
|
||||
predicate callsBound(DataFlow::InvokeNode invk, Function f, int boundArgs) {
|
||||
CallGraph::getABoundFunctionReference(f.flow(), boundArgs, false).flowsTo(invk.getCalleeNode())
|
||||
callsBoundInternal(invk, f, boundArgs, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,10 +118,10 @@ private module CachedSteps {
|
||||
*/
|
||||
cached
|
||||
predicate exploratoryBoundInvokeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::InvokeNode invk, DataFlow::FunctionNode f, int i, int boundArgs |
|
||||
CallGraph::getABoundFunctionReference(f, boundArgs, _).flowsTo(invk.getCalleeNode()) and
|
||||
exists(DataFlow::InvokeNode invk, Function f, int i, int boundArgs |
|
||||
callsBoundInternal(invk, f, boundArgs, _) and
|
||||
pred = invk.getArgument(i) and
|
||||
succ = f.getParameter(i + boundArgs)
|
||||
succ = DataFlow::parameterNode(f.getParameter(i + boundArgs))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -144,11 +151,14 @@ private module CachedSteps {
|
||||
) {
|
||||
calls(invk, f) and
|
||||
(
|
||||
exists(int i, Parameter p |
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
arg = invk.getArgument(i) and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
exists(int i | arg = invk.getArgument(i) |
|
||||
exists(Parameter p |
|
||||
f.getParameter(i) = p and
|
||||
not p.isRestParameter() and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
)
|
||||
or
|
||||
parm = reflectiveParameterAccess(f, i)
|
||||
)
|
||||
or
|
||||
arg = invk.(DataFlow::CallNode).getReceiver() and
|
||||
@@ -178,6 +188,22 @@ private module CachedSteps {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node inside `f` that refers to the `arguments` object of `f`.
|
||||
*/
|
||||
private DataFlow::Node argumentsAccess(Function f) {
|
||||
result.getContainer().getEnclosingContainer*() = f and
|
||||
result.analyze().getAValue().(AbstractArguments).getFunction() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that refers to the `i`th parameter of `f` through its `arguments`
|
||||
* object.
|
||||
*/
|
||||
private DataFlow::SourceNode reflectiveParameterAccess(Function f, int i) {
|
||||
result.(DataFlow::PropRead).accesses(argumentsAccess(f), any(string p | i = p.toInt()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow step from `pred` to `succ` through parameter passing
|
||||
* to a function call.
|
||||
|
||||
@@ -187,8 +187,7 @@ private class IIFEWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
|
||||
*/
|
||||
private VarAccess getOnlyAccess(FunctionDeclStmt fn, LocalVariable v) {
|
||||
v = fn.getVariable() and
|
||||
result = v.getAnAccess() and
|
||||
strictcount(v.getAnAccess()) = 1
|
||||
result = unique(VarAccess acc | acc = v.getAnAccess())
|
||||
}
|
||||
|
||||
/** A function that only is used locally, making it amenable to type inference. */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
private import javascript
|
||||
private import AbstractValuesImpl
|
||||
private import AnalyzedParameters
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.dataflow.Refinements
|
||||
|
||||
@@ -120,7 +121,7 @@ class AnalyzedVarDef extends VarDef {
|
||||
* due to the given `cause`.
|
||||
*/
|
||||
predicate isIncomplete(DataFlow::Incompleteness cause) {
|
||||
this instanceof Parameter and cause = "call"
|
||||
this instanceof Parameter and DataFlow::valueNode(this).(AnalyzedValueNode).isIncomplete(cause)
|
||||
or
|
||||
this instanceof ImportSpecifier and cause = "import"
|
||||
or
|
||||
@@ -143,47 +144,21 @@ class AnalyzedVarDef extends VarDef {
|
||||
/**
|
||||
* Flow analysis for simple parameters of selected functions.
|
||||
*/
|
||||
private class AnalyzedParameter extends AnalyzedVarDef, @vardecl {
|
||||
AnalyzedParameter() {
|
||||
exists(FunctionWithAnalyzedParameters f, int parmIdx | this = f.getParameter(parmIdx) |
|
||||
// we cannot track flow into rest parameters
|
||||
not this.(Parameter).isRestParameter()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the function this is a parameter of. */
|
||||
FunctionWithAnalyzedParameters getFunction() { this = result.getAParameter() }
|
||||
|
||||
override DataFlow::AnalyzedNode getRhs() {
|
||||
getFunction().argumentPassing(this, result.asExpr()) or
|
||||
result = AnalyzedVarDef.super.getRhs()
|
||||
}
|
||||
private class AnalyzedParameterAsVarDef extends AnalyzedVarDef, @vardecl {
|
||||
AnalyzedParameterAsVarDef() { this instanceof Parameter }
|
||||
|
||||
override AbstractValue getAnRhsValue() {
|
||||
result = AnalyzedVarDef.super.getAnRhsValue()
|
||||
or
|
||||
not getFunction().mayReceiveArgument(this) and
|
||||
result = TAbstractUndefined()
|
||||
}
|
||||
|
||||
override predicate isIncomplete(DataFlow::Incompleteness cause) {
|
||||
getFunction().isIncomplete(cause)
|
||||
or
|
||||
not getFunction().argumentPassing(this, _) and
|
||||
getFunction().mayReceiveArgument(this) and
|
||||
cause = "call"
|
||||
result = DataFlow::valueNode(this).(AnalyzedValueNode).getALocalValue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow analysis for simple rest parameters.
|
||||
*/
|
||||
private class AnalyzedRestParameter extends AnalyzedVarDef, @vardecl {
|
||||
AnalyzedRestParameter() { this.(Parameter).isRestParameter() }
|
||||
private class AnalyzedRestParameter extends AnalyzedValueNode {
|
||||
AnalyzedRestParameter() { astNode.(Parameter).isRestParameter() }
|
||||
|
||||
override AbstractValue getAnRhsValue() { result = TAbstractOtherObject() }
|
||||
|
||||
override predicate isIncomplete(DataFlow::Incompleteness cause) { none() }
|
||||
override AbstractValue getALocalValue() { result = TAbstractOtherObject() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,9 +387,7 @@ private class AnalyzedGlobalVarUse extends DataFlow::AnalyzedValueNode {
|
||||
result.getBase().analyze().getALocalValue() instanceof AbstractGlobalObject
|
||||
}
|
||||
|
||||
override predicate isIncomplete(DataFlow::Incompleteness reason) {
|
||||
super.isIncomplete(reason)
|
||||
or
|
||||
override predicate hasAdditionalIncompleteness(DataFlow::Incompleteness reason) {
|
||||
clobberedProp(gv, reason)
|
||||
}
|
||||
|
||||
@@ -448,7 +421,7 @@ private AnalyzedVarDef defIn(GlobalVariable gv, TopLevel tl) {
|
||||
* Holds if there is a write to a property with the same name as `gv` on an object
|
||||
* for which the analysis is incomplete due to the given `reason`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
cached
|
||||
private predicate clobberedProp(GlobalVariable gv, DataFlow::Incompleteness reason) {
|
||||
exists(AnalyzedNode base |
|
||||
potentialPropWriteOfGlobal(base, gv) and
|
||||
@@ -456,13 +429,13 @@ private predicate clobberedProp(GlobalVariable gv, DataFlow::Incompleteness reas
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate indefiniteObjectValue(AbstractValue val, DataFlow::Incompleteness reason) {
|
||||
val.isIndefinite(reason) and
|
||||
val.getType() = TTObject()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate potentialPropWriteOfGlobal(AnalyzedNode base, GlobalVariable gv) {
|
||||
exists(DataFlow::PropWrite pwn |
|
||||
pwn.getPropertyName() = gv.getName() and
|
||||
@@ -668,7 +641,7 @@ abstract class FunctionWithAnalyzedParameters extends Function {
|
||||
* Holds if `p` is a parameter of this function and `arg` is
|
||||
* the corresponding argument.
|
||||
*/
|
||||
abstract predicate argumentPassing(SimpleParameter p, Expr arg);
|
||||
abstract predicate argumentPassing(Parameter p, Expr arg);
|
||||
|
||||
/**
|
||||
* Holds if `p` is a parameter of this function that may receive a value from an argument.
|
||||
@@ -688,7 +661,7 @@ abstract private class CallWithAnalyzedParameters extends FunctionWithAnalyzedPa
|
||||
*/
|
||||
abstract DataFlow::InvokeNode getAnInvocation();
|
||||
|
||||
override predicate argumentPassing(SimpleParameter p, Expr arg) {
|
||||
override predicate argumentPassing(Parameter p, Expr arg) {
|
||||
exists(DataFlow::InvokeNode invk, int argIdx | invk = getAnInvocation() |
|
||||
p = getParameter(argIdx) and
|
||||
not p.isRestParameter() and
|
||||
|
||||
291
javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
Normal file
291
javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.frameworks.HTTP
|
||||
|
||||
/**
|
||||
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
|
||||
*/
|
||||
module Fastify {
|
||||
/**
|
||||
* An expression that creates a new Fastify server.
|
||||
*/
|
||||
abstract class ServerDefinition extends HTTP::Servers::StandardServerDefinition { }
|
||||
|
||||
/**
|
||||
* A standard way to create a Fastify server.
|
||||
*/
|
||||
class StandardServerDefinition extends ServerDefinition {
|
||||
StandardServerDefinition() {
|
||||
this = DataFlow::moduleImport("fastify").getAnInvocation().asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function used as a Fastify route handler.
|
||||
*
|
||||
* By default, only handlers installed by a Fastify route setup are recognized,
|
||||
* but support for other kinds of route handlers can be added by implementing
|
||||
* additional subclasses of this class.
|
||||
*/
|
||||
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the request object.
|
||||
*/
|
||||
abstract DataFlow::ParameterNode getRequestParameter();
|
||||
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the reply object.
|
||||
*/
|
||||
abstract DataFlow::ParameterNode getReplyParameter();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify route handler installed by a route setup.
|
||||
*/
|
||||
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
|
||||
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
|
||||
|
||||
override DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
|
||||
|
||||
override DataFlow::ParameterNode getReplyParameter() { result = this.getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify reply source, that is, the `reply` parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class ReplySource extends HTTP::Servers::ResponseSource {
|
||||
RouteHandler rh;
|
||||
|
||||
ReplySource() { this = rh.getReplyParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that provides this response.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Fastify request source, that is, the request parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class RequestSource extends HTTP::Servers::RequestSource {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestSource() { this = rh.getRequestParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that handles this request.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Fastify method that sets up a route.
|
||||
*/
|
||||
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
|
||||
ServerDefinition server;
|
||||
string methodName;
|
||||
|
||||
RouteSetup() {
|
||||
this.getMethodName() = methodName and
|
||||
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] and
|
||||
server.flowsTo(this.getReceiver())
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
result = getARouteHandler(DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = this.getARouteHandlerExpr().getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
|
||||
}
|
||||
|
||||
override Expr getServer() { result = server }
|
||||
|
||||
/** Gets an argument that represents a route handler being registered. */
|
||||
private DataFlow::Node getARouteHandlerExpr() {
|
||||
if methodName = "route"
|
||||
then
|
||||
result =
|
||||
this
|
||||
.flow()
|
||||
.(DataFlow::MethodCallNode)
|
||||
.getOptionArgument(0,
|
||||
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
|
||||
"onSend", "onResponse", "handler"])
|
||||
else result = getLastArgument().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a user-controlled Fastify request input.
|
||||
*/
|
||||
private class RequestInputAccess extends HTTP::RequestInputAccess {
|
||||
RouteHandler rh;
|
||||
string kind;
|
||||
|
||||
RequestInputAccess() {
|
||||
exists(string name | this = rh.getARequestSource().ref().getAPropertyRead(name) |
|
||||
kind = "parameter" and
|
||||
name = ["params", "query"]
|
||||
or
|
||||
kind = "body" and
|
||||
name = "body"
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = kind }
|
||||
|
||||
override predicate isUserControlledObject() {
|
||||
kind = "body" and
|
||||
(
|
||||
usesFastifyPlugin(rh,
|
||||
DataFlow::moduleImport(["fastify-xml-body-parser", "fastify-formbody"]))
|
||||
or
|
||||
usesMiddleware(rh,
|
||||
any(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects()))
|
||||
)
|
||||
or
|
||||
kind = "parameter" and
|
||||
usesFastifyPlugin(rh, DataFlow::moduleImport("fastify-qs"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rh` uses `plugin`.
|
||||
*/
|
||||
private predicate usesFastifyPlugin(RouteHandler rh, DataFlow::SourceNode plugin) {
|
||||
exists(RouteSetup setup |
|
||||
plugin
|
||||
.flowsTo(setup
|
||||
.getServer()
|
||||
.flow()
|
||||
.(DataFlow::SourceNode)
|
||||
.getAMethodCall("register")
|
||||
.getArgument(0)) and // only matches the plugins that apply to all routes
|
||||
rh = setup.getARouteHandler()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rh` uses `plugin`.
|
||||
*/
|
||||
private predicate usesMiddleware(RouteHandler rh, DataFlow::SourceNode middleware) {
|
||||
exists(RouteSetup setup |
|
||||
middleware
|
||||
.flowsTo(setup
|
||||
.getServer()
|
||||
.flow()
|
||||
.(DataFlow::SourceNode)
|
||||
.getAMethodCall("use")
|
||||
.getArgument(0)) and // only matches the middlewares that apply to all routes
|
||||
rh = setup.getARouteHandler()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a header on a Fastify request.
|
||||
*/
|
||||
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestHeaderAccess() {
|
||||
this = rh.getARequestSource().ref().getAPropertyRead("headers").getAPropertyRead()
|
||||
}
|
||||
|
||||
override string getAHeaderName() {
|
||||
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = "header" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument passed to the `send` or `end` method of an HTTP response object.
|
||||
*/
|
||||
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
|
||||
RouteHandler rh;
|
||||
|
||||
ResponseSendArgument() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0).asExpr()
|
||||
or
|
||||
this = rh.(DataFlow::FunctionNode).getAReturn().asExpr()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of the `redirect` method of an HTTP response object.
|
||||
*/
|
||||
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
|
||||
RouteHandler rh;
|
||||
|
||||
RedirectInvocation() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("redirect").asExpr()
|
||||
}
|
||||
|
||||
override Expr getUrlArgument() { result = this.getLastArgument() }
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets a single header of the HTTP response.
|
||||
*/
|
||||
private class SetOneHeader extends HTTP::Servers::StandardHeaderDefinition,
|
||||
DataFlow::MethodCallNode {
|
||||
RouteHandler rh;
|
||||
|
||||
SetOneHeader() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("header") and
|
||||
this.getNumArgument() = 2
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets any number of headers of the HTTP response.
|
||||
*/
|
||||
class SetMultipleHeaders extends HTTP::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
|
||||
RouteHandler rh;
|
||||
|
||||
SetMultipleHeaders() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall("headers") and
|
||||
this.getNumArgument() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the multiple headers object that is to be set.
|
||||
*/
|
||||
private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(this.getArgument(0)) }
|
||||
|
||||
override predicate definesExplicitly(string headerName, Expr headerValue) {
|
||||
exists(string header |
|
||||
getAHeaderSource().hasPropertyWrite(header, headerValue.flow()) and
|
||||
headerName = header.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override Expr getNameExpr() {
|
||||
exists(DataFlow::PropWrite write |
|
||||
this.getAHeaderSource().flowsTo(write.getBase()) and
|
||||
result = write.getPropertyNameExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ import semmle.javascript.frameworks.Koa
|
||||
import semmle.javascript.frameworks.NodeJSLib
|
||||
import semmle.javascript.frameworks.Restify
|
||||
import semmle.javascript.frameworks.Connect
|
||||
import semmle.javascript.frameworks.Fastify
|
||||
|
||||
@@ -7,18 +7,25 @@ import semmle.javascript.frameworks.HTTP
|
||||
import semmle.javascript.security.SensitiveActions
|
||||
|
||||
module NodeJSLib {
|
||||
private GlobalVariable processVariable() { variables(result, "process", any(GlobalScope sc)) }
|
||||
|
||||
pragma[nomagic]
|
||||
private GlobalVarAccess processExprInTopLevel(TopLevel tl) {
|
||||
result = processVariable().getAnAccess() and
|
||||
tl = result.getTopLevel()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private GlobalVarAccess processExprInNodeModule() {
|
||||
result = processExprInTopLevel(any(NodeModule m))
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to the global `process` variable in a Node.js module, interpreted as
|
||||
* an import of the `process` module.
|
||||
*/
|
||||
private class ImplicitProcessImport extends DataFlow::ModuleImportNode::Range {
|
||||
ImplicitProcessImport() {
|
||||
exists(GlobalVariable process |
|
||||
process.getName() = "process" and
|
||||
this = DataFlow::exprNode(process.getAnAccess())
|
||||
) and
|
||||
getTopLevel() instanceof NodeModule
|
||||
}
|
||||
ImplicitProcessImport() { this = DataFlow::exprNode(processExprInNodeModule()) }
|
||||
|
||||
override string getPath() { result = "process" }
|
||||
}
|
||||
@@ -272,7 +279,7 @@ module NodeJSLib {
|
||||
DataFlow::Node tainted;
|
||||
|
||||
PathFlowTarget() {
|
||||
exists(string methodName | this = DataFlow::moduleMember("path", methodName).getACall() |
|
||||
exists(string methodName | this = NodeJSLib::Path::moduleMember(methodName).getACall() |
|
||||
// getters
|
||||
methodName = "basename" and tainted = getArgument(0)
|
||||
or
|
||||
@@ -442,10 +449,7 @@ module NodeJSLib {
|
||||
|
||||
private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) {
|
||||
exists(string moduleName |
|
||||
moduleName = "fs" or
|
||||
moduleName = "graceful-fs" or
|
||||
moduleName = "fs-extra" or
|
||||
moduleName = "original-fs"
|
||||
moduleName = ["mz/fs", "original-fs", "fs-extra", "graceful-fs", "fs"]
|
||||
|
|
||||
result = DataFlow::moduleImport(moduleName)
|
||||
or
|
||||
@@ -614,6 +618,8 @@ module NodeJSLib {
|
||||
|
||||
ChildProcessMethodCall() {
|
||||
this = maybePromisified(DataFlow::moduleMember("child_process", methodName)).getACall()
|
||||
or
|
||||
this = DataFlow::moduleMember("mz/child_process", methodName).getACall()
|
||||
}
|
||||
|
||||
private DataFlow::Node getACommandArgument(boolean shell) {
|
||||
@@ -710,23 +716,25 @@ module NodeJSLib {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method from module `vm`
|
||||
* DEPRECATED Use `VmModuleMemberInvocation` instead.
|
||||
*/
|
||||
class VmModuleMethodCall extends DataFlow::CallNode {
|
||||
string methodName;
|
||||
deprecated class VmModuleMethodCall = VmModuleMemberInvocation;
|
||||
|
||||
VmModuleMethodCall() { this = DataFlow::moduleMember("vm", methodName).getACall() }
|
||||
/**
|
||||
* An invocation of a member from module `vm`
|
||||
*/
|
||||
class VmModuleMemberInvocation extends DataFlow::InvokeNode {
|
||||
string memberName;
|
||||
|
||||
VmModuleMemberInvocation() { this = DataFlow::moduleMember("vm", memberName).getAnInvocation() }
|
||||
|
||||
/**
|
||||
* Gets the code to be executed as part of this call.
|
||||
* Gets the code to be executed as part of this invocation.
|
||||
*/
|
||||
DataFlow::Node getACodeArgument() {
|
||||
(
|
||||
methodName = "runInContext" or
|
||||
methodName = "runInNewContext" or
|
||||
methodName = "runInThisContext"
|
||||
) and
|
||||
// all of the above methods take the command as their first argument
|
||||
memberName in ["Script", "SourceTextModule", "compileFunction", "runInContext",
|
||||
"runInNewContext", "runInThisContext"] and
|
||||
// all of the above methods/constructors take the command as their first argument
|
||||
result = getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,39 +28,44 @@ module SQL {
|
||||
* Provides classes modelling the (API compatible) `mysql` and `mysql2` packages.
|
||||
*/
|
||||
private module MySql {
|
||||
/** Gets the package name `mysql` or `mysql2`. */
|
||||
string mysql() { result = "mysql" or result = "mysql2" }
|
||||
private DataFlow::SourceNode mysql() { result = DataFlow::moduleImport(["mysql", "mysql2"]) }
|
||||
|
||||
private DataFlow::CallNode createPool() { result = mysql().getAMemberCall("createPool") }
|
||||
|
||||
/** Gets a reference to a MySQL pool. */
|
||||
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = createPool()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a MySQL pool. */
|
||||
private DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a call to `mysql.createConnection`. */
|
||||
DataFlow::SourceNode createConnection() {
|
||||
result = DataFlow::moduleMember(mysql(), "createConnection").getACall()
|
||||
}
|
||||
DataFlow::CallNode createConnection() { result = mysql().getAMemberCall("createConnection") }
|
||||
|
||||
/** Gets a call to `mysql.createPool`. */
|
||||
DataFlow::SourceNode createPool() {
|
||||
result = DataFlow::moduleMember(mysql(), "createPool").getACall()
|
||||
}
|
||||
|
||||
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
|
||||
DataFlow::SourceNode connection() {
|
||||
result = createConnection()
|
||||
/** Gets a reference to a MySQL connection instance. */
|
||||
private DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = createConnection()
|
||||
or
|
||||
result = pool().getAMethodCall("getConnection").getABoundCallbackParameter(0, 1)
|
||||
)
|
||||
or
|
||||
result = createPool().getAMethodCall("getConnection").getCallback(0).getParameter(1)
|
||||
exists(DataFlow::TypeTracker t2 | result = connection(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a MySQL connection instance. */
|
||||
DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A call to the MySql `query` method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override MethodCallExpr astNode;
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
QueryCall() { this = [pool(), connection()].getAMethodCall("query") }
|
||||
|
||||
QueryCall() {
|
||||
exists(DataFlow::SourceNode recv | recv = createPool() or recv = connection() |
|
||||
this = recv.getAMethodCall("query")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = DataFlow::valueNode(astNode.getArgument(0))
|
||||
}
|
||||
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
@@ -69,20 +74,11 @@ private module MySql {
|
||||
}
|
||||
|
||||
/** A call to the `escape` or `escapeId` method that performs SQL sanitization. */
|
||||
class EscapingSanitizer extends SQL::SqlSanitizer, @callexpr {
|
||||
class EscapingSanitizer extends SQL::SqlSanitizer, MethodCallExpr {
|
||||
EscapingSanitizer() {
|
||||
exists(string esc | esc = "escape" or esc = "escapeId" |
|
||||
exists(DataFlow::SourceNode escape, MethodCallExpr mce |
|
||||
escape = DataFlow::moduleMember(mysql(), esc) or
|
||||
escape = connection().getAPropertyRead(esc) or
|
||||
escape = createPool().getAPropertyRead(esc)
|
||||
|
|
||||
this = mce and
|
||||
mce = escape.getACall().asExpr() and
|
||||
input = mce.getArgument(0) and
|
||||
output = mce
|
||||
)
|
||||
)
|
||||
this = [mysql(), pool(), connection()].getAMethodCall(["escape", "escapeId"]).asExpr() and
|
||||
input = this.getArgument(0) and
|
||||
output = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +87,8 @@ private module MySql {
|
||||
string kind;
|
||||
|
||||
Credentials() {
|
||||
exists(DataFlow::SourceNode call, string prop |
|
||||
(call = createConnection() or call = createPool()) and
|
||||
call.asExpr().(CallExpr).hasOptionArgument(0, prop, this) and
|
||||
exists(string prop |
|
||||
this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and
|
||||
(
|
||||
prop = "user" and kind = "user name"
|
||||
or
|
||||
@@ -110,21 +105,8 @@ private module MySql {
|
||||
* Provides classes modelling the `pg` package.
|
||||
*/
|
||||
private module Postgres {
|
||||
/** Gets an expression of the form `new require('pg').Client()`. */
|
||||
DataFlow::SourceNode newClient() {
|
||||
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client")
|
||||
}
|
||||
|
||||
/** Gets a data flow node that holds a freshly created Postgres client instance. */
|
||||
DataFlow::SourceNode client() {
|
||||
result = newClient()
|
||||
or
|
||||
// pool.connect(function(err, client) { ... })
|
||||
result = newPool().getAMethodCall("connect").getCallback(0).getParameter(1)
|
||||
}
|
||||
|
||||
/** Gets an expression that constructs a new connection pool. */
|
||||
DataFlow::SourceNode newPool() {
|
||||
DataFlow::InvokeNode newPool() {
|
||||
// new require('pg').Pool()
|
||||
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Pool")
|
||||
or
|
||||
@@ -132,26 +114,42 @@ private module Postgres {
|
||||
result = DataFlow::moduleImport("pg-pool").getAnInstantiation()
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode clientOrPool(DataFlow::TypeTracker t) {
|
||||
/** Gets a data flow node referring to a connection pool. */
|
||||
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(result = client() or result = newPool())
|
||||
result = newPool()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = clientOrPool(t2).track(t2, t))
|
||||
exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t))
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode clientOrPool() {
|
||||
result = clientOrPool(DataFlow::TypeTracker::end())
|
||||
/** Gets a data flow node referring to a connection pool. */
|
||||
DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a creation of a Postgres client. */
|
||||
DataFlow::InvokeNode newClient() {
|
||||
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client")
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a Postgres client. */
|
||||
private DataFlow::SourceNode client(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = newClient()
|
||||
or
|
||||
result = pool().getAMethodCall("connect").getABoundCallbackParameter(0, 1)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = client(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a Postgres client. */
|
||||
DataFlow::SourceNode client() { result = client(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A call to the Postgres `query` method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override MethodCallExpr astNode;
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
QueryCall() { this = [client(), pool()].getAMethodCall("query") }
|
||||
|
||||
QueryCall() { this = clientOrPool().getAMethodCall("query") }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = DataFlow::valueNode(astNode.getArgument(0))
|
||||
}
|
||||
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
@@ -164,14 +162,10 @@ private module Postgres {
|
||||
string kind;
|
||||
|
||||
Credentials() {
|
||||
exists(DataFlow::InvokeNode call, string prop |
|
||||
(call = newClient() or call = newPool()) and
|
||||
this = call.getOptionArgument(0, prop).asExpr() and
|
||||
(
|
||||
prop = "user" and kind = "user name"
|
||||
or
|
||||
prop = "password" and kind = prop
|
||||
)
|
||||
exists(string prop | this = [newClient(), newPool()].getOptionArgument(0, prop).asExpr() |
|
||||
prop = "user" and kind = "user name"
|
||||
or
|
||||
prop = "password" and kind = prop
|
||||
)
|
||||
}
|
||||
|
||||
@@ -196,10 +190,19 @@ private module Sqlite {
|
||||
result = sqlite().getAConstructorInvocation("Database")
|
||||
}
|
||||
|
||||
/** A call to a Sqlite query method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override MethodCallExpr astNode;
|
||||
/** Gets a data flow node referring to a Sqlite database instance. */
|
||||
private DataFlow::SourceNode db(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = newDb()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = db(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a Sqlite database instance. */
|
||||
DataFlow::SourceNode db() { result = db(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A call to a Sqlite query method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
QueryCall() {
|
||||
exists(string meth |
|
||||
meth = "all" or
|
||||
@@ -209,13 +212,11 @@ private module Sqlite {
|
||||
meth = "prepare" or
|
||||
meth = "run"
|
||||
|
|
||||
this = newDb().getAMethodCall(meth)
|
||||
this = db().getAMethodCall(meth)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = DataFlow::valueNode(astNode.getArgument(0))
|
||||
}
|
||||
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
@@ -229,17 +230,25 @@ private module Sqlite {
|
||||
*/
|
||||
private module MsSql {
|
||||
/** Gets a reference to the `mssql` module. */
|
||||
DataFlow::ModuleImportNode mssql() { result.getPath() = "mssql" }
|
||||
DataFlow::SourceNode mssql() { result = DataFlow::moduleImport("mssql") }
|
||||
|
||||
/** Gets an expression that creates a request object. */
|
||||
DataFlow::SourceNode request() {
|
||||
// new require('mssql').Request()
|
||||
result = mssql().getAConstructorInvocation("Request")
|
||||
/** Gets a data flow node referring to a request object. */
|
||||
private DataFlow::SourceNode request(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
// new require('mssql').Request()
|
||||
result = mssql().getAConstructorInvocation("Request")
|
||||
or
|
||||
// request.input(...)
|
||||
result = request().getAMethodCall("input")
|
||||
)
|
||||
or
|
||||
// request.input(...)
|
||||
result = request().getAMethodCall("input")
|
||||
exists(DataFlow::TypeTracker t2 | result = request(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a request object. */
|
||||
DataFlow::SourceNode request() { result = request(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A tagged template evaluated as a query. */
|
||||
private class QueryTemplateExpr extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override TaggedTemplateExpr astNode;
|
||||
@@ -252,16 +261,10 @@ private module MsSql {
|
||||
}
|
||||
|
||||
/** A call to a MsSql query method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override MethodCallExpr astNode;
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
QueryCall() { this = request().getAMethodCall(["query", "batch"]) }
|
||||
|
||||
QueryCall() {
|
||||
exists(string meth | this = request().getAMethodCall(meth) | meth = "query" or meth = "batch")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = DataFlow::valueNode(astNode.getArgument(0))
|
||||
}
|
||||
override DataFlow::Node getAQueryArgument() { result = getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to a method that interprets it as SQL. */
|
||||
@@ -310,17 +313,22 @@ private module MsSql {
|
||||
* Provides classes modelling the `sequelize` package.
|
||||
*/
|
||||
private module Sequelize {
|
||||
/** Gets an import of the `sequelize` module. */
|
||||
DataFlow::ModuleImportNode sequelize() { result.getPath() = "sequelize" }
|
||||
/** Gets a node referring to an instance of the `Sequelize` class. */
|
||||
private DataFlow::SourceNode sequelize(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::moduleImport("sequelize").getAnInstantiation()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = sequelize(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets an expression that creates an instance of the `Sequelize` class. */
|
||||
DataFlow::SourceNode newSequelize() { result = sequelize().getAnInstantiation() }
|
||||
/** Gets a node referring to an instance of the `Sequelize` class. */
|
||||
DataFlow::SourceNode sequelize() { result = sequelize(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** A call to `Sequelize.query`. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
|
||||
override MethodCallExpr astNode;
|
||||
|
||||
QueryCall() { this = newSequelize().getAMethodCall("query") }
|
||||
QueryCall() { this = sequelize().getAMethodCall("query") }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = DataFlow::valueNode(astNode.getArgument(0))
|
||||
@@ -341,7 +349,7 @@ private module Sequelize {
|
||||
|
||||
Credentials() {
|
||||
exists(NewExpr ne, string prop |
|
||||
ne = newSequelize().asExpr() and
|
||||
ne = sequelize().asExpr() and
|
||||
(
|
||||
this = ne.getArgument(1) and prop = "username"
|
||||
or
|
||||
@@ -376,27 +384,61 @@ private module Spanner {
|
||||
result = DataFlow::moduleMember("@google-cloud/spanner", "Spanner")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to an instance of the `Database` class.
|
||||
*/
|
||||
DataFlow::SourceNode database() {
|
||||
result = spanner().getAnInvocation().getAMethodCall("instance").getAMethodCall("database")
|
||||
/** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */
|
||||
private DataFlow::SourceNode spannerNew(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = spanner().getAnInvocation()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = spannerNew(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to an instance of the `v1.SpannerClient` class.
|
||||
*/
|
||||
DataFlow::SourceNode v1SpannerClient() {
|
||||
/** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */
|
||||
DataFlow::SourceNode spannerNew() { result = spannerNew(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a data flow node referring to the result of `.instance()`. */
|
||||
private DataFlow::SourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = spannerNew().getAMethodCall("instance")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to the result of `.instance()`. */
|
||||
DataFlow::SourceNode instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a node that refers to an instance of the `Database` class. */
|
||||
private DataFlow::SourceNode database(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = instance().getAMethodCall("database")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = database(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a node that refers to an instance of the `Database` class. */
|
||||
DataFlow::SourceNode database() { result = database(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a node that refers to an instance of the `v1.SpannerClient` class. */
|
||||
private DataFlow::SourceNode v1SpannerClient(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = spanner().getAPropertyRead("v1").getAPropertyRead("SpannerClient").getAnInstantiation()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = v1SpannerClient(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that refers to a transaction object.
|
||||
*/
|
||||
DataFlow::SourceNode transaction() {
|
||||
result = database().getAMethodCall("runTransaction").getCallback(0).getParameter(1)
|
||||
/** Gets a node that refers to an instance of the `v1.SpannerClient` class. */
|
||||
DataFlow::SourceNode v1SpannerClient() { result = v1SpannerClient(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a node that refers to a transaction object. */
|
||||
private DataFlow::SourceNode transaction(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = database().getAMethodCall("runTransaction").getABoundCallbackParameter(0, 1)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = transaction(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a node that refers to a transaction object. */
|
||||
DataFlow::SourceNode transaction() { result = transaction(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* A call to a Spanner method that executes a SQL query.
|
||||
*/
|
||||
@@ -418,9 +460,7 @@ private module Spanner {
|
||||
*/
|
||||
class DatabaseRunCall extends SqlExecution {
|
||||
DatabaseRunCall() {
|
||||
exists(string run | run = "run" or run = "runPartitionedUpdate" or run = "runStream" |
|
||||
this = database().getAMethodCall(run)
|
||||
)
|
||||
this = database().getAMethodCall(["run", "runPartitionedUpdate", "runStream"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,11 +468,7 @@ private module Spanner {
|
||||
* A call to `Transaction.run`, `Transaction.runStream` or `Transaction.runUpdate`.
|
||||
*/
|
||||
class TransactionRunCall extends SqlExecution {
|
||||
TransactionRunCall() {
|
||||
exists(string run | run = "run" or run = "runStream" or run = "runUpdate" |
|
||||
this = transaction().getAMethodCall(run)
|
||||
)
|
||||
}
|
||||
TransactionRunCall() { this = transaction().getAMethodCall(["run", "runStream", "runUpdate"]) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,9 +476,7 @@ private module Spanner {
|
||||
*/
|
||||
class ExecuteSqlCall extends SqlExecution {
|
||||
ExecuteSqlCall() {
|
||||
exists(string exec | exec = "executeSql" or exec = "executeStreamingSql" |
|
||||
this = v1SpannerClient().getAMethodCall(exec)
|
||||
)
|
||||
this = v1SpannerClient().getAMethodCall(["executeSql", "executeStreamingSql"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
|
||||
@@ -51,13 +51,9 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
|
||||
)
|
||||
or
|
||||
shell = true and
|
||||
(
|
||||
mod = "exec" and
|
||||
optionsArg = -2 and
|
||||
cmdArg = 0
|
||||
or
|
||||
mod = "remote-exec" and cmdArg = 1 and optionsArg = -1
|
||||
)
|
||||
mod = "exec" and
|
||||
optionsArg = -2 and
|
||||
cmdArg = 0
|
||||
) and
|
||||
callee = DataFlow::moduleImport(mod)
|
||||
|
|
||||
@@ -97,3 +93,33 @@ private boolean getSync(string name) {
|
||||
then result = true
|
||||
else result = false
|
||||
}
|
||||
|
||||
private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::InvokeNode {
|
||||
int cmdArg;
|
||||
|
||||
RemoteCommandExecutor() {
|
||||
this = DataFlow::moduleImport("remote-exec").getACall() and
|
||||
cmdArg = 1
|
||||
or
|
||||
exists(DataFlow::SourceNode ssh2, DataFlow::SourceNode client |
|
||||
ssh2 = DataFlow::moduleImport("ssh2") and
|
||||
(client = ssh2 or client = ssh2.getAPropertyRead("Client")) and
|
||||
this = client.getAnInstantiation().getAMethodCall("exec") and
|
||||
cmdArg = 0
|
||||
)
|
||||
or
|
||||
exists(DataFlow::SourceNode ssh2stream |
|
||||
ssh2stream = DataFlow::moduleMember("ssh2-streams", "SSH2Stream") and
|
||||
this = ssh2stream.getAnInstantiation().getAMethodCall("exec") and
|
||||
cmdArg = 1
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getACommandArgument() { result = getArgument(cmdArg) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() }
|
||||
|
||||
override predicate isSync() { none() }
|
||||
|
||||
override DataFlow::Node getOptionsArg() { none() }
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@ module CodeInjection {
|
||||
* `vm` module.
|
||||
*/
|
||||
class NodeJSVmSink extends Sink, DataFlow::ValueNode {
|
||||
NodeJSVmSink() { exists(NodeJSLib::VmModuleMethodCall call | this = call.getACodeArgument()) }
|
||||
NodeJSVmSink() {
|
||||
exists(NodeJSLib::VmModuleMemberInvocation inv | this = inv.getACodeArgument())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,10 @@ module DomBasedXss {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
override predicate isAdditionalLoadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string predProp, string succProp
|
||||
) {
|
||||
@@ -47,6 +51,10 @@ module DomBasedXss {
|
||||
prop = urlSuffixPseudoProperty()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
private string urlSuffixPseudoProperty() { result = "$UrlSuffix$" }
|
||||
|
||||
@@ -51,8 +51,11 @@ module IncompleteHtmlAttributeSanitization {
|
||||
string lhs;
|
||||
|
||||
HtmlAttributeConcatenation() {
|
||||
lhs = this.getPreviousLeaf().getStringValue().regexpCapture("(.*)=\"[^\"]*", 1) and
|
||||
this.getNextLeaf().getStringValue().regexpMatch(".*\".*")
|
||||
lhs = this.getPreviousLeaf().getStringValue().regexpCapture("(?s)(.*)=\"[^\"]*", 1) and
|
||||
(
|
||||
this.getNextLeaf().getStringValue().regexpMatch(".*\".*") or
|
||||
this instanceof StringOps::HtmlConcatenationLeaf
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow::
|
||||
result = pred.backtrack(t2, t)
|
||||
or
|
||||
t = t2.continue() and
|
||||
TaintTracking::arrayFunctionTaintStep(result, pred, _)
|
||||
TaintTracking::arrayFunctionTaintStep(any(DataFlow::Node n | result.flowsTo(n)), pred, _)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,5 +22,9 @@ module ReflectedXss {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ module StoredXss {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
/** A file name, considered as a flow source for stored XSS. */
|
||||
|
||||
@@ -32,227 +32,14 @@ module TaintedPath {
|
||||
}
|
||||
|
||||
override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
||||
guard instanceof StartsWithDotDotSanitizer or
|
||||
guard instanceof StartsWithDirSanitizer or
|
||||
guard instanceof IsAbsoluteSanitizer or
|
||||
guard instanceof ContainsDotDotSanitizer or
|
||||
guard instanceof RelativePathStartsWithSanitizer or
|
||||
guard instanceof IsInsideCheckSanitizer
|
||||
guard instanceof BarrierGuardNode
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
|
||||
DataFlow::FlowLabel dstlabel
|
||||
) {
|
||||
isTaintedPathStep(src, dst, srclabel, dstlabel)
|
||||
or
|
||||
// Ignore all preliminary sanitization after decoding URI components
|
||||
srclabel instanceof Label::PosixPath and
|
||||
dstlabel instanceof Label::PosixPath and
|
||||
(
|
||||
any(UriLibraryStep step).step(src, dst)
|
||||
or
|
||||
exists(DataFlow::CallNode decode |
|
||||
decode.getCalleeName() = "decodeURIComponent" or decode.getCalleeName() = "decodeURI"
|
||||
|
|
||||
src = decode.getArgument(0) and
|
||||
dst = decode
|
||||
)
|
||||
)
|
||||
or
|
||||
promiseTaintStep(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
any(TaintTracking::PersistentStorageTaintStep st).step(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
exists(DataFlow::PropRead read | read = dst |
|
||||
src = read.getBase() and
|
||||
read.getPropertyName() != "length" and
|
||||
srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// string method calls of interest
|
||||
exists(DataFlow::MethodCallNode mcn, string name |
|
||||
srclabel = dstlabel and dst = mcn and mcn.calls(src, name)
|
||||
|
|
||||
exists(string substringMethodName |
|
||||
substringMethodName = "substr" or
|
||||
substringMethodName = "substring" or
|
||||
substringMethodName = "slice"
|
||||
|
|
||||
name = substringMethodName and
|
||||
// to avoid very dynamic transformations, require at least one fixed index
|
||||
exists(mcn.getAnArgument().asExpr().getIntValue())
|
||||
)
|
||||
or
|
||||
exists(string argumentlessMethodName |
|
||||
argumentlessMethodName = "toLocaleLowerCase" or
|
||||
argumentlessMethodName = "toLocaleUpperCase" or
|
||||
argumentlessMethodName = "toLowerCase" or
|
||||
argumentlessMethodName = "toUpperCase" or
|
||||
argumentlessMethodName = "trim" or
|
||||
argumentlessMethodName = "trimLeft" or
|
||||
argumentlessMethodName = "trimRight"
|
||||
|
|
||||
name = argumentlessMethodName
|
||||
)
|
||||
)
|
||||
or
|
||||
// A `str.split()` call can either split into path elements (`str.split("/")`) or split by some other string.
|
||||
exists(StringSplitCall mcn | dst = mcn and mcn.getBaseString() = src |
|
||||
if mcn.getSeparator() = "/"
|
||||
then
|
||||
srclabel.(Label::PosixPath).canContainDotDotSlash() and
|
||||
dstlabel instanceof Label::SplitPath
|
||||
else srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// array method calls of interest
|
||||
exists(DataFlow::MethodCallNode mcn, string name | dst = mcn and mcn.calls(src, name) |
|
||||
(
|
||||
name = "pop" or
|
||||
name = "shift"
|
||||
) and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
or
|
||||
(
|
||||
name = "slice" or
|
||||
name = "splice" or
|
||||
name = "concat"
|
||||
) and
|
||||
dstlabel instanceof Label::SplitPath and
|
||||
srclabel instanceof Label::SplitPath
|
||||
or
|
||||
name = "join" and
|
||||
mcn.getArgument(0).mayHaveStringValue("/") and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
)
|
||||
or
|
||||
// prefix.concat(path)
|
||||
exists(DataFlow::MethodCallNode mcn |
|
||||
mcn.getMethodName() = "concat" and mcn.getAnArgument() = src
|
||||
|
|
||||
dst = mcn and
|
||||
dstlabel instanceof Label::SplitPath and
|
||||
srclabel instanceof Label::SplitPath
|
||||
)
|
||||
or
|
||||
// reading unknown property of split path
|
||||
exists(DataFlow::PropRead read | read = dst |
|
||||
src = read.getBase() and
|
||||
not read.getPropertyName() = "length" and
|
||||
not exists(read.getPropertyNameExpr().getIntValue()) and
|
||||
// split[split.length - 1]
|
||||
not exists(BinaryExpr binop |
|
||||
read.getPropertyNameExpr() = binop and
|
||||
binop.getAnOperand().getIntValue() = 1 and
|
||||
binop.getAnOperand().(PropAccess).getPropertyName() = "length"
|
||||
) and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if we should include a step from `src -> dst` with labels `srclabel -> dstlabel`, and the
|
||||
* standard taint step `src -> dst` should be suppresesd.
|
||||
*/
|
||||
predicate isTaintedPathStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, Label::PosixPath srclabel, Label::PosixPath dstlabel
|
||||
) {
|
||||
// path.normalize() and similar
|
||||
exists(NormalizingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel = srclabel.toNormalized()
|
||||
)
|
||||
or
|
||||
// path.resolve() and similar
|
||||
exists(ResolvingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel.isAbsolute() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// path.relative() and similar
|
||||
exists(NormalizingRelativePathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel.isRelative() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// path.dirname() and similar
|
||||
exists(PreservingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// foo.replace(/\./, "") and similar
|
||||
exists(DotRemovingReplaceCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
srclabel.isAbsolute() and
|
||||
dstlabel.isAbsolute() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// foo.replace(/(\.\.\/)*/, "") and similar
|
||||
exists(DotDotSlashPrefixRemovingReplace call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput()
|
||||
|
|
||||
// the 4 possible combinations of normalized + relative for `srclabel`, and the possible values for `dstlabel` in each case.
|
||||
srclabel.isNonNormalized() and srclabel.isRelative() // raw + relative -> any()
|
||||
or
|
||||
srclabel.isNormalized() and srclabel.isAbsolute() and srclabel = dstlabel // normalized + absolute -> normalized + absolute
|
||||
or
|
||||
srclabel.isNonNormalized() and srclabel.isAbsolute() and dstlabel.isAbsolute() // raw + absolute -> raw/normalized + absolute
|
||||
// normalized + relative -> none()
|
||||
)
|
||||
or
|
||||
// path.join()
|
||||
exists(DataFlow::CallNode join, int n |
|
||||
join = NodeJSLib::Path::moduleMember("join").getACall()
|
||||
|
|
||||
src = join.getArgument(n) and
|
||||
dst = join and
|
||||
(
|
||||
// If the initial argument is tainted, just normalize it. It can be relative or absolute.
|
||||
n = 0 and
|
||||
dstlabel = srclabel.toNormalized()
|
||||
or
|
||||
// For later arguments, the flow label depends on whether the first argument is absolute or relative.
|
||||
// If in doubt, we assume it is absolute.
|
||||
n > 0 and
|
||||
srclabel.canContainDotDotSlash() and
|
||||
dstlabel.isNormalized() and
|
||||
if isRelative(join.getArgument(0).getStringValue())
|
||||
then dstlabel.isRelative()
|
||||
else dstlabel.isAbsolute()
|
||||
)
|
||||
)
|
||||
or
|
||||
// String concatenation - behaves like path.join() except without normalization
|
||||
exists(DataFlow::Node operator, int n |
|
||||
StringConcatenation::taintStep(src, dst, operator, n)
|
||||
|
|
||||
// use ordinary taint flow for the first operand
|
||||
n = 0 and
|
||||
srclabel = dstlabel
|
||||
or
|
||||
n > 0 and
|
||||
srclabel.canContainDotDotSlash() and
|
||||
dstlabel.isNonNormalized() and // The ../ is no longer at the beginning of the string.
|
||||
(
|
||||
if isRelative(StringConcatenation::getOperand(operator, 0).getStringValue())
|
||||
then dstlabel.isRelative()
|
||||
else dstlabel.isAbsolute()
|
||||
)
|
||||
)
|
||||
isAdditionalTaintedPathFlowStep(src, dst, srclabel, dstlabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ module TaintedPath {
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A barrier guard for tainted-path vulnerabilities.
|
||||
*/
|
||||
abstract class BarrierGuardNode extends DataFlow::LabeledBarrierGuardNode { }
|
||||
|
||||
module Label {
|
||||
/**
|
||||
* A string indicating if a path is normalized, that is, whether internal `../` components
|
||||
@@ -343,7 +348,7 @@ module TaintedPath {
|
||||
*
|
||||
* This is relevant for paths that are known to be normalized.
|
||||
*/
|
||||
class StartsWithDotDotSanitizer extends DataFlow::LabeledBarrierGuardNode {
|
||||
class StartsWithDotDotSanitizer extends BarrierGuardNode {
|
||||
StringOps::StartsWith startsWith;
|
||||
|
||||
StartsWithDotDotSanitizer() {
|
||||
@@ -369,7 +374,7 @@ module TaintedPath {
|
||||
* A check of form `x.startsWith(dir)` that sanitizes normalized absolute paths, since it is then
|
||||
* known to be in a subdirectory of `dir`.
|
||||
*/
|
||||
class StartsWithDirSanitizer extends DataFlow::LabeledBarrierGuardNode {
|
||||
class StartsWithDirSanitizer extends BarrierGuardNode {
|
||||
StringOps::StartsWith startsWith;
|
||||
|
||||
StartsWithDirSanitizer() {
|
||||
@@ -393,7 +398,7 @@ module TaintedPath {
|
||||
* A call to `path.isAbsolute` as a sanitizer for relative paths in true branch,
|
||||
* and a sanitizer for absolute paths in the false branch.
|
||||
*/
|
||||
class IsAbsoluteSanitizer extends DataFlow::LabeledBarrierGuardNode {
|
||||
class IsAbsoluteSanitizer extends BarrierGuardNode {
|
||||
DataFlow::Node operand;
|
||||
boolean polarity;
|
||||
boolean negatable;
|
||||
@@ -429,7 +434,7 @@ module TaintedPath {
|
||||
/**
|
||||
* An expression of form `x.includes("..")` or similar.
|
||||
*/
|
||||
class ContainsDotDotSanitizer extends DataFlow::LabeledBarrierGuardNode {
|
||||
class ContainsDotDotSanitizer extends BarrierGuardNode {
|
||||
StringOps::Includes contains;
|
||||
|
||||
ContainsDotDotSanitizer() {
|
||||
@@ -465,7 +470,7 @@ module TaintedPath {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class RelativePathStartsWithSanitizer extends DataFlow::BarrierGuardNode {
|
||||
class RelativePathStartsWithSanitizer extends BarrierGuardNode {
|
||||
StringOps::StartsWith startsWith;
|
||||
DataFlow::CallNode pathCall;
|
||||
string member;
|
||||
@@ -507,7 +512,7 @@ module TaintedPath {
|
||||
* An expression of form `isInside(x, y)` or similar, where `isInside` is
|
||||
* a library check for the relation between `x` and `y`.
|
||||
*/
|
||||
class IsInsideCheckSanitizer extends DataFlow::LabeledBarrierGuardNode {
|
||||
class IsInsideCheckSanitizer extends BarrierGuardNode {
|
||||
DataFlow::Node checked;
|
||||
boolean onlyNormalizedAbsolutePaths;
|
||||
|
||||
@@ -614,4 +619,220 @@ module TaintedPath {
|
||||
class SendPathSink extends Sink, DataFlow::ValueNode {
|
||||
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
|
||||
*/
|
||||
predicate isAdditionalTaintedPathFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
|
||||
DataFlow::FlowLabel dstlabel
|
||||
) {
|
||||
isPosixPathStep(src, dst, srclabel, dstlabel)
|
||||
or
|
||||
// Ignore all preliminary sanitization after decoding URI components
|
||||
srclabel instanceof Label::PosixPath and
|
||||
dstlabel instanceof Label::PosixPath and
|
||||
(
|
||||
any(UriLibraryStep step).step(src, dst)
|
||||
or
|
||||
exists(DataFlow::CallNode decode |
|
||||
decode.getCalleeName() = "decodeURIComponent" or decode.getCalleeName() = "decodeURI"
|
||||
|
|
||||
src = decode.getArgument(0) and
|
||||
dst = decode
|
||||
)
|
||||
)
|
||||
or
|
||||
promiseTaintStep(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
any(TaintTracking::PersistentStorageTaintStep st).step(src, dst) and srclabel = dstlabel
|
||||
or
|
||||
exists(DataFlow::PropRead read | read = dst |
|
||||
src = read.getBase() and
|
||||
read.getPropertyName() != "length" and
|
||||
srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// string method calls of interest
|
||||
exists(DataFlow::MethodCallNode mcn, string name |
|
||||
srclabel = dstlabel and dst = mcn and mcn.calls(src, name)
|
||||
|
|
||||
exists(string substringMethodName |
|
||||
substringMethodName = "substr" or
|
||||
substringMethodName = "substring" or
|
||||
substringMethodName = "slice"
|
||||
|
|
||||
name = substringMethodName and
|
||||
// to avoid very dynamic transformations, require at least one fixed index
|
||||
exists(mcn.getAnArgument().asExpr().getIntValue())
|
||||
)
|
||||
or
|
||||
exists(string argumentlessMethodName |
|
||||
argumentlessMethodName = "toLocaleLowerCase" or
|
||||
argumentlessMethodName = "toLocaleUpperCase" or
|
||||
argumentlessMethodName = "toLowerCase" or
|
||||
argumentlessMethodName = "toUpperCase" or
|
||||
argumentlessMethodName = "trim" or
|
||||
argumentlessMethodName = "trimLeft" or
|
||||
argumentlessMethodName = "trimRight"
|
||||
|
|
||||
name = argumentlessMethodName
|
||||
)
|
||||
)
|
||||
or
|
||||
// A `str.split()` call can either split into path elements (`str.split("/")`) or split by some other string.
|
||||
exists(StringSplitCall mcn | dst = mcn and mcn.getBaseString() = src |
|
||||
if mcn.getSeparator() = "/"
|
||||
then
|
||||
srclabel.(Label::PosixPath).canContainDotDotSlash() and
|
||||
dstlabel instanceof Label::SplitPath
|
||||
else srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// array method calls of interest
|
||||
exists(DataFlow::MethodCallNode mcn, string name | dst = mcn and mcn.calls(src, name) |
|
||||
(
|
||||
name = "pop" or
|
||||
name = "shift"
|
||||
) and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
or
|
||||
(
|
||||
name = "slice" or
|
||||
name = "splice" or
|
||||
name = "concat"
|
||||
) and
|
||||
dstlabel instanceof Label::SplitPath and
|
||||
srclabel instanceof Label::SplitPath
|
||||
or
|
||||
name = "join" and
|
||||
mcn.getArgument(0).mayHaveStringValue("/") and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
)
|
||||
or
|
||||
// prefix.concat(path)
|
||||
exists(DataFlow::MethodCallNode mcn |
|
||||
mcn.getMethodName() = "concat" and mcn.getAnArgument() = src
|
||||
|
|
||||
dst = mcn and
|
||||
dstlabel instanceof Label::SplitPath and
|
||||
srclabel instanceof Label::SplitPath
|
||||
)
|
||||
or
|
||||
// reading unknown property of split path
|
||||
exists(DataFlow::PropRead read | read = dst |
|
||||
src = read.getBase() and
|
||||
not read.getPropertyName() = "length" and
|
||||
not exists(read.getPropertyNameExpr().getIntValue()) and
|
||||
// split[split.length - 1]
|
||||
not exists(BinaryExpr binop |
|
||||
read.getPropertyNameExpr() = binop and
|
||||
binop.getAnOperand().getIntValue() = 1 and
|
||||
binop.getAnOperand().(PropAccess).getPropertyName() = "length"
|
||||
) and
|
||||
srclabel instanceof Label::SplitPath and
|
||||
dstlabel.(Label::PosixPath).canContainDotDotSlash()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if we should include a step from `src -> dst` with labels `srclabel -> dstlabel`, and the
|
||||
* standard taint step `src -> dst` should be suppresesd.
|
||||
*/
|
||||
private predicate isPosixPathStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, Label::PosixPath srclabel, Label::PosixPath dstlabel
|
||||
) {
|
||||
// path.normalize() and similar
|
||||
exists(NormalizingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel = srclabel.toNormalized()
|
||||
)
|
||||
or
|
||||
// path.resolve() and similar
|
||||
exists(ResolvingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel.isAbsolute() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// path.relative() and similar
|
||||
exists(NormalizingRelativePathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
dstlabel.isRelative() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// path.dirname() and similar
|
||||
exists(PreservingPathCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
srclabel = dstlabel
|
||||
)
|
||||
or
|
||||
// foo.replace(/\./, "") and similar
|
||||
exists(DotRemovingReplaceCall call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput() and
|
||||
srclabel.isAbsolute() and
|
||||
dstlabel.isAbsolute() and
|
||||
dstlabel.isNormalized()
|
||||
)
|
||||
or
|
||||
// foo.replace(/(\.\.\/)*/, "") and similar
|
||||
exists(DotDotSlashPrefixRemovingReplace call |
|
||||
src = call.getInput() and
|
||||
dst = call.getOutput()
|
||||
|
|
||||
// the 4 possible combinations of normalized + relative for `srclabel`, and the possible values for `dstlabel` in each case.
|
||||
srclabel.isNonNormalized() and srclabel.isRelative() // raw + relative -> any()
|
||||
or
|
||||
srclabel.isNormalized() and srclabel.isAbsolute() and srclabel = dstlabel // normalized + absolute -> normalized + absolute
|
||||
or
|
||||
srclabel.isNonNormalized() and srclabel.isAbsolute() and dstlabel.isAbsolute() // raw + absolute -> raw/normalized + absolute
|
||||
// normalized + relative -> none()
|
||||
)
|
||||
or
|
||||
// path.join()
|
||||
exists(DataFlow::CallNode join, int n |
|
||||
join = NodeJSLib::Path::moduleMember("join").getACall()
|
||||
|
|
||||
src = join.getArgument(n) and
|
||||
dst = join and
|
||||
(
|
||||
// If the initial argument is tainted, just normalize it. It can be relative or absolute.
|
||||
n = 0 and
|
||||
dstlabel = srclabel.toNormalized()
|
||||
or
|
||||
// For later arguments, the flow label depends on whether the first argument is absolute or relative.
|
||||
// If in doubt, we assume it is absolute.
|
||||
n > 0 and
|
||||
srclabel.canContainDotDotSlash() and
|
||||
dstlabel.isNormalized() and
|
||||
if isRelative(join.getArgument(0).getStringValue())
|
||||
then dstlabel.isRelative()
|
||||
else dstlabel.isAbsolute()
|
||||
)
|
||||
)
|
||||
or
|
||||
// String concatenation - behaves like path.join() except without normalization
|
||||
exists(DataFlow::Node operator, int n | StringConcatenation::taintStep(src, dst, operator, n) |
|
||||
// use ordinary taint flow for the first operand
|
||||
n = 0 and
|
||||
srclabel = dstlabel
|
||||
or
|
||||
n > 0 and
|
||||
srclabel.canContainDotDotSlash() and
|
||||
dstlabel.isNonNormalized() and // The ../ is no longer at the beginning of the string.
|
||||
(
|
||||
if isRelative(StringConcatenation::getOperand(operator, 0).getStringValue())
|
||||
then dstlabel.isRelative()
|
||||
else dstlabel.isAbsolute()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about shell command
|
||||
* constructed from library input vulnerabilities (CWE-078).
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `UnsafeShellCommandConstruction::Configuration` is needed, otherwise
|
||||
* `UnsafeShellCommandConstructionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Classes and predicates for the shell command constructed from library input query.
|
||||
*/
|
||||
module UnsafeShellCommandConstruction {
|
||||
import UnsafeShellCommandConstructionCustomizations::UnsafeShellCommandConstruction
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about shell command constructed from library input vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeShellCommandConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof PathExistsSanitizerGuard or
|
||||
guard instanceof TaintTracking::AdHocWhitelistCheckSanitizer
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* shell command constructed from library input vulnerabilities,
|
||||
* as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.security.dataflow.RemoteFlowSources
|
||||
private import semmle.javascript.PackageExports as Exports
|
||||
|
||||
/**
|
||||
* Module containing sources, sinks, and sanitizers for shell command constructed from library input.
|
||||
*/
|
||||
module UnsafeShellCommandConstruction {
|
||||
import IndirectCommandArgument
|
||||
import semmle.javascript.security.IncompleteBlacklistSanitizer as IncompleteBlacklistSanitizer
|
||||
|
||||
/**
|
||||
* A data flow source for shell command constructed from library input.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for shell command constructed from library input.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a description how the shell command is constructed for this sink.
|
||||
*/
|
||||
abstract string getSinkType();
|
||||
|
||||
/**
|
||||
* Gets the dataflow node that executes the shell command.
|
||||
*/
|
||||
abstract SystemCommandExecution getCommandExecution();
|
||||
|
||||
/**
|
||||
* Gets the node that should be highlighted for this sink.
|
||||
* E.g. for a string concatenation, the sink is one of the leaves and the highlight is the concatenation root.
|
||||
*/
|
||||
abstract DataFlow::Node getAlertLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for shell command constructed from library input.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A parameter of an exported function, seen as a source for shell command constructed from library input.
|
||||
*/
|
||||
class ExternalInputSource extends Source, DataFlow::ParameterNode {
|
||||
ExternalInputSource() {
|
||||
this =
|
||||
Exports::getAValueExportedBy(Exports::getTopmostPackageJSON())
|
||||
.(DataFlow::FunctionNode)
|
||||
.getAParameter() and
|
||||
not this.getName() = ["cmd", "command"] // looks to be on purpose.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is later executed as a shell command in the command execution `sys`.
|
||||
*/
|
||||
private DataFlow::Node isExecutedAsShellCommand(
|
||||
DataFlow::TypeBackTracker t, SystemCommandExecution sys
|
||||
) {
|
||||
t.start() and result = sys.getACommandArgument() and sys.isShellInterpreted(result)
|
||||
or
|
||||
t.start() and isIndirectCommandArgument(result, sys)
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
t2 = t.smallstep(result, isExecutedAsShellCommand(t2, sys))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A string concatenation that is later executed as a shell command.
|
||||
*/
|
||||
class StringConcatEndingInCommandExecutionSink extends Sink, StringOps::ConcatenationLeaf {
|
||||
SystemCommandExecution sys;
|
||||
StringOps::ConcatenationRoot root;
|
||||
|
||||
StringConcatEndingInCommandExecutionSink() {
|
||||
this = root.getALeaf() and
|
||||
root = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
|
||||
exists(string prev | prev = this.getPreviousLeaf().getStringValue() |
|
||||
prev.regexpMatch(".* ('|\")?[0-9a-zA-Z/]*")
|
||||
)
|
||||
}
|
||||
|
||||
override string getSinkType() { result = "String concatenation" }
|
||||
|
||||
override SystemCommandExecution getCommandExecution() { result = sys }
|
||||
|
||||
override DataFlow::Node getAlertLocation() { result = root }
|
||||
}
|
||||
|
||||
/**
|
||||
* An element pushed to an array, where the array is later used to execute a shell command.
|
||||
*/
|
||||
class ArrayAppendEndingInCommandExecutinSink extends Sink {
|
||||
DataFlow::SourceNode array;
|
||||
SystemCommandExecution sys;
|
||||
|
||||
ArrayAppendEndingInCommandExecutinSink() {
|
||||
this =
|
||||
[array.(DataFlow::ArrayCreationNode).getAnElement(),
|
||||
array.getAMethodCall(["push", "unshift"]).getAnArgument()] and
|
||||
exists(DataFlow::MethodCallNode joinCall | array.getAMethodCall("join") = joinCall |
|
||||
joinCall = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
|
||||
joinCall.getNumArgument() = 1 and
|
||||
joinCall.getArgument(0).getStringValue() = " "
|
||||
)
|
||||
}
|
||||
|
||||
override string getSinkType() { result = "Array element" }
|
||||
|
||||
override SystemCommandExecution getCommandExecution() { result = sys }
|
||||
|
||||
override DataFlow::Node getAlertLocation() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A formatted string that is later executed as a shell command.
|
||||
*/
|
||||
class FormatedStringInCommandExecutionSink extends Sink {
|
||||
PrintfStyleCall call;
|
||||
SystemCommandExecution sys;
|
||||
|
||||
FormatedStringInCommandExecutionSink() {
|
||||
this = call.getFormatArgument(_) and
|
||||
call = isExecutedAsShellCommand(DataFlow::TypeBackTracker::end(), sys) and
|
||||
exists(string formatString | call.getFormatString().mayHaveStringValue(formatString) |
|
||||
formatString.regexpMatch(".* ('|\")?[0-9a-zA-Z/]*%.*")
|
||||
)
|
||||
}
|
||||
|
||||
override string getSinkType() { result = "Formatted string" }
|
||||
|
||||
override SystemCommandExecution getCommandExecution() { result = sys }
|
||||
|
||||
override DataFlow::Node getAlertLocation() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer like: "'"+name.replace(/'/g,"'\\''")+"'"
|
||||
* Which sanitizes on Unix.
|
||||
* The sanitizer is only safe if sorounded by single-quotes, which is assumed.
|
||||
*/
|
||||
class ReplaceQuotesSanitizer extends Sanitizer, StringReplaceCall {
|
||||
ReplaceQuotesSanitizer() {
|
||||
this.getAReplacedString() = "'" and
|
||||
this.isGlobal() and
|
||||
this.getRawReplacement().mayHaveStringValue(["'\\''", ""])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A chain of replace calls that replaces all unsafe chars for shell-commands.
|
||||
*/
|
||||
class ChainSanitizer extends Sanitizer, IncompleteBlacklistSanitizer::StringReplaceCallSequence {
|
||||
ChainSanitizer() {
|
||||
forall(string char |
|
||||
char = ["&", "`", "$", "|", ">", "<", "#", ";", "(", ")", "[", "]", "\n"]
|
||||
|
|
||||
this.getAMember().getAReplacedString() = char
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer that sanitizers paths that exist in the file-system.
|
||||
* For example: `x` is sanitized in `fs.existsSync(x)` or `fs.existsSync(x + "/suffix/path")`.
|
||||
*/
|
||||
class PathExistsSanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::CallNode {
|
||||
PathExistsSanitizerGuard() {
|
||||
this = DataFlow::moduleMember("path", "exist").getACall() or
|
||||
this = DataFlow::moduleMember("fs", "existsSync").getACall()
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
(
|
||||
e = getArgument(0).asExpr() or
|
||||
e = getArgument(0).(StringOps::ConcatenationRoot).getALeaf().asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ module Shared {
|
||||
/** A sanitizer for XSS vulnerabilities. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer guard for XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode { }
|
||||
|
||||
/**
|
||||
* A regexp replacement involving an HTML meta-character, viewed as a sanitizer for
|
||||
* XSS vulnerabilities.
|
||||
@@ -51,6 +54,85 @@ module Shared {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private import semmle.javascript.security.dataflow.IncompleteHtmlAttributeSanitizationCustomizations::IncompleteHtmlAttributeSanitization as IncompleteHTML
|
||||
|
||||
/**
|
||||
* A guard that checks if a string can contain quotes, which is a guard for strings that are inside a HTML attribute.
|
||||
*/
|
||||
class QuoteGuard extends SanitizerGuard, StringOps::Includes {
|
||||
QuoteGuard() {
|
||||
this.getSubstring().mayHaveStringValue("\"") and
|
||||
this
|
||||
.getBaseString()
|
||||
.getALocalSource()
|
||||
.flowsTo(any(IncompleteHTML::HtmlAttributeConcatenation attributeConcat))
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
e = this.getBaseString().getEnclosingExpr() and outcome = this.getPolarity().booleanNot()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer guard that checks for the existence of HTML chars in a string.
|
||||
* E.g. `/["'&<>]/.exec(str)`.
|
||||
*/
|
||||
class ContainsHTMLGuard extends SanitizerGuard, DataFlow::MethodCallNode {
|
||||
DataFlow::RegExpCreationNode regExp;
|
||||
|
||||
ContainsHTMLGuard() {
|
||||
this.getMethodName() = ["test", "exec"] and
|
||||
this.getReceiver().getALocalSource() = regExp and
|
||||
regExp.getRoot() instanceof RegExpCharacterClass and
|
||||
forall(string s | s = ["\"", "&", "<", ">"] | regExp.getRoot().getAMatchedString() = s)
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = false and e = this.getArgument(0).asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `str` is used in a switch-case that has cases matching HTML escaping.
|
||||
*/
|
||||
private predicate isUsedInHTMLEscapingSwitch(Expr str) {
|
||||
exists(SwitchStmt switch |
|
||||
// "\"".charCodeAt(0) == 34, "&".charCodeAt(0) == 38, "<".charCodeAt(0) == 60
|
||||
forall(int c | c = [34, 38, 60] | c = switch.getACase().getExpr().getIntValue()) and
|
||||
exists(DataFlow::MethodCallNode mcn | mcn.getMethodName() = "charCodeAt" |
|
||||
mcn.flowsToExpr(switch.getExpr()) and
|
||||
str = mcn.getReceiver().asExpr()
|
||||
)
|
||||
or
|
||||
forall(string c | c = ["\"", "&", "<"] | c = switch.getACase().getExpr().getStringValue()) and
|
||||
(
|
||||
exists(DataFlow::MethodCallNode mcn | mcn.getMethodName() = "charAt" |
|
||||
mcn.flowsToExpr(switch.getExpr()) and
|
||||
str = mcn.getReceiver().asExpr()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::PropRead read | exists(read.getPropertyNameExpr()) |
|
||||
read.flowsToExpr(switch.getExpr()) and
|
||||
str = read.getBase().asExpr()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an Ssa variable that is used in a sanitizing switch statement.
|
||||
* The `pragma[noinline]` is to avoid materializing a cartesian product.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private SsaVariable getAPathEscapedInSwitch() { isUsedInHTMLEscapingSwitch(result.getAUse()) }
|
||||
|
||||
/**
|
||||
* An expression that is sanitized by a switch-case.
|
||||
*/
|
||||
class IsEscapedInSwitchSanitizer extends Sanitizer {
|
||||
IsEscapedInSwitchSanitizer() { this.asExpr() = getAPathEscapedInSwitch().getAUse() }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes and predicates for the DOM-based XSS query. */
|
||||
@@ -64,6 +146,9 @@ module DomBasedXss {
|
||||
/** A sanitizer for DOM-based XSS vulnerabilities. */
|
||||
abstract class Sanitizer extends Shared::Sanitizer { }
|
||||
|
||||
/** A sanitizer guard for DOM-based XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends Shared::SanitizerGuard { }
|
||||
|
||||
/**
|
||||
* An expression whose value is interpreted as HTML
|
||||
* and may be inserted into the DOM through a library.
|
||||
@@ -302,6 +387,42 @@ module DomBasedXss {
|
||||
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
|
||||
|
||||
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
|
||||
|
||||
private class IsEscapedInSwitchSanitizer extends Sanitizer, Shared::IsEscapedInSwitchSanitizer { }
|
||||
|
||||
private class QuoteGuard extends SanitizerGuard, Shared::QuoteGuard { }
|
||||
|
||||
/**
|
||||
* Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`.
|
||||
*/
|
||||
predicate isOptionallySanitizedEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(HtmlSanitizerCall sanitizer |
|
||||
// sanitized = sanitize ? sanitizer(source) : source;
|
||||
exists(ConditionalExpr branch, Variable var, VarAccess access |
|
||||
branch = succ.asExpr() and access = var.getAnAccess()
|
||||
|
|
||||
branch.getABranch() = access and
|
||||
pred.getEnclosingExpr() = access and
|
||||
sanitizer = branch.getABranch().flow() and
|
||||
sanitizer.getAnArgument().getEnclosingExpr() = var.getAnAccess()
|
||||
)
|
||||
or
|
||||
// sanitized = source; if (sanitize) {sanitized = sanitizer(source)};
|
||||
exists(SsaPhiNode phi, SsaExplicitDefinition a, SsaDefinition b |
|
||||
a = phi.getAnInput().getDefinition() and
|
||||
b = phi.getAnInput().getDefinition() and
|
||||
count(phi.getAnInput()) = 2 and
|
||||
not a = b and
|
||||
sanitizer = DataFlow::valueNode(a.getDef().getSource()) and
|
||||
sanitizer.getAnArgument().asExpr().(VarAccess).getVariable() = b.getSourceVariable()
|
||||
|
|
||||
pred = DataFlow::ssaDefinitionNode(b) and
|
||||
succ = DataFlow::ssaDefinitionNode(phi)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class ContainsHTMLGuard extends SanitizerGuard, Shared::ContainsHTMLGuard { }
|
||||
}
|
||||
|
||||
/** Provides classes and predicates for the reflected XSS query. */
|
||||
@@ -315,6 +436,9 @@ module ReflectedXss {
|
||||
/** A sanitizer for reflected XSS vulnerabilities. */
|
||||
abstract class Sanitizer extends Shared::Sanitizer { }
|
||||
|
||||
/** A sanitizer guard for reflected XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends Shared::SanitizerGuard { }
|
||||
|
||||
/**
|
||||
* An expression that is sent as part of an HTTP response, considered as an XSS sink.
|
||||
*
|
||||
@@ -401,6 +525,12 @@ module ReflectedXss {
|
||||
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
|
||||
|
||||
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
|
||||
|
||||
private class IsEscapedInSwitchSanitizer extends Sanitizer, Shared::IsEscapedInSwitchSanitizer { }
|
||||
|
||||
private class QuoteGuard extends SanitizerGuard, Shared::QuoteGuard { }
|
||||
|
||||
private class ContainsHTMLGuard extends SanitizerGuard, Shared::ContainsHTMLGuard { }
|
||||
}
|
||||
|
||||
/** Provides classes and predicates for the stored XSS query. */
|
||||
@@ -414,6 +544,9 @@ module StoredXss {
|
||||
/** A sanitizer for stored XSS vulnerabilities. */
|
||||
abstract class Sanitizer extends Shared::Sanitizer { }
|
||||
|
||||
/** A sanitizer guard for stored XSS vulnerabilities. */
|
||||
abstract class SanitizerGuard extends Shared::SanitizerGuard { }
|
||||
|
||||
/** An arbitrary XSS sink, considered as a flow sink for stored XSS. */
|
||||
private class AnySink extends Sink {
|
||||
AnySink() { this instanceof Shared::Sink }
|
||||
@@ -429,6 +562,12 @@ module StoredXss {
|
||||
private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { }
|
||||
|
||||
private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { }
|
||||
|
||||
private class IsEscapedInSwitchSanitizer extends Sanitizer, Shared::IsEscapedInSwitchSanitizer { }
|
||||
|
||||
private class QuoteGuard extends SanitizerGuard, Shared::QuoteGuard { }
|
||||
|
||||
private class ContainsHTMLGuard extends SanitizerGuard, Shared::ContainsHTMLGuard { }
|
||||
}
|
||||
|
||||
/** Provides classes and predicates for the XSS through DOM query. */
|
||||
|
||||
@@ -31,7 +31,12 @@ module XssThroughDom {
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof TypeTestGuard or
|
||||
guard instanceof UnsafeJQuery::PropertyPresenceSanitizer
|
||||
guard instanceof UnsafeJQuery::PropertyPresenceSanitizer or
|
||||
guard instanceof DomBasedXss::SanitizerGuard
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,31 @@ module ZipSlip {
|
||||
import ZipSlipCustomizations::ZipSlip
|
||||
|
||||
/** A taint tracking configuration for unsafe archive extraction. */
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "ZipSlip" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
label = source.(Source).getAFlowLabel()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
label = sink.(Sink).getAFlowLabel()
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
super.isBarrier(node) or
|
||||
node instanceof TaintedPath::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
||||
nd instanceof SanitizerGuard
|
||||
override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
||||
guard instanceof TaintedPath::BarrierGuardNode
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
|
||||
DataFlow::FlowLabel dstlabel
|
||||
) {
|
||||
TaintedPath::isAdditionalTaintedPathFlowStep(src, dst, srclabel, dstlabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,23 @@
|
||||
import javascript
|
||||
|
||||
module ZipSlip {
|
||||
import TaintedPathCustomizations::TaintedPath as TaintedPath
|
||||
|
||||
/**
|
||||
* A data flow source for unsafe archive extraction.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
abstract class Source extends DataFlow::Node {
|
||||
/** Gets a flow label denoting the type of value for which this is a source. */
|
||||
TaintedPath::Label::PosixPath getAFlowLabel() { result.isRelative() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow sink for unsafe archive extraction.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for unsafe archive extraction.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for unsafe archive extraction.
|
||||
*/
|
||||
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { }
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/** Gets a flow label denoting the type of value for which this is a sink. */
|
||||
TaintedPath::Label::PosixPath getAFlowLabel() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can be a parsed archive.
|
||||
@@ -122,42 +120,4 @@ module ZipSlip {
|
||||
class FileSystemWriteSink extends Sink {
|
||||
FileSystemWriteSink() { exists(FileSystemWriteAccess fsw | fsw.getAPathArgument() = this) }
|
||||
}
|
||||
|
||||
/** An expression that sanitizes by calling path.basename */
|
||||
class BasenameSanitizer extends Sanitizer {
|
||||
BasenameSanitizer() { this = DataFlow::moduleImport("path").getAMemberCall("basename") }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that forces the output path to be in the current working folder.
|
||||
* Recognizes the pattern: `path.join(cwd, path.join('/', orgPath))`.
|
||||
*/
|
||||
class PathSanitizer extends Sanitizer, DataFlow::CallNode {
|
||||
PathSanitizer() {
|
||||
this = NodeJSLib::Path::moduleMember("join").getACall() and
|
||||
exists(DataFlow::CallNode inner | inner = getArgument(1) |
|
||||
inner = NodeJSLib::Path::moduleMember("join").getACall() and
|
||||
inner.getArgument(0).mayHaveStringValue("/")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string which is sufficient to exclude to make
|
||||
* a filepath definitely not refer to parent directories.
|
||||
*/
|
||||
private string getAParentDirName() { result = ".." or result = "../" }
|
||||
|
||||
/** A check that a path string does not include '..' */
|
||||
class NoParentDirSanitizerGuard extends SanitizerGuard {
|
||||
StringOps::Includes incl;
|
||||
|
||||
NoParentDirSanitizerGuard() { this = incl }
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
incl.getPolarity().booleanNot() = outcome and
|
||||
incl.getBaseString().asExpr() = e and
|
||||
incl.getSubstring().mayHaveStringValue(getAParentDirName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user