mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Merge branch 'main' of github.com:github/codeql into htmlReg
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: codeql-javascript-examples
|
||||
name: codeql/javascript-examples
|
||||
version: 0.0.3
|
||||
dependencies:
|
||||
codeql/javascript-all: "*"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# [Internal only] Adaptive Threat Modeling for JavaScript
|
||||
|
||||
This directory contains CodeQL libraries and queries that power adaptive threat modeling for JavaScript.
|
||||
All APIs are experimental and may change in the future.
|
||||
|
||||
These queries can only be run by internal users; for external users they will return no results.
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Configures boosting for adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
private import javascript as raw
|
||||
import EndpointTypes
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* A configuration class for defining known endpoints and endpoint filters for adaptive threat
|
||||
* modeling (ATM). Each boosted query must define its own extension of this abstract class.
|
||||
*
|
||||
* A configuration defines a set of known sources (`isKnownSource`) and sinks (`isKnownSink`).
|
||||
* It must also define a sink endpoint filter (`isEffectiveSink`) that filters candidate sinks
|
||||
* predicted by the machine learning model to a set of effective sinks.
|
||||
*
|
||||
* To get started with ATM, you can copy-paste an implementation of the relevant predicates from a
|
||||
* `DataFlow::Configuration` or `TaintTracking::Configuration` class for a standard security query.
|
||||
* For example, for SQL injection you can start by defining the `isKnownSource` and `isKnownSink`
|
||||
* predicates in the ATM configuration by copying and pasting the implementations of `isSource` and
|
||||
* `isSink` from `SqlInjection::Configuration`.
|
||||
*
|
||||
* Note that if the security query configuration defines additional edges beyond the standard data
|
||||
* flow edges, such as `NosqlInjection::Configuration`, you may need to replace the definition of
|
||||
* `isAdditionalFlowStep` with a more generalised definition of additional edges. See
|
||||
* `NosqlInjectionATM.qll` for an example of doing this.
|
||||
*/
|
||||
abstract class ATMConfig extends string {
|
||||
bindingset[this]
|
||||
ATMConfig() { any() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if `source` is a known source of flow.
|
||||
*/
|
||||
predicate isKnownSource(raw::DataFlow::Node source) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if `sink` is a known sink of flow.
|
||||
*/
|
||||
predicate isKnownSink(raw::DataFlow::Node sink) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the candidate source `candidateSource` predicted by the machine learning model should be
|
||||
* an effective source, i.e. one considered as a possible source of flow in the boosted query.
|
||||
*/
|
||||
predicate isEffectiveSource(raw::DataFlow::Node candidateSource) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the candidate sink `candidateSink` predicted by the machine learning model should be
|
||||
* an effective sink, i.e. one considered as a possible sink of flow in the boosted query.
|
||||
*/
|
||||
predicate isEffectiveSink(raw::DataFlow::Node candidateSink) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the candidate sink `candidateSink` predicted by the machine learning model should be
|
||||
* an effective sink that overrides the score provided by the machine learning model with the
|
||||
* score `score` for reason `why`. The effective sinks identified by this predicate MUST be a
|
||||
* subset of those identified by the `isEffectiveSink` predicate.
|
||||
*
|
||||
* For example, in the ATM external API query, we use this method to ensure the ATM external API
|
||||
* query produces the same results as the standard external API query, but assigns flows
|
||||
* involving sinks that are filtered out by the endpoint filters a score of 0.
|
||||
*
|
||||
* This predicate can be phased out once we no longer need to rely on predicates like
|
||||
* `paddedScore` in the ATM CodeQL libraries to add scores to alert messages in a way that works
|
||||
* with lexical sort orders.
|
||||
*/
|
||||
predicate isEffectiveSinkWithOverridingScore(
|
||||
raw::DataFlow::Node candidateSink, float score, string why
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Get an endpoint type for the sources of this query. A query may have multiple applicable
|
||||
* endpoint types for its sources.
|
||||
*/
|
||||
EndpointType getASourceEndpointType() { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Get an endpoint type for the sinks of this query. A query may have multiple applicable
|
||||
* endpoint types for its sinks.
|
||||
*/
|
||||
EndpointType getASinkEndpointType() { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Specifies the default cut-off value that controls how many alerts are produced.
|
||||
* The cut-off value must be in the range [0,1].
|
||||
* A cut-off value of 0 only produces alerts that are likely true-positives.
|
||||
* A cut-off value of 1 produces all alerts including those that are likely false-positives.
|
||||
*/
|
||||
float getScoreCutoff() { result = 0.0 }
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Provides information about the results of boosted queries for use in adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
private import javascript as raw
|
||||
private import raw::DataFlow as DataFlow
|
||||
import ATMConfig
|
||||
private import BaseScoring
|
||||
private import EndpointScoring as EndpointScoring
|
||||
|
||||
module ATM {
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* This module contains informational predicates about the results returned by adaptive threat
|
||||
* modeling (ATM).
|
||||
*/
|
||||
module ResultsInfo {
|
||||
/**
|
||||
* Indicates whether the flow from source to sink represents a result with
|
||||
* sufficiently high likelihood of being a true-positive.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate shouldResultBeIncluded(DataFlow::Node source, DataFlow::Node sink) {
|
||||
any(ScoringResults results).shouldResultBeIncluded(source, sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Returns the score for the flow between the source `source` and the `sink` sink in the
|
||||
* boosted query.
|
||||
*/
|
||||
pragma[inline]
|
||||
float getScoreForFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
any(DataFlow::Configuration cfg).hasFlow(source, sink) and
|
||||
shouldResultBeIncluded(source, sink) and
|
||||
result = unique(float s | s = any(ScoringResults results).getScoreForFlow(source, sink))
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad a score returned from `getKnownScoreForFlow` to a particular length by adding a decimal
|
||||
* point if one does not already exist, and "0"s after that decimal point.
|
||||
*
|
||||
* Note that this predicate must itself define an upper bound on `length`, so that it has a
|
||||
* finite number of results. Currently this is defined as 12.
|
||||
*/
|
||||
private string paddedScore(float score, int length) {
|
||||
// In this definition, we must restrict the values that `length` and `score` can take on so
|
||||
// that the predicate has a finite number of results.
|
||||
(score = getScoreForFlow(_, _) or score = 0) and
|
||||
length = result.length() and
|
||||
(
|
||||
// We need to make sure the padded score contains a "." so lexically sorting the padded
|
||||
// scores is equivalent to numerically sorting the scores.
|
||||
score.toString().charAt(_) = "." and
|
||||
result = score.toString()
|
||||
or
|
||||
not score.toString().charAt(_) = "." and
|
||||
result = score.toString() + "."
|
||||
)
|
||||
or
|
||||
result = paddedScore(score, length - 1) + "0" and
|
||||
length <= 12
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Return a string representing the score of the flow between `source` and `sink` in the
|
||||
* boosted query.
|
||||
*
|
||||
* The returned string is a fixed length, such that lexically sorting the strings returned by
|
||||
* this predicate gives the same sort order as numerically sorting the scores of the flows.
|
||||
*/
|
||||
pragma[inline]
|
||||
string getScoreStringForFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
exists(float score |
|
||||
score = getScoreForFlow(source, sink) and
|
||||
(
|
||||
// A length of 12 is equivalent to 10 decimal places.
|
||||
score.toString().length() >= 12 and
|
||||
result = score.toString().substring(0, 12)
|
||||
or
|
||||
score.toString().length() < 12 and
|
||||
result = paddedScore(score, 12)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Indicates whether the flow from source to sink is likely to be reported by the base security
|
||||
* query.
|
||||
*
|
||||
* Currently this is a heuristic: it ignores potential differences in the definitions of
|
||||
* additional flow steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate isFlowLikelyInBaseQuery(DataFlow::Node source, DataFlow::Node sink) {
|
||||
getCfg().isKnownSource(source) and getCfg().isKnownSink(sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Get additional information about why ATM included the flow from source to sink as an alert.
|
||||
*/
|
||||
pragma[inline]
|
||||
string getAdditionalAlertInfo(DataFlow::Node source, DataFlow::Node sink) {
|
||||
exists(string sourceOrigins, string sinkOrigins |
|
||||
sourceOrigins = concat(any(ScoringResults results).getASourceOrigin(source), ", ") and
|
||||
sinkOrigins = concat(any(ScoringResults results).getASinkOrigin(sink), ", ") and
|
||||
result =
|
||||
"[Source origins: " +
|
||||
any(string s | if sourceOrigins != "" then s = sourceOrigins else s = "unknown") +
|
||||
"; sink origins: " +
|
||||
any(string s | if sinkOrigins != "" then s = sinkOrigins else s = "unknown") + "]"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Provides shared scoring functionality for use in adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import ATMConfig
|
||||
|
||||
external predicate adaptiveThreatModelingModels(
|
||||
string modelChecksum, string modelLanguage, string modelName, string modelType
|
||||
);
|
||||
|
||||
/** Get the ATM configuration. */
|
||||
ATMConfig getCfg() { any() }
|
||||
|
||||
/**
|
||||
* This module provides functionality that takes an endpoint and provides an entity that encloses that
|
||||
* endpoint and is suitable for similarity analysis.
|
||||
*/
|
||||
module EndpointToEntity {
|
||||
private import CodeToFeatures
|
||||
|
||||
/**
|
||||
* Get an entity enclosing the endpoint that is suitable for similarity analysis. In general,
|
||||
* this may associate multiple entities to a single endpoint.
|
||||
*/
|
||||
DatabaseFeatures::Entity getAnEntityForEndpoint(DataFlow::Node endpoint) {
|
||||
DatabaseFeatures::entities(result, _, _, _, _, _, _, _, _) and
|
||||
result.getDefinedFunction() = endpoint.getContainer().getEnclosingContainer*()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module provides functionality that takes an entity and provides effective endpoints within
|
||||
* that entity.
|
||||
*
|
||||
* We use the following terminology to describe endpoints:
|
||||
*
|
||||
* - The *candidate* endpoints are the set of data flow nodes that should be passed to the
|
||||
* appropriate endpoint filter to produce the set of effective endpoints.
|
||||
* When we have a model that beats the performance of the baseline, we will likely define the
|
||||
* candidate endpoints based on the most confident predictions of the model.
|
||||
* - An *effective* endpoint is a candidate endpoint which passes through the endpoint filter.
|
||||
* In other words, it is a candidate endpoint for which the `isEffectiveSink` (or
|
||||
* `isEffectiveSource`) predicate defined in the `ATMConfig` instance in scope holds.
|
||||
*/
|
||||
module EntityToEffectiveEndpoint {
|
||||
private import CodeToFeatures
|
||||
|
||||
/**
|
||||
* Returns endpoint candidates within the specified entities.
|
||||
*
|
||||
* The baseline implementation of this is that a candidate endpoint is any data flow node that is
|
||||
* enclosed within the specified entity.
|
||||
*/
|
||||
private DataFlow::Node getABaselineEndpointCandidate(DatabaseFeatures::Entity entity) {
|
||||
result.getContainer().getEnclosingContainer*() = entity.getDefinedFunction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an effective source enclosed by the specified entity.
|
||||
*
|
||||
* N.B. This is _not_ an inverse of `EndpointToEntity::getAnEntityForEndpoint`: the effective
|
||||
* source may occur in a function defined within the specified entity.
|
||||
*/
|
||||
DataFlow::Node getAnEffectiveSource(DatabaseFeatures::Entity entity) {
|
||||
result = getABaselineEndpointCandidate(entity) and
|
||||
getCfg().isEffectiveSource(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an effective sink enclosed by the specified entity.
|
||||
*
|
||||
* N.B. This is _not_ an inverse of `EndpointToEntity::getAnEntityForEndpoint`: the effective
|
||||
* sink may occur in a function defined within the specified entity.
|
||||
*/
|
||||
DataFlow::Node getAnEffectiveSink(DatabaseFeatures::Entity entity) {
|
||||
result = getABaselineEndpointCandidate(entity) and
|
||||
getCfg().isEffectiveSink(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoring information produced by a scoring model.
|
||||
*
|
||||
* Scoring models include embedding models and endpoint scoring models.
|
||||
*/
|
||||
abstract class ScoringResults extends string {
|
||||
bindingset[this]
|
||||
ScoringResults() { any() }
|
||||
|
||||
/**
|
||||
* Get ATM's confidence that a path between `source` and `sink` represents a security
|
||||
* vulnerability. This will be a number between 0.0 and 1.0.
|
||||
*/
|
||||
abstract float getScoreForFlow(DataFlow::Node source, DataFlow::Node sink);
|
||||
|
||||
/**
|
||||
* Get a string representing why ATM included the given source in the dataflow analysis.
|
||||
*
|
||||
* In general, there may be multiple reasons why ATM included the given source, in which case
|
||||
* this predicate should have multiple results.
|
||||
*/
|
||||
abstract string getASourceOrigin(DataFlow::Node source);
|
||||
|
||||
/**
|
||||
* Get a string representing why ATM included the given sink in the dataflow analysis.
|
||||
*
|
||||
* In general, there may be multiple reasons why ATM included the given sink, in which case this
|
||||
* predicate should have multiple results.
|
||||
*/
|
||||
abstract string getASinkOrigin(DataFlow::Node sink);
|
||||
|
||||
/**
|
||||
* Indicates whether the flow from source to sink represents a result with
|
||||
* sufficiently high likelihood of being a true-positive.
|
||||
*/
|
||||
pragma[inline]
|
||||
abstract predicate shouldResultBeIncluded(DataFlow::Node source, DataFlow::Node sink);
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Extracts data about the functions in the database for use in adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
module Raw {
|
||||
private import javascript as raw
|
||||
|
||||
class RawAstNode = raw::ASTNode;
|
||||
|
||||
class Entity = raw::Function;
|
||||
|
||||
class Location = raw::Location;
|
||||
|
||||
/**
|
||||
* Exposed as a tool for defining anchors for semantic search.
|
||||
*/
|
||||
class UnderlyingFunction = raw::Function;
|
||||
|
||||
/**
|
||||
* Determines whether an entity should be omitted from ATM.
|
||||
*/
|
||||
predicate isEntityIgnored(Entity entity) {
|
||||
// Ignore entities which don't have definitions, for example those in TypeScript
|
||||
// declaration files.
|
||||
not exists(entity.getBody())
|
||||
or
|
||||
// Ignore entities with an empty body, for example the JavaScript function () => {}.
|
||||
entity.getNumBodyStmt() = 0 and not exists(entity.getAReturnedExpr())
|
||||
}
|
||||
|
||||
newtype WrappedAstNode = TAstNode(RawAstNode rawNode)
|
||||
|
||||
/**
|
||||
* This class represents nodes in the AST.
|
||||
*/
|
||||
class AstNode extends TAstNode {
|
||||
RawAstNode rawNode;
|
||||
|
||||
AstNode() { this = TAstNode(rawNode) }
|
||||
|
||||
AstNode getAChildNode() { result = TAstNode(rawNode.getAChild()) }
|
||||
|
||||
AstNode getParentNode() { result = TAstNode(rawNode.getParent()) }
|
||||
|
||||
/**
|
||||
* Holds if the AST node has `result` as its `index`th attribute.
|
||||
*
|
||||
* The index is not intended to mean anything, and is only here for disambiguation.
|
||||
* There are no guarantees about any particular index being used (or not being used).
|
||||
*/
|
||||
string astNodeAttribute(int index) {
|
||||
(
|
||||
// NB: Unary and binary operator expressions e.g. -a, a + b and compound
|
||||
// assignments e.g. a += b can be identified by the expression type.
|
||||
result = rawNode.(raw::Identifier).getName()
|
||||
or
|
||||
// Computed property accesses for which we can predetermine the property being accessed.
|
||||
// NB: May alias with operators e.g. could have '+' as a property name.
|
||||
result = rawNode.(raw::IndexExpr).getPropertyName()
|
||||
or
|
||||
// We use `getRawValue` to give us distinct representations for `0xa`, `0xA`, and `10`.
|
||||
result = rawNode.(raw::NumberLiteral).getRawValue()
|
||||
or
|
||||
// We use `getValue` rather than `getRawValue` so we assign `"a"` and `'a'` the same representation.
|
||||
not rawNode instanceof raw::NumberLiteral and
|
||||
result = rawNode.(raw::Literal).getValue()
|
||||
or
|
||||
result = rawNode.(raw::TemplateElement).getRawValue()
|
||||
) and
|
||||
index = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string indicating the "type" of the AST node.
|
||||
*/
|
||||
string astNodeType() {
|
||||
// The definition of this method should correspond with that of the `@ast_node` entry in the
|
||||
// dbscheme.
|
||||
result = "js_exprs." + any(int kind | exprs(rawNode, kind, _, _, _))
|
||||
or
|
||||
result = "js_properties." + any(int kind | properties(rawNode, _, _, kind, _))
|
||||
or
|
||||
result = "js_stmts." + any(int kind | stmts(rawNode, kind, _, _, _))
|
||||
or
|
||||
result = "js_toplevel" and rawNode instanceof raw::TopLevel
|
||||
or
|
||||
result = "js_typeexprs." + any(int kind | typeexprs(rawNode, kind, _, _, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `result` is the `index`'th child of the AST node, for some arbitrary indexing.
|
||||
* A root of the AST should be its own child, with an arbitrary (though conventionally
|
||||
* 0) index.
|
||||
*
|
||||
* Notably, the order in which child nodes are visited is not required to be meaningful,
|
||||
* and no particular index is required to be meaningful. However, `(parent, index)`
|
||||
* should be a keyset.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
AstNode astNodeChild(int index) {
|
||||
result =
|
||||
rank[index - 1](AstNode child, raw::Location l |
|
||||
child = this.getAChildNode() and l = child.getLocation()
|
||||
|
|
||||
child
|
||||
order by
|
||||
l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn(),
|
||||
child.astNodeType()
|
||||
)
|
||||
or
|
||||
not exists(result.getParentNode()) and this = result and index = 0
|
||||
}
|
||||
|
||||
raw::Location getLocation() { result = rawNode.getLocation() }
|
||||
|
||||
string toString() { result = rawNode.toString() }
|
||||
|
||||
predicate isEntityNameNode(Entity entity) {
|
||||
exists(int index |
|
||||
TAstNode(entity) = getParentNode() and
|
||||
this = getParentNode().astNodeChild(index) and
|
||||
// An entity name node must be the first child of the entity.
|
||||
index = min(int otherIndex | exists(getParentNode().astNodeChild(otherIndex))) and
|
||||
entity.getName() = rawNode.(raw::VarDecl).getName()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `result` is the `index`'th child of the `parent` entity. Such
|
||||
* a node is a root of an AST associated with this entity.
|
||||
*/
|
||||
AstNode entityChild(AstNode parent, int index) {
|
||||
// In JavaScript, entities appear in the AST parent/child relationship.
|
||||
result = parent.astNodeChild(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is contained in `entity`. Note that a single node may be contained
|
||||
* in multiple entities, if they are nested. An entity, in particular, should be
|
||||
* reported as contained within itself.
|
||||
*/
|
||||
predicate entityContains(Entity entity, AstNode node) {
|
||||
node.getParentNode*() = TAstNode(entity) and not node.isEntityNameNode(entity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the entity.
|
||||
*
|
||||
* We attempt to assign unnamed entities approximate names if they are passed to a likely
|
||||
* external library function. If we can't assign them an approximate name, we give them the name
|
||||
* `""`, so that these entities are included in `AdaptiveThreatModeling.qll`.
|
||||
*
|
||||
* For entities which have multiple names, we choose the lexically smallest name.
|
||||
*/
|
||||
string getEntityName(Entity entity) {
|
||||
if exists(entity.getName())
|
||||
then
|
||||
// https://github.com/github/ml-ql-adaptive-threat-modeling/issues/244 discusses making use
|
||||
// of all the names during training.
|
||||
result = min(entity.getName())
|
||||
else
|
||||
if exists(getApproximateNameForEntity(entity))
|
||||
then result = getApproximateNameForEntity(entity)
|
||||
else result = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the call `call` has `entity` is its `argumentIndex`th argument.
|
||||
*/
|
||||
private predicate entityUsedAsArgumentToCall(
|
||||
Entity entity, raw::DataFlow::CallNode call, int argumentIndex
|
||||
) {
|
||||
raw::DataFlow::localFlowStep*(call.getArgument(argumentIndex), entity.flow())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generated name for the entity. This name is generated such that
|
||||
* entities with the same names have similar behaviour.
|
||||
*/
|
||||
private string getApproximateNameForEntity(Entity entity) {
|
||||
count(raw::DataFlow::CallNode call, int index | entityUsedAsArgumentToCall(entity, call, index)) =
|
||||
1 and
|
||||
exists(raw::DataFlow::CallNode call, int index, string basePart |
|
||||
entityUsedAsArgumentToCall(entity, call, index) and
|
||||
(
|
||||
if count(getReceiverName(call)) = 1
|
||||
then basePart = getReceiverName(call) + "."
|
||||
else basePart = ""
|
||||
) and
|
||||
result = basePart + call.getCalleeName() + "#functionalargument"
|
||||
)
|
||||
}
|
||||
|
||||
private string getReceiverName(raw::DataFlow::CallNode call) {
|
||||
result = call.getReceiver().asExpr().(raw::VarAccess).getName()
|
||||
}
|
||||
|
||||
/** Consistency checks: these predicates should each have no results */
|
||||
module Consistency {
|
||||
/** `getEntityName` should assign each entity a single name. */
|
||||
query predicate entityWithManyNames(Entity entity, string name) {
|
||||
name = getEntityName(entity) and
|
||||
count(getEntityName(entity)) > 1
|
||||
}
|
||||
|
||||
query predicate nodeWithNoType(AstNode node) { not exists(node.astNodeType()) }
|
||||
|
||||
query predicate nodeWithManyTypes(AstNode node, string type) {
|
||||
type = node.astNodeType() and
|
||||
count(node.astNodeType()) > 1
|
||||
}
|
||||
|
||||
query predicate nodeWithNoParent(AstNode node, string type) {
|
||||
not node = any(AstNode parent).astNodeChild(_) and
|
||||
type = node.astNodeType() and
|
||||
not exists(RawAstNode rawNode | node = TAstNode(rawNode) and rawNode instanceof raw::Module)
|
||||
}
|
||||
|
||||
query predicate duplicateChildIndex(AstNode parent, int index, AstNode child) {
|
||||
child = parent.astNodeChild(index) and
|
||||
count(parent.astNodeChild(index)) > 1
|
||||
}
|
||||
|
||||
query predicate duplicateAttributeIndex(AstNode node, int index) {
|
||||
exists(node.astNodeAttribute(index)) and
|
||||
count(node.astNodeAttribute(index)) > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Wrapped {
|
||||
/*
|
||||
* We require any node with attributes to be a leaf. Where a non-leaf node
|
||||
* has an attribute, we instead create a synthetic leaf node that has that
|
||||
* attribute.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds if the AST node `e` is a leaf node.
|
||||
*/
|
||||
private predicate isLeaf(Raw::AstNode e) { not exists(e.astNodeChild(_)) }
|
||||
|
||||
newtype WrappedEntity =
|
||||
TEntity(Raw::Entity entity) {
|
||||
exists(entity.getLocation().getFile().getRelativePath()) and
|
||||
Raw::entityContains(entity, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A type ranging over the kinds of entities for which we want to consider embeddings.
|
||||
*/
|
||||
class Entity extends WrappedEntity {
|
||||
Raw::Entity rawEntity;
|
||||
|
||||
Entity() { this = TEntity(rawEntity) and not Raw::isEntityIgnored(rawEntity) }
|
||||
|
||||
string getName() { result = Raw::getEntityName(rawEntity) }
|
||||
|
||||
AstNode getAstRoot(int index) {
|
||||
result = TAstNode(rawEntity, Raw::entityChild(Raw::TAstNode(rawEntity), index))
|
||||
}
|
||||
|
||||
string toString() { result = rawEntity.toString() }
|
||||
|
||||
Raw::Location getLocation() { result = rawEntity.getLocation() }
|
||||
|
||||
Raw::UnderlyingFunction getDefinedFunction() { result = rawEntity }
|
||||
}
|
||||
|
||||
newtype WrappedAstNode =
|
||||
TAstNode(Raw::Entity enclosingEntity, Raw::AstNode node) {
|
||||
Raw::entityContains(enclosingEntity, node)
|
||||
} or
|
||||
TSyntheticNode(
|
||||
Raw::Entity enclosingEntity, Raw::AstNode node, int syntheticChildIndex, int attrIndex
|
||||
) {
|
||||
Raw::entityContains(enclosingEntity, node) and
|
||||
exists(node.astNodeAttribute(attrIndex)) and
|
||||
not isLeaf(node) and
|
||||
if exists(node.astNodeChild(_))
|
||||
then
|
||||
syntheticChildIndex =
|
||||
attrIndex - min(int other | exists(node.astNodeAttribute(other))) +
|
||||
max(int other | exists(node.astNodeChild(other))) + 1
|
||||
else syntheticChildIndex = attrIndex
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private AstNode injectedChild(Raw::Entity enclosingEntity, Raw::AstNode parent, int index) {
|
||||
result = TAstNode(enclosingEntity, parent.astNodeChild(index)) or
|
||||
result = TSyntheticNode(enclosingEntity, parent, index, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A type ranging over AST nodes. Ultimately, only nodes contained in entities will
|
||||
* be considered.
|
||||
*/
|
||||
class AstNode extends WrappedAstNode {
|
||||
Raw::Entity enclosingEntity;
|
||||
Raw::AstNode rawNode;
|
||||
|
||||
AstNode() {
|
||||
(
|
||||
this = TAstNode(enclosingEntity, rawNode) or
|
||||
this = TSyntheticNode(enclosingEntity, rawNode, _, _)
|
||||
) and
|
||||
not Raw::isEntityIgnored(enclosingEntity)
|
||||
}
|
||||
|
||||
string getAttribute(int index) {
|
||||
result = rawNode.astNodeAttribute(index) and
|
||||
not exists(TSyntheticNode(enclosingEntity, rawNode, _, index))
|
||||
}
|
||||
|
||||
string getType() { result = rawNode.astNodeType() }
|
||||
|
||||
AstNode getChild(int index) { result = injectedChild(enclosingEntity, rawNode, index) }
|
||||
|
||||
string toString() { result = getType() }
|
||||
|
||||
Raw::Location getLocation() { result = rawNode.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic AST node, created to be a leaf for an otherwise non-leaf attribute.
|
||||
*/
|
||||
class SyntheticAstNode extends AstNode, TSyntheticNode {
|
||||
int childIndex;
|
||||
int attributeIndex;
|
||||
|
||||
SyntheticAstNode() {
|
||||
this = TSyntheticNode(enclosingEntity, rawNode, childIndex, attributeIndex)
|
||||
}
|
||||
|
||||
override string getAttribute(int index) {
|
||||
result = rawNode.astNodeAttribute(attributeIndex) and index = attributeIndex
|
||||
}
|
||||
|
||||
override string getType() {
|
||||
result = rawNode.astNodeType() + "::<synthetic " + childIndex + ">"
|
||||
}
|
||||
|
||||
override AstNode getChild(int index) { none() }
|
||||
}
|
||||
}
|
||||
|
||||
module DatabaseFeatures {
|
||||
/**
|
||||
* Exposed as a tool for defining anchors for semantic search.
|
||||
*/
|
||||
class UnderlyingFunction = Raw::UnderlyingFunction;
|
||||
|
||||
private class Location = Raw::Location;
|
||||
|
||||
private newtype TEntityOrAstNode =
|
||||
TEntity(Wrapped::Entity entity) or
|
||||
TAstNode(Wrapped::AstNode astNode)
|
||||
|
||||
class EntityOrAstNode extends TEntityOrAstNode {
|
||||
abstract string getType();
|
||||
|
||||
abstract string toString();
|
||||
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
class Entity extends EntityOrAstNode, TEntity {
|
||||
Wrapped::Entity entity;
|
||||
|
||||
Entity() { this = TEntity(entity) }
|
||||
|
||||
string getName() { result = entity.getName() }
|
||||
|
||||
AstNode getAstRoot(int index) { result = TAstNode(entity.getAstRoot(index)) }
|
||||
|
||||
override string getType() { result = "javascript function" }
|
||||
|
||||
override string toString() { result = "Entity: " + getName() }
|
||||
|
||||
override Location getLocation() { result = entity.getLocation() }
|
||||
|
||||
UnderlyingFunction getDefinedFunction() { result = entity.getDefinedFunction() }
|
||||
}
|
||||
|
||||
class AstNode extends EntityOrAstNode, TAstNode {
|
||||
Wrapped::AstNode rawNode;
|
||||
|
||||
AstNode() { this = TAstNode(rawNode) }
|
||||
|
||||
AstNode getChild(int index) { result = TAstNode(rawNode.getChild(index)) }
|
||||
|
||||
string getAttribute(int index) { result = rawNode.getAttribute(index) }
|
||||
|
||||
override string getType() { result = rawNode.getType() }
|
||||
|
||||
override string toString() { result = this.getType() }
|
||||
|
||||
override Location getLocation() { result = rawNode.getLocation() }
|
||||
}
|
||||
|
||||
/** Consistency checks: these predicates should each have no results */
|
||||
module Consistency {
|
||||
query predicate nonLeafAttribute(AstNode node, int index, string attribute) {
|
||||
attribute = node.getAttribute(index) and
|
||||
exists(node.getChild(_))
|
||||
}
|
||||
}
|
||||
|
||||
query predicate entities(
|
||||
Entity entity, string entity_name, string entity_type, string path, int startLine,
|
||||
int startColumn, int endLine, int endColumn, string absolutePath
|
||||
) {
|
||||
entity_name = entity.getName() and
|
||||
entity_type = entity.getType() and
|
||||
exists(Location l | l = entity.getLocation() |
|
||||
path = l.getFile().getRelativePath() and
|
||||
absolutePath = l.getFile().getAbsolutePath() and
|
||||
l.hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
|
||||
)
|
||||
}
|
||||
|
||||
query predicate astNodes(
|
||||
Entity enclosingEntity, EntityOrAstNode parent, int index, AstNode node, string node_type
|
||||
) {
|
||||
node = enclosingEntity.getAstRoot(index) and
|
||||
parent = enclosingEntity and
|
||||
node_type = node.getType()
|
||||
or
|
||||
astNodes(enclosingEntity, _, _, parent, _) and
|
||||
node = parent.(AstNode).getChild(index) and
|
||||
node_type = node.getType()
|
||||
}
|
||||
|
||||
query predicate nodeAttributes(AstNode node, string attr) {
|
||||
// Only get attributes of AST nodes we extract.
|
||||
// This excludes nodes in standard libraries since the standard library files
|
||||
// are located outside the source root.
|
||||
astNodes(_, _, _, node, _) and
|
||||
attr = node.getAttribute(_)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Provides predicates that expose the knowledge of models
|
||||
* in the core CodeQL JavaScript libraries.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.security.dataflow.XxeCustomizations
|
||||
private import semmle.javascript.security.dataflow.RemotePropertyInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTamperingCustomizations
|
||||
private import semmle.javascript.security.dataflow.ZipSlipCustomizations
|
||||
private import semmle.javascript.security.dataflow.TaintedPathCustomizations
|
||||
private import semmle.javascript.security.dataflow.CleartextLoggingCustomizations
|
||||
private import semmle.javascript.security.dataflow.XpathInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.Xss::Shared as Xss
|
||||
private import semmle.javascript.security.dataflow.StackTraceExposureCustomizations
|
||||
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.RequestForgeryCustomizations
|
||||
private import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations
|
||||
private import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentCustomizations
|
||||
private import semmle.javascript.security.dataflow.DifferentKindsComparisonBypassCustomizations
|
||||
private import semmle.javascript.security.dataflow.CommandInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.PrototypePollutionCustomizations
|
||||
private import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCallCustomizations
|
||||
private import semmle.javascript.security.dataflow.TaintedFormatStringCustomizations
|
||||
private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.PostMessageStarCustomizations
|
||||
private import semmle.javascript.security.dataflow.RegExpInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.SqlInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.InsecureRandomnessCustomizations
|
||||
private import semmle.javascript.security.dataflow.XmlBombCustomizations
|
||||
private import semmle.javascript.security.dataflow.InsufficientPasswordHashCustomizations
|
||||
private import semmle.javascript.security.dataflow.HardcodedCredentialsCustomizations
|
||||
private import semmle.javascript.security.dataflow.FileAccessToHttpCustomizations
|
||||
private import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccessCustomizations
|
||||
private import semmle.javascript.security.dataflow.UnsafeDeserializationCustomizations
|
||||
private import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCodeCustomizations
|
||||
private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
|
||||
private import semmle.javascript.security.dataflow.IndirectCommandInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.ConditionalBypassCustomizations
|
||||
private import semmle.javascript.security.dataflow.HttpToFileAccessCustomizations
|
||||
private import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmCustomizations
|
||||
private import semmle.javascript.security.dataflow.LoopBoundInjectionCustomizations
|
||||
private import semmle.javascript.security.dataflow.CleartextStorageCustomizations
|
||||
import FilteringReasons
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is a known sink in a modeled library, or a sibling-argument of such a sink.
|
||||
*/
|
||||
predicate isArgumentToKnownLibrarySinkFunction(DataFlow::Node n) {
|
||||
exists(DataFlow::InvokeNode invk, DataFlow::Node known |
|
||||
invk.getAnArgument() = n and invk.getAnArgument() = known and isKnownLibrarySink(known)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is a known sink for the external API security query.
|
||||
*
|
||||
* This corresponds to known sinks from security queries whose sources include remote flow and
|
||||
* DOM-based sources.
|
||||
*/
|
||||
predicate isKnownExternalAPIQuerySink(DataFlow::Node n) {
|
||||
n instanceof Xxe::Sink or
|
||||
n instanceof TaintedPath::Sink or
|
||||
n instanceof XpathInjection::Sink or
|
||||
n instanceof Xss::Sink or
|
||||
n instanceof ClientSideUrlRedirect::Sink or
|
||||
n instanceof CodeInjection::Sink or
|
||||
n instanceof RequestForgery::Sink or
|
||||
n instanceof CorsMisconfigurationForCredentials::Sink or
|
||||
n instanceof CommandInjection::Sink or
|
||||
n instanceof PrototypePollution::Sink or
|
||||
n instanceof UnvalidatedDynamicMethodCall::Sink or
|
||||
n instanceof TaintedFormatString::Sink or
|
||||
n instanceof NosqlInjection::Sink or
|
||||
n instanceof PostMessageStar::Sink or
|
||||
n instanceof RegExpInjection::Sink or
|
||||
n instanceof SqlInjection::Sink or
|
||||
n instanceof XmlBomb::Sink or
|
||||
n instanceof ZipSlip::Sink or
|
||||
n instanceof UnsafeDeserialization::Sink or
|
||||
n instanceof ServerSideUrlRedirect::Sink or
|
||||
n instanceof CleartextStorage::Sink or
|
||||
n instanceof HttpToFileAccess::Sink
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is a known sink in a modeled library.
|
||||
*/
|
||||
predicate isKnownLibrarySink(DataFlow::Node n) {
|
||||
isKnownExternalAPIQuerySink(n) or
|
||||
n instanceof CleartextLogging::Sink or
|
||||
n instanceof StackTraceExposure::Sink or
|
||||
n instanceof ShellCommandInjectionFromEnvironment::Sink or
|
||||
n instanceof InsecureRandomness::Sink or
|
||||
n instanceof FileAccessToHttp::Sink or
|
||||
n instanceof IndirectCommandInjection::Sink
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is known as the predecessor in a modeled flow step.
|
||||
*/
|
||||
predicate isKnownStepSrc(DataFlow::Node n) {
|
||||
any(TaintTracking::AdditionalTaintStep s).step(n, _) or
|
||||
any(DataFlow::AdditionalFlowStep s).step(n, _) or
|
||||
any(DataFlow::AdditionalFlowStep s).step(n, _, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is an argument to a function of a builtin object.
|
||||
*/
|
||||
private predicate isArgumentToBuiltinFunction(DataFlow::Node n, FilteringReason reason) {
|
||||
exists(DataFlow::SourceNode builtin, DataFlow::SourceNode receiver, DataFlow::InvokeNode invk |
|
||||
(
|
||||
builtin instanceof DataFlow::ArrayCreationNode and
|
||||
reason instanceof ArgumentToArrayReason
|
||||
or
|
||||
builtin =
|
||||
DataFlow::globalVarRef([
|
||||
"Map", "Set", "WeakMap", "WeakSet", "Number", "Object", "String", "Array", "Error",
|
||||
"Math", "Boolean"
|
||||
]) and
|
||||
reason instanceof ArgumentToBuiltinGlobalVarRefReason
|
||||
)
|
||||
|
|
||||
receiver = [builtin.getAnInvocation(), builtin] and
|
||||
invk = [receiver, receiver.getAPropertyRead()].getAnInvocation() and
|
||||
invk.getAnArgument() = n
|
||||
)
|
||||
or
|
||||
exists(Expr primitive, MethodCallExpr c |
|
||||
primitive instanceof ConstantString or
|
||||
primitive instanceof NumberLiteral or
|
||||
primitive instanceof BooleanLiteral
|
||||
|
|
||||
c.calls(primitive, _) and
|
||||
c.getAnArgument() = n.asExpr() and
|
||||
reason instanceof ConstantReceiverReason
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode call |
|
||||
call.getAnArgument() = n and
|
||||
call.getCalleeName() =
|
||||
[
|
||||
"indexOf", "hasOwnProperty", "substring", "isDecimal", "decode", "encode", "keys", "shift",
|
||||
"values", "forEach", "toString", "slice", "splice", "push", "isArray", "sort"
|
||||
] and
|
||||
reason instanceof BuiltinCallNameReason
|
||||
)
|
||||
}
|
||||
|
||||
predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
|
||||
isArgumentToBuiltinFunction(n, reason)
|
||||
or
|
||||
any(LodashUnderscore::Member m).getACall().getAnArgument() = n and
|
||||
reason instanceof LodashUnderscoreArgumentReason
|
||||
or
|
||||
exists(ClientRequest r |
|
||||
r.getAnArgument() = n or n = r.getUrl() or n = r.getHost() or n = r.getADataNode()
|
||||
) and
|
||||
reason instanceof ClientRequestReason
|
||||
or
|
||||
exists(PromiseDefinition p |
|
||||
n = [p.getResolveParameter(), p.getRejectParameter()].getACall().getAnArgument()
|
||||
) and
|
||||
reason instanceof PromiseDefinitionReason
|
||||
or
|
||||
n instanceof CryptographicKey and reason instanceof CryptographicKeyReason
|
||||
or
|
||||
any(CryptographicOperation op).getInput().flow() = n and
|
||||
reason instanceof CryptographicOperationFlowReason
|
||||
or
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
call.getCalleeName() = getAStandardLoggerMethodName() and
|
||||
reason instanceof LoggerMethodReason
|
||||
or
|
||||
call.getCalleeName() = ["setTimeout", "clearTimeout"] and
|
||||
reason instanceof TimeoutReason
|
||||
or
|
||||
call.getReceiver() = DataFlow::globalVarRef(["localStorage", "sessionStorage"]) and
|
||||
reason instanceof ReceiverStorageReason
|
||||
or
|
||||
call instanceof StringOps::StartsWith and reason instanceof StringStartsWithReason
|
||||
or
|
||||
call instanceof StringOps::EndsWith and reason instanceof StringEndsWithReason
|
||||
or
|
||||
call instanceof StringOps::RegExpTest and reason instanceof StringRegExpTestReason
|
||||
or
|
||||
call instanceof EventRegistration and reason instanceof EventRegistrationReason
|
||||
or
|
||||
call instanceof EventDispatch and reason instanceof EventDispatchReason
|
||||
or
|
||||
call = any(MembershipCandidate c).getTest() and
|
||||
reason instanceof MembershipCandidateTestReason
|
||||
or
|
||||
call instanceof FileSystemAccess and reason instanceof FileSystemAccessReason
|
||||
or
|
||||
call instanceof DatabaseAccess and reason instanceof DatabaseAccessReason
|
||||
or
|
||||
call = DOM::domValueRef() and reason instanceof DOMReason
|
||||
or
|
||||
call.getCalleeName() = "next" and
|
||||
exists(DataFlow::FunctionNode f | call = f.getLastParameter().getACall()) and
|
||||
reason instanceof NextFunctionCallReason
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Extracts data about the database for use in adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import CodeToFeatures
|
||||
import EndpointScoring
|
||||
|
||||
/**
|
||||
* Gets the value of the token-based feature named `featureName` for the endpoint `endpoint`.
|
||||
*
|
||||
* This is a single string containing a space-separated list of tokens.
|
||||
*/
|
||||
private string getTokenFeature(DataFlow::Node endpoint, string featureName) {
|
||||
// Features for endpoints that are contained within a function.
|
||||
exists(DatabaseFeatures::Entity entity | entity = getRepresentativeEntityForEndpoint(endpoint) |
|
||||
// The name of the function that encloses the endpoint.
|
||||
featureName = "enclosingFunctionName" and result = entity.getName()
|
||||
or
|
||||
// A feature containing natural language tokens from the function that encloses the endpoint in
|
||||
// the order that they appear in the source code.
|
||||
featureName = "enclosingFunctionBody" and
|
||||
result = unique(string x | x = FunctionBodies::getBodyTokenFeatureForEntity(entity))
|
||||
)
|
||||
or
|
||||
exists(getACallBasedTokenFeatureComponent(endpoint, _, featureName)) and
|
||||
result =
|
||||
concat(DataFlow::CallNode call, string component |
|
||||
component = getACallBasedTokenFeatureComponent(endpoint, call, featureName)
|
||||
|
|
||||
component, " "
|
||||
)
|
||||
or
|
||||
// The access path of the function being called, both with and without structural info, if the
|
||||
// function being called originates from an external API. For example, the endpoint here:
|
||||
//
|
||||
// ```js
|
||||
// const mongoose = require('mongoose'),
|
||||
// User = mongoose.model('User', null);
|
||||
// User.findOne(ENDPOINT);
|
||||
// ```
|
||||
//
|
||||
// would have a callee access path with structural info of
|
||||
// `mongoose member model instanceorreturn member findOne instanceorreturn`, and a callee access
|
||||
// path without structural info of `mongoose model findOne`.
|
||||
//
|
||||
// These features indicate that the callee comes from (reading the access path backwards) an
|
||||
// instance of the `findOne` member of an instance of the `model` member of the `mongoose`
|
||||
// external library.
|
||||
exists(AccessPaths::Boolean includeStructuralInfo |
|
||||
featureName =
|
||||
"calleeAccessPath" +
|
||||
any(string x | if includeStructuralInfo = true then x = "WithStructuralInfo" else x = "") and
|
||||
result =
|
||||
concat(API::Node node, string accessPath |
|
||||
node.getInducingNode().(DataFlow::CallNode).getAnArgument() = endpoint and
|
||||
accessPath = AccessPaths::getAccessPath(node, includeStructuralInfo)
|
||||
|
|
||||
accessPath, " "
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value of the function-call-related token-based feature named `featureName` associated
|
||||
* with the function call `call` and the endpoint `endpoint`.
|
||||
*
|
||||
* This may in general report multiple strings, each containing a space-separated list of tokens.
|
||||
*
|
||||
* **Technical details:** This predicate can have multiple values per endpoint and feature name. As a
|
||||
* result, the results from this predicate must be concatenated together. However concatenating
|
||||
* other features like the function body tokens is expensive, so we separate out this predicate
|
||||
* from others like `FunctionBodies::getBodyTokenFeatureForEntity` to avoid having to perform this
|
||||
* concatenation operation on other features like the function body tokens.
|
||||
*/
|
||||
private string getACallBasedTokenFeatureComponent(
|
||||
DataFlow::Node endpoint, DataFlow::CallNode call, string featureName
|
||||
) {
|
||||
// Features for endpoints that are an argument to a function call.
|
||||
endpoint = call.getAnArgument() and
|
||||
(
|
||||
// The name of the function being called, e.g. in a call `Artist.findOne(...)`, this is `findOne`.
|
||||
featureName = "calleeName" and result = call.getCalleeName()
|
||||
or
|
||||
// The name of the receiver of the call, e.g. in a call `Artist.findOne(...)`, this is `Artist`.
|
||||
featureName = "receiverName" and result = call.getReceiver().asExpr().(VarRef).getName()
|
||||
or
|
||||
// The argument index of the endpoint, e.g. in `f(a, endpoint, b)`, this is 1.
|
||||
featureName = "argumentIndex" and
|
||||
result = any(int argIndex | call.getArgument(argIndex) = endpoint).toString()
|
||||
or
|
||||
// The name of the API that the function being called originates from, if the function being
|
||||
// called originates from an external API. For example, the endpoint here:
|
||||
//
|
||||
// ```js
|
||||
// const mongoose = require('mongoose'),
|
||||
// User = mongoose.model('User', null);
|
||||
// User.findOne(ENDPOINT);
|
||||
// ```
|
||||
//
|
||||
// would have a callee API name of `mongoose`.
|
||||
featureName = "calleeApiName" and
|
||||
result = getAnApiName(call)
|
||||
)
|
||||
}
|
||||
|
||||
/** This module provides functionality for getting the function body feature associated with a particular entity. */
|
||||
module FunctionBodies {
|
||||
/** Holds if `node` is an AST node within the entity `entity` and `token` is a node attribute associated with `node`. */
|
||||
private predicate bodyTokens(
|
||||
DatabaseFeatures::Entity entity, DatabaseFeatures::AstNode node, string token
|
||||
) {
|
||||
DatabaseFeatures::astNodes(entity, _, _, node, _) and
|
||||
token = unique(string t | DatabaseFeatures::nodeAttributes(node, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the body token feature for the specified entity.
|
||||
*
|
||||
* This is a string containing natural language tokens in the order that they appear in the source code for the entity.
|
||||
*/
|
||||
string getBodyTokenFeatureForEntity(DatabaseFeatures::Entity entity) {
|
||||
// If a function has more than 256 body subtokens, then featurize it as absent. This
|
||||
// approximates the behavior of the classifer on non-generic body features where large body
|
||||
// features are replaced by the absent token.
|
||||
if count(DatabaseFeatures::AstNode node, string token | bodyTokens(entity, node, token)) > 256
|
||||
then result = ""
|
||||
else
|
||||
result =
|
||||
concat(int i, string rankedToken |
|
||||
rankedToken =
|
||||
rank[i](DatabaseFeatures::AstNode node, string token, Location l |
|
||||
bodyTokens(entity, node, token) and l = node.getLocation()
|
||||
|
|
||||
token
|
||||
order by
|
||||
l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(),
|
||||
l.getEndColumn(), token
|
||||
)
|
||||
|
|
||||
rankedToken, " " order by i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name of the API that a node originates from, if the node originates from an API.
|
||||
*
|
||||
* This predicate may have multiple results if the node corresponds to multiple nodes in the API graph forest.
|
||||
*/
|
||||
pragma[inline]
|
||||
private string getAnApiName(DataFlow::Node node) {
|
||||
API::moduleImport(result).getASuccessor*().getInducingNode() = node
|
||||
}
|
||||
|
||||
/**
|
||||
* This module provides functionality for getting a representation of the access path of nodes
|
||||
* within the program.
|
||||
*
|
||||
* For example, it gives the `User.find` callee here:
|
||||
*
|
||||
* ```js
|
||||
* const mongoose = require('mongoose'),
|
||||
* User = mongoose.model('User', null);
|
||||
* User.find({ 'isAdmin': true })
|
||||
* ```
|
||||
* the access path `mongoose member model instanceorreturn member find instanceorreturn`.
|
||||
*
|
||||
* This access path is based on the simplified access path that the untrusted data flowing to
|
||||
* external API query associates to each of its sinks, with modifications to optionally include
|
||||
* explicit structural information and to improve how well the path tokenizes.
|
||||
*/
|
||||
private module AccessPaths {
|
||||
bindingset[str]
|
||||
private predicate isNumericString(string str) { exists(str.toInt()) }
|
||||
|
||||
/**
|
||||
* Gets a parameter of `base` with name `name`, or a property named `name` of a destructuring parameter.
|
||||
*/
|
||||
private API::Node getNamedParameter(API::Node base, string name) {
|
||||
exists(API::Node param |
|
||||
param = base.getAParameter() and
|
||||
not param = base.getReceiver()
|
||||
|
|
||||
result = param and
|
||||
name = param.getAnImmediateUse().asExpr().(Parameter).getName()
|
||||
or
|
||||
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
|
||||
result = param.getMember(name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility class that is equivalent to `boolean` but does not require type joining.
|
||||
*/
|
||||
class Boolean extends boolean {
|
||||
Boolean() { this = true or this = false }
|
||||
}
|
||||
|
||||
/** Get the access path for the node. This includes structural information like `member`, `param`, and `functionalarg` if `includeStructuralInfo` is true. */
|
||||
string getAccessPath(API::Node node, Boolean includeStructuralInfo) {
|
||||
node = API::moduleImport(result)
|
||||
or
|
||||
exists(API::Node base, string baseName |
|
||||
base.getDepth() < node.getDepth() and baseName = getAccessPath(base, includeStructuralInfo)
|
||||
|
|
||||
// e.g. `new X`, `X()`
|
||||
node = [base.getInstance(), base.getReturn()] and
|
||||
if includeStructuralInfo = true
|
||||
then result = baseName + " instanceorreturn"
|
||||
else result = baseName
|
||||
or
|
||||
// e.g. `x.y`, `x[y]`, `const { y } = x`, where `y` is non-numeric and is known at analysis
|
||||
// time.
|
||||
exists(string member |
|
||||
node = base.getMember(member) and
|
||||
not node = base.getUnknownMember() and
|
||||
not isNumericString(member) and
|
||||
not (member = "default" and base = API::moduleImport(_)) and
|
||||
not member = "then" // use the 'promised' edges for .then callbacks
|
||||
|
|
||||
if includeStructuralInfo = true
|
||||
then result = baseName + " member " + member
|
||||
else result = baseName + " " + member
|
||||
)
|
||||
or
|
||||
// e.g. `x.y`, `x[y]`, `const { y } = x`, where `y` is numeric or not known at analysis time.
|
||||
(
|
||||
node = base.getUnknownMember() or
|
||||
node = base.getMember(any(string s | isNumericString(s)))
|
||||
) and
|
||||
if includeStructuralInfo = true then result = baseName + " member" else result = baseName
|
||||
or
|
||||
// e.g. `x.then(y => ...)`
|
||||
node = base.getPromised() and
|
||||
result = baseName
|
||||
or
|
||||
// e.g. `x.y((a, b) => ...)`
|
||||
// Name callback parameters after their name in the source code.
|
||||
// For example, the `res` parameter in `express.get('/foo', (req, res) => {...})` will be
|
||||
// named `express member get functionalarg param res`.
|
||||
exists(string paramName |
|
||||
node = getNamedParameter(base.getAParameter(), paramName) and
|
||||
(
|
||||
if includeStructuralInfo = true
|
||||
then result = baseName + " functionalarg param " + paramName
|
||||
else result = baseName + " " + paramName
|
||||
)
|
||||
or
|
||||
exists(string callbackName, string index |
|
||||
node =
|
||||
getNamedParameter(base.getASuccessor("param " + index).getMember(callbackName),
|
||||
paramName) and
|
||||
index != "-1" and // ignore receiver
|
||||
if includeStructuralInfo = true
|
||||
then
|
||||
result =
|
||||
baseName + " functionalarg " + index + " " + callbackName + " param " + paramName
|
||||
else result = baseName + " " + index + " " + callbackName + " " + paramName
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a name of a supported generic token-based feature. */
|
||||
private string getASupportedFeatureName() {
|
||||
result =
|
||||
[
|
||||
"enclosingFunctionName", "calleeName", "receiverName", "argumentIndex", "calleeApiName",
|
||||
"calleeAccessPath", "calleeAccessPathWithStructuralInfo", "enclosingFunctionBody"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic token-based features for ATM.
|
||||
*
|
||||
* This predicate holds if the generic token-based feature named `featureName` has the value
|
||||
* `featureValue` for the endpoint `endpoint`.
|
||||
*/
|
||||
predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
|
||||
featureName = getASupportedFeatureName() and
|
||||
(
|
||||
featureValue = unique(string x | x = getTokenFeature(endpoint, featureName))
|
||||
or
|
||||
not exists(unique(string x | x = getTokenFeature(endpoint, featureName))) and featureValue = ""
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
* Provides an implementation of scoring alerts for use in adaptive threat modeling (ATM).
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
import BaseScoring
|
||||
import CodeToFeatures
|
||||
import EndpointFeatures as EndpointFeatures
|
||||
import EndpointTypes
|
||||
|
||||
private string getACompatibleModelChecksum() {
|
||||
adaptiveThreatModelingModels(result, "javascript", _, "atm-endpoint-scoring")
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of AST nodes an entity containing an endpoint should have before we should
|
||||
* choose a smaller entity to represent the endpoint.
|
||||
*
|
||||
* This is intended to represent a balance in terms of the amount of context we provide to the
|
||||
* model: we don't want the function to be too small, because then it doesn't contain very much
|
||||
* context and miss useful information, but also we don't want it to be too large, because then
|
||||
* there's likely to be a lot of irrelevant or very loosely related context.
|
||||
*/
|
||||
private int getMaxNumAstNodes() { result = 1024 }
|
||||
|
||||
/**
|
||||
* Returns the number of AST nodes contained within the specified entity.
|
||||
*/
|
||||
private int getNumAstNodesInEntity(DatabaseFeatures::Entity entity) {
|
||||
// Restrict the values `entity` can take on
|
||||
entity = EndpointToEntity::getAnEntityForEndpoint(_) and
|
||||
result =
|
||||
count(DatabaseFeatures::AstNode astNode | DatabaseFeatures::astNodes(entity, _, _, astNode, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single entity to use as the representative entity for the endpoint.
|
||||
*
|
||||
* We try to use the largest entity containing the endpoint that's below the AST node limit defined
|
||||
* in `getMaxNumAstNodes`. In the event of a tie, we use the entity that appears first within the
|
||||
* source archive.
|
||||
*
|
||||
* If no entities are smaller than the AST node limit, then we use the smallest entity containing
|
||||
* the endpoint.
|
||||
*/
|
||||
DatabaseFeatures::Entity getRepresentativeEntityForEndpoint(DataFlow::Node endpoint) {
|
||||
// Check whether there's an entity containing the endpoint that's smaller than the AST node limit.
|
||||
if
|
||||
getNumAstNodesInEntity(EndpointToEntity::getAnEntityForEndpoint(endpoint)) <=
|
||||
getMaxNumAstNodes()
|
||||
then
|
||||
// Use the largest entity smaller than the AST node limit, resolving ties using the entity that
|
||||
// appears first in the source archive.
|
||||
result =
|
||||
rank[1](DatabaseFeatures::Entity entity, int numAstNodes, Location l |
|
||||
entity = EndpointToEntity::getAnEntityForEndpoint(endpoint) and
|
||||
numAstNodes = getNumAstNodesInEntity(entity) and
|
||||
numAstNodes <= getMaxNumAstNodes() and
|
||||
l = entity.getLocation()
|
||||
|
|
||||
entity
|
||||
order by
|
||||
numAstNodes desc, l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn()
|
||||
)
|
||||
else
|
||||
// Use the smallest entity, resolving ties using the entity that
|
||||
// appears first in the source archive.
|
||||
result =
|
||||
rank[1](DatabaseFeatures::Entity entity, int numAstNodes, Location l |
|
||||
entity = EndpointToEntity::getAnEntityForEndpoint(endpoint) and
|
||||
numAstNodes = getNumAstNodesInEntity(entity) and
|
||||
l = entity.getLocation()
|
||||
|
|
||||
entity
|
||||
order by
|
||||
numAstNodes, l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn()
|
||||
)
|
||||
}
|
||||
|
||||
module ModelScoring {
|
||||
predicate endpoints(DataFlow::Node endpoint) {
|
||||
getCfg().isEffectiveSource(endpoint) or
|
||||
getCfg().isEffectiveSink(endpoint)
|
||||
}
|
||||
|
||||
private int requestedEndpointTypes() { result = any(EndpointType type).getEncoding() }
|
||||
|
||||
private predicate relevantTokenFeatures(
|
||||
DataFlow::Node endpoint, string featureName, string featureValue
|
||||
) {
|
||||
endpoints(endpoint) and
|
||||
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
|
||||
}
|
||||
|
||||
predicate endpointScores(DataFlow::Node endpoint, int encodedEndpointType, float score) =
|
||||
scoreEndpoints(endpoints/1, requestedEndpointTypes/0, relevantTokenFeatures/3,
|
||||
getACompatibleModelChecksum/0)(endpoint, encodedEndpointType, score)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ATM's confidence that `source` is a source for the given security query. This will be a
|
||||
* number between 0.0 and 1.0.
|
||||
*/
|
||||
private float getScoreForSource(DataFlow::Node source) {
|
||||
if getCfg().isKnownSource(source)
|
||||
then result = 1.0
|
||||
else (
|
||||
// This restriction on `source` has no semantic effect but improves performance.
|
||||
getCfg().isEffectiveSource(source) and
|
||||
ModelScoring::endpointScores(source, getCfg().getASourceEndpointType().getEncoding(), result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ATM's confidence that `sink` is a sink for the given security query. This will be a
|
||||
* number between 0.0 and 1.0.
|
||||
*/
|
||||
private float getScoreForSink(DataFlow::Node sink) {
|
||||
if getCfg().isKnownSink(sink)
|
||||
then result = 1.0
|
||||
else
|
||||
if getCfg().isEffectiveSinkWithOverridingScore(sink, result, _)
|
||||
then any()
|
||||
else (
|
||||
// This restriction on `sink` has no semantic effect but improves performance.
|
||||
getCfg().isEffectiveSink(sink) and
|
||||
ModelScoring::endpointScores(sink, getCfg().getASinkEndpointType().getEncoding(), result)
|
||||
)
|
||||
}
|
||||
|
||||
class EndpointScoringResults extends ScoringResults {
|
||||
EndpointScoringResults() {
|
||||
this = "EndpointScoringResults" and exists(getACompatibleModelChecksum())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ATM's confidence that a path between `source` and `sink` represents a security
|
||||
* vulnerability. This will be a number between 0.0 and 1.0.
|
||||
*/
|
||||
override float getScoreForFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
result = getScoreForSource(source) * getScoreForSink(sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representing why ATM included the given source in the dataflow analysis.
|
||||
*
|
||||
* In general, there may be multiple reasons why ATM included the given source, in which case
|
||||
* this predicate should have multiple results.
|
||||
*/
|
||||
pragma[inline]
|
||||
override string getASourceOrigin(DataFlow::Node source) {
|
||||
result = "known" and getCfg().isKnownSource(source)
|
||||
or
|
||||
result = "predicted" and getCfg().isEffectiveSource(source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representing why ATM included the given sink in the dataflow analysis.
|
||||
*
|
||||
* In general, there may be multiple reasons why ATM included the given sink, in which case
|
||||
* this predicate should have multiple results.
|
||||
*/
|
||||
pragma[inline]
|
||||
override string getASinkOrigin(DataFlow::Node sink) {
|
||||
result = "known" and getCfg().isKnownSink(sink)
|
||||
or
|
||||
not getCfg().isKnownSink(sink) and
|
||||
getCfg().isEffectiveSinkWithOverridingScore(sink, _, result)
|
||||
or
|
||||
not getCfg().isKnownSink(sink) and
|
||||
not getCfg().isEffectiveSinkWithOverridingScore(sink, _, _) and
|
||||
result =
|
||||
"predicted (scores: " +
|
||||
concat(EndpointType type, float score |
|
||||
ModelScoring::endpointScores(sink, type.getEncoding(), score)
|
||||
|
|
||||
type.getDescription() + "=" + score.toString(), ", " order by type.getEncoding()
|
||||
) + ")" and
|
||||
getCfg().isEffectiveSink(sink)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
override predicate shouldResultBeIncluded(DataFlow::Node source, DataFlow::Node sink) {
|
||||
if getCfg().isKnownSink(sink)
|
||||
then any()
|
||||
else
|
||||
if getCfg().isEffectiveSinkWithOverridingScore(sink, _, _)
|
||||
then
|
||||
exists(float score |
|
||||
getCfg().isEffectiveSinkWithOverridingScore(sink, score, _) and
|
||||
score >= getCfg().getScoreCutoff()
|
||||
)
|
||||
else (
|
||||
// This restriction on `sink` has no semantic effect but improves performance.
|
||||
getCfg().isEffectiveSink(sink) and
|
||||
exists(float sinkScore |
|
||||
ModelScoring::endpointScores(sink, getCfg().getASinkEndpointType().getEncoding(),
|
||||
sinkScore) and
|
||||
// Include the endpoint if (a) the query endpoint type scores higher than all other
|
||||
// endpoint types, or (b) the query endpoint type scores at least
|
||||
// 0.5 - (getCfg().getScoreCutoff() / 2).
|
||||
sinkScore >=
|
||||
[
|
||||
max(float s | ModelScoring::endpointScores(sink, _, s)),
|
||||
0.5 - getCfg().getScoreCutoff() / 2
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module Debugging {
|
||||
query predicate hopInputEndpoints = ModelScoring::endpoints/1;
|
||||
|
||||
query predicate endpointScores = ModelScoring::endpointScores/3;
|
||||
|
||||
query predicate shouldResultBeIncluded(DataFlow::Node source, DataFlow::Node sink) {
|
||||
any(ScoringResults scoringResults).shouldResultBeIncluded(source, sink) and
|
||||
any(DataFlow::Configuration cfg).hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines the set of classes that endpoint scoring models can predict. Endpoint scoring models must
|
||||
* only predict classes defined within this file. This file is the source of truth for the integer
|
||||
* representation of each of these classes.
|
||||
*/
|
||||
newtype TEndpointType =
|
||||
TNotASinkType() or
|
||||
TXssSinkType() or
|
||||
TNosqlInjectionSinkType() or
|
||||
TSqlInjectionSinkType() or
|
||||
TTaintedPathSinkType()
|
||||
|
||||
/** A class that can be predicted by endpoint scoring models. */
|
||||
abstract class EndpointType extends TEndpointType {
|
||||
abstract string getDescription();
|
||||
|
||||
abstract int getEncoding();
|
||||
|
||||
string toString() { result = getDescription() }
|
||||
}
|
||||
|
||||
/** The `NotASink` class that can be predicted by endpoint scoring models. */
|
||||
class NotASinkType extends EndpointType, TNotASinkType {
|
||||
override string getDescription() { result = "NotASink" }
|
||||
|
||||
override int getEncoding() { result = 0 }
|
||||
}
|
||||
|
||||
/** The `XssSink` class that can be predicted by endpoint scoring models. */
|
||||
class XssSinkType extends EndpointType, TXssSinkType {
|
||||
override string getDescription() { result = "XssSink" }
|
||||
|
||||
override int getEncoding() { result = 1 }
|
||||
}
|
||||
|
||||
/** The `NosqlInjectionSink` class that can be predicted by endpoint scoring models. */
|
||||
class NosqlInjectionSinkType extends EndpointType, TNosqlInjectionSinkType {
|
||||
override string getDescription() { result = "NosqlInjectionSink" }
|
||||
|
||||
override int getEncoding() { result = 2 }
|
||||
}
|
||||
|
||||
/** The `SqlInjectionSink` class that can be predicted by endpoint scoring models. */
|
||||
class SqlInjectionSinkType extends EndpointType, TSqlInjectionSinkType {
|
||||
override string getDescription() { result = "SqlInjectionSink" }
|
||||
|
||||
override int getEncoding() { result = 3 }
|
||||
}
|
||||
|
||||
/** The `TaintedPathSink` class that can be predicted by endpoint scoring models. */
|
||||
class TaintedPathSinkType extends EndpointType, TTaintedPathSinkType {
|
||||
override string getDescription() { result = "TaintedPathSink" }
|
||||
|
||||
override int getEncoding() { result = 4 }
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines a set of reasons why a particular endpoint was filtered out. This set of reasons
|
||||
* contains both reasons why an endpoint could be `NotASink` and reasons why an endpoint could be
|
||||
* `LikelyNotASink`. The `NotASinkReason`s defined here are exhaustive, but the
|
||||
* `LikelyNotASinkReason`s are not exhaustive.
|
||||
*/
|
||||
newtype TFilteringReason =
|
||||
TIsArgumentToBuiltinFunctionReason() or
|
||||
TLodashUnderscoreArgumentReason() or
|
||||
TClientRequestReason() or
|
||||
TPromiseDefinitionReason() or
|
||||
TCryptographicKeyReason() or
|
||||
TCryptographicOperationFlowReason() or
|
||||
TLoggerMethodReason() or
|
||||
TTimeoutReason() or
|
||||
TReceiverStorageReason() or
|
||||
TStringStartsWithReason() or
|
||||
TStringEndsWithReason() or
|
||||
TStringRegExpTestReason() or
|
||||
TEventRegistrationReason() or
|
||||
TEventDispatchReason() or
|
||||
TMembershipCandidateTestReason() or
|
||||
TFileSystemAccessReason() or
|
||||
TDatabaseAccessReason() or
|
||||
TDOMReason() or
|
||||
TNextFunctionCallReason() or
|
||||
TArgumentToArrayReason() or
|
||||
TArgumentToBuiltinGlobalVarRefReason() or
|
||||
TConstantReceiverReason() or
|
||||
TBuiltinCallNameReason()
|
||||
|
||||
/** A reason why a particular endpoint was filtered out by the endpoint filters. */
|
||||
abstract class FilteringReason extends TFilteringReason {
|
||||
abstract string getDescription();
|
||||
|
||||
abstract int getEncoding();
|
||||
|
||||
string toString() { result = getDescription() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason why a particular endpoint might be considered to be `NotASink`.
|
||||
*
|
||||
* An endpoint is `NotASink` if it has at least one `NotASinkReason`, it does not have any
|
||||
* `LikelyNotASinkReason`s, and it is not a known sink.
|
||||
*/
|
||||
abstract class NotASinkReason extends FilteringReason { }
|
||||
|
||||
/**
|
||||
* A reason why a particular endpoint might be considered to be `LikelyNotASink`.
|
||||
*
|
||||
* An endpoint is `LikelyNotASink` if it has at least one `LikelyNotASinkReason` and it is not a
|
||||
* known sink.
|
||||
*/
|
||||
abstract class LikelyNotASinkReason extends FilteringReason { }
|
||||
|
||||
class IsArgumentToBuiltinFunctionReason extends NotASinkReason, TIsArgumentToBuiltinFunctionReason {
|
||||
override string getDescription() { result = "IsArgumentToBuiltinFunction" }
|
||||
|
||||
override int getEncoding() { result = 5 }
|
||||
}
|
||||
|
||||
class LodashUnderscoreArgumentReason extends NotASinkReason, TLodashUnderscoreArgumentReason {
|
||||
override string getDescription() { result = "LodashUnderscoreArgument" }
|
||||
|
||||
override int getEncoding() { result = 6 }
|
||||
}
|
||||
|
||||
class ClientRequestReason extends NotASinkReason, TClientRequestReason {
|
||||
override string getDescription() { result = "ClientRequest" }
|
||||
|
||||
override int getEncoding() { result = 7 }
|
||||
}
|
||||
|
||||
class PromiseDefinitionReason extends NotASinkReason, TPromiseDefinitionReason {
|
||||
override string getDescription() { result = "PromiseDefinition" }
|
||||
|
||||
override int getEncoding() { result = 8 }
|
||||
}
|
||||
|
||||
class CryptographicKeyReason extends NotASinkReason, TCryptographicKeyReason {
|
||||
override string getDescription() { result = "CryptographicKey" }
|
||||
|
||||
override int getEncoding() { result = 9 }
|
||||
}
|
||||
|
||||
class CryptographicOperationFlowReason extends NotASinkReason, TCryptographicOperationFlowReason {
|
||||
override string getDescription() { result = "CryptographicOperationFlow" }
|
||||
|
||||
override int getEncoding() { result = 10 }
|
||||
}
|
||||
|
||||
class LoggerMethodReason extends NotASinkReason, TLoggerMethodReason {
|
||||
override string getDescription() { result = "LoggerMethod" }
|
||||
|
||||
override int getEncoding() { result = 11 }
|
||||
}
|
||||
|
||||
class TimeoutReason extends NotASinkReason, TTimeoutReason {
|
||||
override string getDescription() { result = "Timeout" }
|
||||
|
||||
override int getEncoding() { result = 12 }
|
||||
}
|
||||
|
||||
class ReceiverStorageReason extends NotASinkReason, TReceiverStorageReason {
|
||||
override string getDescription() { result = "ReceiverStorage" }
|
||||
|
||||
override int getEncoding() { result = 13 }
|
||||
}
|
||||
|
||||
class StringStartsWithReason extends NotASinkReason, TStringStartsWithReason {
|
||||
override string getDescription() { result = "StringStartsWith" }
|
||||
|
||||
override int getEncoding() { result = 14 }
|
||||
}
|
||||
|
||||
class StringEndsWithReason extends NotASinkReason, TStringEndsWithReason {
|
||||
override string getDescription() { result = "StringEndsWith" }
|
||||
|
||||
override int getEncoding() { result = 15 }
|
||||
}
|
||||
|
||||
class StringRegExpTestReason extends NotASinkReason, TStringRegExpTestReason {
|
||||
override string getDescription() { result = "StringRegExpTest" }
|
||||
|
||||
override int getEncoding() { result = 16 }
|
||||
}
|
||||
|
||||
class EventRegistrationReason extends NotASinkReason, TEventRegistrationReason {
|
||||
override string getDescription() { result = "EventRegistration" }
|
||||
|
||||
override int getEncoding() { result = 17 }
|
||||
}
|
||||
|
||||
class EventDispatchReason extends NotASinkReason, TEventDispatchReason {
|
||||
override string getDescription() { result = "EventDispatch" }
|
||||
|
||||
override int getEncoding() { result = 18 }
|
||||
}
|
||||
|
||||
class MembershipCandidateTestReason extends NotASinkReason, TMembershipCandidateTestReason {
|
||||
override string getDescription() { result = "MembershipCandidateTest" }
|
||||
|
||||
override int getEncoding() { result = 19 }
|
||||
}
|
||||
|
||||
class FileSystemAccessReason extends NotASinkReason, TFileSystemAccessReason {
|
||||
override string getDescription() { result = "FileSystemAccess" }
|
||||
|
||||
override int getEncoding() { result = 20 }
|
||||
}
|
||||
|
||||
class DatabaseAccessReason extends NotASinkReason, TDatabaseAccessReason {
|
||||
override string getDescription() { result = "DatabaseAccess" }
|
||||
|
||||
override int getEncoding() { result = 21 }
|
||||
}
|
||||
|
||||
class DOMReason extends NotASinkReason, TDOMReason {
|
||||
override string getDescription() { result = "DOM" }
|
||||
|
||||
override int getEncoding() { result = 22 }
|
||||
}
|
||||
|
||||
class NextFunctionCallReason extends NotASinkReason, TNextFunctionCallReason {
|
||||
override string getDescription() { result = "NextFunctionCall" }
|
||||
|
||||
override int getEncoding() { result = 23 }
|
||||
}
|
||||
|
||||
class ArgumentToArrayReason extends LikelyNotASinkReason, TArgumentToArrayReason {
|
||||
override string getDescription() { result = "ArgumentToArray" }
|
||||
|
||||
override int getEncoding() { result = 24 }
|
||||
}
|
||||
|
||||
class ArgumentToBuiltinGlobalVarRefReason extends LikelyNotASinkReason,
|
||||
TArgumentToBuiltinGlobalVarRefReason {
|
||||
override string getDescription() { result = "ArgumentToBuiltinGlobalVarRef" }
|
||||
|
||||
override int getEncoding() { result = 25 }
|
||||
}
|
||||
|
||||
class ConstantReceiverReason extends NotASinkReason, TConstantReceiverReason {
|
||||
override string getDescription() { result = "ConstantReceiver" }
|
||||
|
||||
override int getEncoding() { result = 26 }
|
||||
}
|
||||
|
||||
class BuiltinCallNameReason extends NotASinkReason, TBuiltinCallNameReason {
|
||||
override string getDescription() { result = "BuiltinCallName" }
|
||||
|
||||
override int getEncoding() { result = 27 }
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines shared code used by the NoSQL injection boosted query.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.heuristics.SyntacticHeuristics
|
||||
private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
|
||||
private import semmle.javascript.security.TaintedObject
|
||||
import AdaptiveThreatModeling
|
||||
private import CoreKnowledge as CoreKnowledge
|
||||
private import StandardEndpointFilters as StandardEndpointFilters
|
||||
|
||||
module SinkEndpointFilter {
|
||||
/**
|
||||
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
|
||||
*
|
||||
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
|
||||
* effective sink.
|
||||
*/
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
|
||||
(
|
||||
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
// Require NoSQL injection sink candidates to be direct arguments to external library calls.
|
||||
//
|
||||
// The standard endpoint filters allow sink candidates which are within object literals or
|
||||
// array literals, for example `req.sendFile(_, { path: ENDPOINT })`.
|
||||
//
|
||||
// However, the NoSQL injection query deals differently with these types of sinks compared to
|
||||
// other security queries. Other security queries such as SQL injection tend to treat
|
||||
// `ENDPOINT` as the ground truth sink, but the NoSQL injection query instead treats
|
||||
// `{ path: ENDPOINT }` as the ground truth sink and defines an additional flow step to ensure
|
||||
// data flows from `ENDPOINT` to the ground truth sink `{ path: ENDPOINT }`.
|
||||
//
|
||||
// Therefore for the NoSQL injection boosted query, we must explicitly ignore sink candidates
|
||||
// within object literals or array literals, to avoid having multiple alerts for the same
|
||||
// security vulnerability (one FP where the sink is `ENDPOINT` and one TP where the sink is
|
||||
// `{ path: ENDPOINT }`).
|
||||
//
|
||||
// We use the same reason as in the standard endpoint filters to avoid duplicate reasons for
|
||||
// endpoints that are neither direct nor indirect arguments to a likely external library call.
|
||||
not sinkCandidate = StandardEndpointFilters::getALikelyExternalLibraryCall().getAnArgument() and
|
||||
result = "not an argument to a likely external library call"
|
||||
or
|
||||
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
|
||||
// additional databases accesses that aren't modeled yet
|
||||
call.(DataFlow::MethodCallNode).getMethodName() =
|
||||
["create", "createCollection", "createIndexes"] and
|
||||
result = "matches database access call heuristic"
|
||||
or
|
||||
// Remove modeled sinks
|
||||
CoreKnowledge::isArgumentToKnownLibrarySinkFunction(sinkCandidate) and
|
||||
result = "modeled sink"
|
||||
or
|
||||
// Remove common kinds of unlikely sinks
|
||||
CoreKnowledge::isKnownStepSrc(sinkCandidate) and
|
||||
result = "predecessor in a modeled flow step"
|
||||
or
|
||||
// Remove modeled database calls. Arguments to modeled calls are very likely to be modeled
|
||||
// as sinks if they are true positives. Therefore arguments that are not modeled as sinks
|
||||
// are unlikely to be true positives.
|
||||
call instanceof DatabaseAccess and
|
||||
result = "modeled database access"
|
||||
or
|
||||
// Remove calls to APIs that aren't relevant to NoSQL injection
|
||||
call.getReceiver().asExpr() instanceof HTTP::RequestExpr and
|
||||
result = "receiver is a HTTP request expression"
|
||||
or
|
||||
call.getReceiver().asExpr() instanceof HTTP::ResponseExpr and
|
||||
result = "receiver is a HTTP response expression"
|
||||
)
|
||||
) and
|
||||
not (
|
||||
// Explicitly allow the following heuristic sinks.
|
||||
//
|
||||
// These are copied from the `HeuristicNosqlInjectionSink` class defined within
|
||||
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
|
||||
// We can't reuse the class because importing that file would cause us to treat these
|
||||
// heuristic sinks as known sinks.
|
||||
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(nosql|query)") or
|
||||
isArgTo(sinkCandidate, "(?i)(query)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class NosqlInjectionATMConfig extends ATMConfig {
|
||||
NosqlInjectionATMConfig() { this = "NosqlInjectionATMConfig" }
|
||||
|
||||
override predicate isKnownSource(DataFlow::Node source) {
|
||||
source instanceof NosqlInjection::Source or TaintedObject::isSource(source, _)
|
||||
}
|
||||
|
||||
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof NosqlInjection::Sink }
|
||||
|
||||
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
|
||||
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
|
||||
}
|
||||
|
||||
override EndpointType getASinkEndpointType() { result instanceof NosqlInjectionSinkType }
|
||||
}
|
||||
|
||||
/** Holds if src -> trg is an additional flow step in the non-boosted NoSQL injection security query. */
|
||||
predicate isBaseAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
|
||||
) {
|
||||
TaintedObject::step(src, trg, inlbl, outlbl)
|
||||
or
|
||||
// additional flow step to track taint through NoSQL query objects
|
||||
inlbl = TaintedObject::label() and
|
||||
outlbl = TaintedObject::label() and
|
||||
exists(NoSQL::Query query, DataFlow::SourceNode queryObj |
|
||||
queryObj.flowsToExpr(query) and
|
||||
queryObj.flowsTo(trg) and
|
||||
src = queryObj.getAPropertyWrite().getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate allows us to propagate data flow through property writes and array constructors
|
||||
* within a query object, enabling the security query to pick up NoSQL injection vulnerabilities
|
||||
* involving more complex queries.
|
||||
*/
|
||||
DataFlow::Node getASubexpressionWithinQuery(DataFlow::Node query) {
|
||||
exists(DataFlow::SourceNode receiver |
|
||||
receiver.flowsTo(getASubexpressionWithinQuery*(query.getALocalSource())) and
|
||||
result =
|
||||
[
|
||||
receiver.(DataFlow::SourceNode).getAPropertyWrite().getRhs(),
|
||||
receiver.(DataFlow::ArrayCreationNode).getAnElement()
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about NoSQL injection vulnerabilities.
|
||||
*
|
||||
* This is largely a copy of the taint tracking configuration for the standard NoSQL injection
|
||||
* query, except additional ATM sinks have been added and the additional flow step has been
|
||||
* generalised to cover the sinks predicted by ATM.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "NosqlInjectionATM" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof NosqlInjection::Source }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
TaintedObject::isSource(source, label)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
sink.(NosqlInjection::Sink).getAFlowLabel() = label
|
||||
or
|
||||
// Allow effective sinks to have any taint label
|
||||
any(NosqlInjectionATMConfig cfg).isEffectiveSink(sink)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof NosqlInjection::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof TaintedObject::SanitizerGuard
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
|
||||
) {
|
||||
// additional flow steps from the base (non-boosted) security query
|
||||
isBaseAdditionalFlowStep(src, trg, inlbl, outlbl)
|
||||
or
|
||||
// relaxed version of previous step to track taint through unmodeled NoSQL query objects
|
||||
any(NosqlInjectionATMConfig cfg).isEffectiveSink(trg) and
|
||||
src = getASubexpressionWithinQuery(trg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines shared code used by the SQL injection boosted query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.heuristics.SyntacticHeuristics
|
||||
import semmle.javascript.security.dataflow.SqlInjectionCustomizations
|
||||
import AdaptiveThreatModeling
|
||||
import CoreKnowledge as CoreKnowledge
|
||||
import StandardEndpointFilters as StandardEndpointFilters
|
||||
|
||||
/**
|
||||
* This module provides logic to filter candidate sinks to those which are likely SQL injection
|
||||
* sinks.
|
||||
*/
|
||||
module SinkEndpointFilter {
|
||||
private import javascript
|
||||
private import SQL
|
||||
|
||||
/**
|
||||
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
|
||||
*
|
||||
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
|
||||
* effective sink.
|
||||
*/
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
|
||||
(
|
||||
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
|
||||
// prepared statements for SQL
|
||||
any(DataFlow::CallNode cn | cn.getCalleeName() = "prepare")
|
||||
.getAMethodCall("run")
|
||||
.getAnArgument() = sinkCandidate and
|
||||
result = "prepared SQL statement"
|
||||
or
|
||||
sinkCandidate instanceof DataFlow::ArrayCreationNode and
|
||||
result = "array creation"
|
||||
or
|
||||
// UI is unrelated to SQL
|
||||
call.getCalleeName().regexpMatch("(?i).*(render|html).*") and
|
||||
result = "HTML / rendering"
|
||||
)
|
||||
) and
|
||||
not (
|
||||
// Explicitly allow the following heuristic sinks.
|
||||
//
|
||||
// These are copied from the `HeuristicSqlInjectionSink` class defined within
|
||||
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
|
||||
// We can't reuse the class because importing that file would cause us to treat these
|
||||
// heuristic sinks as known sinks.
|
||||
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(sql|query)") or
|
||||
isArgTo(sinkCandidate, "(?i)(query)") or
|
||||
isConcatenatedWithString(sinkCandidate,
|
||||
"(?s).*(ALTER|COUNT|CREATE|DATABASE|DELETE|DISTINCT|DROP|FROM|GROUP|INSERT|INTO|LIMIT|ORDER|SELECT|TABLE|UPDATE|WHERE).*")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SqlInjectionATMConfig extends ATMConfig {
|
||||
SqlInjectionATMConfig() { this = "SqlInjectionATMConfig" }
|
||||
|
||||
override predicate isKnownSource(DataFlow::Node source) { source instanceof SqlInjection::Source }
|
||||
|
||||
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof SqlInjection::Sink }
|
||||
|
||||
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
|
||||
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
|
||||
}
|
||||
|
||||
override EndpointType getASinkEndpointType() { result instanceof SqlInjectionSinkType }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about SQL injection vulnerabilities.
|
||||
*
|
||||
* This is largely a copy of the taint tracking configuration for the standard SQL injection
|
||||
* query, except additional sinks have been added using the sink endpoint filter.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SqlInjectionATM" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SqlInjection::Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink instanceof SqlInjection::Sink or any(SqlInjectionATMConfig cfg).isEffectiveSink(sink)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof SqlInjection::Sanitizer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Provides classes and predicates that are useful for endpoint filters.
|
||||
*
|
||||
* The standard use of this library is to make use of `isPotentialEffectiveSink/1`
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
|
||||
private import semmle.javascript.heuristics.SyntacticHeuristics
|
||||
private import CoreKnowledge as CoreKnowledge
|
||||
|
||||
/** Provides a set of reasons why a given data flow node should be excluded as a sink candidate. */
|
||||
string getAReasonSinkExcluded(DataFlow::Node n) {
|
||||
not flowsToArgumentOfLikelyExternalLibraryCall(n) and
|
||||
result = "not an argument to a likely external library call"
|
||||
or
|
||||
isArgumentToModeledFunction(n) and result = "argument to modeled function"
|
||||
or
|
||||
isArgumentToSinklessLibrary(n) and result = "argument to sinkless library"
|
||||
or
|
||||
isSanitizer(n) and result = "sanitizer"
|
||||
or
|
||||
isPredicate(n) and result = "predicate"
|
||||
or
|
||||
isHash(n) and result = "hash"
|
||||
or
|
||||
isNumeric(n) and result = "numeric"
|
||||
or
|
||||
// Ignore candidate sinks within externs, generated, library, and test code
|
||||
exists(string category | category = ["externs", "generated", "library", "test"] |
|
||||
ClassifyFiles::classify(n.getFile(), category) and
|
||||
result = "in " + category + " file"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is an argument to a function that has a manual model.
|
||||
*/
|
||||
predicate isArgumentToModeledFunction(DataFlow::Node n) {
|
||||
exists(DataFlow::InvokeNode invk, DataFlow::Node known |
|
||||
invk.getAnArgument() = n and invk.getAnArgument() = known and isSomeModeledArgument(known)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the node `n` is an argument that has a manual model.
|
||||
*/
|
||||
predicate isSomeModeledArgument(DataFlow::Node n) {
|
||||
CoreKnowledge::isKnownLibrarySink(n) or
|
||||
CoreKnowledge::isKnownStepSrc(n) or
|
||||
CoreKnowledge::isOtherModeledArgument(n, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` appears to be a numeric value.
|
||||
*/
|
||||
predicate isNumeric(DataFlow::Node n) { isReadFrom(n, ".*index.*") }
|
||||
|
||||
/**
|
||||
* Holds if `n` is an argument to a library without sinks.
|
||||
*/
|
||||
predicate isArgumentToSinklessLibrary(DataFlow::Node n) {
|
||||
exists(DataFlow::InvokeNode invk, DataFlow::SourceNode commonSafeLibrary, string libraryName |
|
||||
libraryName = ["slugify", "striptags", "marked"]
|
||||
|
|
||||
commonSafeLibrary = DataFlow::moduleImport(libraryName) and
|
||||
invk = [commonSafeLibrary, commonSafeLibrary.getAPropertyRead()].getAnInvocation() and
|
||||
n = invk.getAnArgument()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isSanitizer(DataFlow::Node n) {
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
call.getCalleeName().regexpMatch("(?i).*(escape|valid(ate)?|sanitize|purify).*")
|
||||
)
|
||||
}
|
||||
|
||||
predicate isPredicate(DataFlow::Node n) {
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
call.getCalleeName().regexpMatch("(equals|(|is|has|can)(_|[A-Z])).*")
|
||||
)
|
||||
}
|
||||
|
||||
predicate isHash(DataFlow::Node n) {
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
call.getCalleeName().regexpMatch("(?i)^(sha\\d*|md5|hash)$")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the data flow node is a (possibly indirect) argument of a likely external library call.
|
||||
*
|
||||
* This includes direct arguments of likely external library calls as well as nested object
|
||||
* literals within those calls.
|
||||
*/
|
||||
predicate flowsToArgumentOfLikelyExternalLibraryCall(DataFlow::Node n) {
|
||||
n = getACallWithoutCallee().getAnArgument()
|
||||
or
|
||||
exists(DataFlow::SourceNode src | flowsToArgumentOfLikelyExternalLibraryCall(src) |
|
||||
n = src.getAPropertyWrite().getRhs()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ArrayCreationNode arr | flowsToArgumentOfLikelyExternalLibraryCall(arr) |
|
||||
n = arr.getAnElement()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calls which are likely to be to external non-built-in libraries.
|
||||
*/
|
||||
DataFlow::CallNode getALikelyExternalLibraryCall() { result = getACallWithoutCallee() }
|
||||
|
||||
/**
|
||||
* Gets a node that flows to callback-parameter `p`.
|
||||
*/
|
||||
private DataFlow::SourceNode getACallback(DataFlow::ParameterNode p, DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = p and
|
||||
any(DataFlow::FunctionNode f).getLastParameter() = p and
|
||||
exists(p.getACall())
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = getACallback(p, t2).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calls for which we do not have the callee (i.e. the definition of the called function). This
|
||||
* acts as a heuristic for identifying calls to external library functions.
|
||||
*/
|
||||
private DataFlow::CallNode getACallWithoutCallee() {
|
||||
forall(Function callee | callee = result.getACallee() | callee.getTopLevel().isExterns()) and
|
||||
not exists(DataFlow::ParameterNode param, DataFlow::FunctionNode callback |
|
||||
param.flowsTo(result.getCalleeNode()) and
|
||||
callback = getACallback(param, DataFlow::TypeBackTracker::end())
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines shared code used by the path injection boosted query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.heuristics.SyntacticHeuristics
|
||||
import semmle.javascript.security.dataflow.TaintedPathCustomizations
|
||||
import AdaptiveThreatModeling
|
||||
import CoreKnowledge as CoreKnowledge
|
||||
import StandardEndpointFilters as StandardEndpointFilters
|
||||
|
||||
/**
|
||||
* This module provides logic to filter candidate sinks to those which are likely path injection
|
||||
* sinks.
|
||||
*/
|
||||
module SinkEndpointFilter {
|
||||
private import javascript
|
||||
private import TaintedPath
|
||||
|
||||
/**
|
||||
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
|
||||
*
|
||||
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
|
||||
* effective sink.
|
||||
*/
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
|
||||
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate) and
|
||||
not (
|
||||
// Explicitly allow the following heuristic sinks.
|
||||
//
|
||||
// These are mostly copied from the `HeuristicTaintedPathSink` class defined within
|
||||
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
|
||||
// We can't reuse the class because importing that file would cause us to treat these
|
||||
// heuristic sinks as known sinks.
|
||||
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(file|folder|dir|absolute)")
|
||||
or
|
||||
isArgTo(sinkCandidate, "(?i)(get|read)file")
|
||||
or
|
||||
exists(string pathPattern |
|
||||
// paths with at least two parts, and either a trailing or leading slash
|
||||
pathPattern = "(?i)([a-z0-9_.-]+/){2,}" or
|
||||
pathPattern = "(?i)(/[a-z0-9_.-]+){2,}"
|
||||
|
|
||||
isConcatenatedWithString(sinkCandidate, pathPattern)
|
||||
)
|
||||
or
|
||||
isConcatenatedWithStrings(".*/", sinkCandidate, "/.*")
|
||||
or
|
||||
// In addition to the names from `HeuristicTaintedPathSink` in the
|
||||
// `isAssignedToOrConcatenatedWith` predicate call above, we also allow the noisier "path"
|
||||
// name.
|
||||
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)path")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TaintedPathATMConfig extends ATMConfig {
|
||||
TaintedPathATMConfig() { this = "TaintedPathATMConfig" }
|
||||
|
||||
override predicate isKnownSource(DataFlow::Node source) { source instanceof TaintedPath::Source }
|
||||
|
||||
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof TaintedPath::Sink }
|
||||
|
||||
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
|
||||
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
|
||||
}
|
||||
|
||||
override EndpointType getASinkEndpointType() { result instanceof TaintedPathSinkType }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about path injection vulnerabilities.
|
||||
*
|
||||
* This is largely a copy of the taint tracking configuration for the standard path injection
|
||||
* query, except additional ATM sinks have been added to the `isSink` predicate.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TaintedPathATM" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof TaintedPath::Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
|
||||
label = sink.(TaintedPath::Sink).getAFlowLabel()
|
||||
or
|
||||
// Allow effective sinks to have any taint label
|
||||
any(TaintedPathATMConfig cfg).isEffectiveSink(sink)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof TaintedPath::Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
|
||||
node instanceof BarrierGuardNodeAsSanitizerGuardNode
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
|
||||
DataFlow::FlowLabel dstlabel
|
||||
) {
|
||||
TaintedPath::isAdditionalTaintedPathFlowStep(src, dst, srclabel, dstlabel)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides sanitizer guards for path injection.
|
||||
*
|
||||
* The standard library path injection query uses a data flow configuration, and therefore defines
|
||||
* barrier nodes. However we're using a taint tracking configuration for path injection to find new
|
||||
* kinds of less certain results. Since taint tracking configurations use sanitizer guards instead
|
||||
* of barrier guards, we port the barrier guards for the boosted query from the standard library to
|
||||
* sanitizer guards here.
|
||||
*/
|
||||
class BarrierGuardNodeAsSanitizerGuardNode extends TaintTracking::LabeledSanitizerGuardNode {
|
||||
BarrierGuardNodeAsSanitizerGuardNode() { this instanceof TaintedPath::BarrierGuardNode }
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
blocks(outcome, e) or blocks(outcome, e, _)
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
sanitizes(outcome, e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* Defines shared code used by the XSS boosted query.
|
||||
*/
|
||||
|
||||
private import semmle.javascript.heuristics.SyntacticHeuristics
|
||||
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
import AdaptiveThreatModeling
|
||||
import CoreKnowledge as CoreKnowledge
|
||||
import StandardEndpointFilters as StandardEndpointFilters
|
||||
|
||||
/**
|
||||
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
|
||||
*/
|
||||
module SinkEndpointFilter {
|
||||
private import javascript
|
||||
private import DomBasedXss
|
||||
|
||||
/**
|
||||
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
|
||||
*
|
||||
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
|
||||
* effective sink.
|
||||
*/
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
|
||||
(
|
||||
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
|
||||
call.getCalleeName() = "setState"
|
||||
) and
|
||||
result = "setState calls ought to be safe in react applications"
|
||||
) and
|
||||
not (
|
||||
// Explicitly allow the following heuristic sinks.
|
||||
//
|
||||
// These are copied from the `HeuristicDomBasedXssSink` class defined within
|
||||
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
|
||||
// We can't reuse the class because importing that file would cause us to treat these
|
||||
// heuristic sinks as known sinks.
|
||||
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(html|innerhtml)")
|
||||
or
|
||||
isArgTo(sinkCandidate, "(?i)(html|render)")
|
||||
or
|
||||
sinkCandidate instanceof StringOps::HtmlConcatenationLeaf
|
||||
or
|
||||
isConcatenatedWithStrings("(?is).*<[a-z ]+.*", sinkCandidate, "(?s).*>.*")
|
||||
or
|
||||
// In addition to the heuristic sinks from `HeuristicDomBasedXssSink`, explicitly allow
|
||||
// property writes like `elem.innerHTML = <TAINT>` that may not be picked up as HTML
|
||||
// concatenation leaves.
|
||||
exists(DataFlow::PropWrite pw |
|
||||
pw.getPropertyName().regexpMatch("(?i).*html*") and
|
||||
pw.getRhs() = sinkCandidate
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DomBasedXssATMConfig extends ATMConfig {
|
||||
DomBasedXssATMConfig() { this = "DomBasedXssATMConfig" }
|
||||
|
||||
override predicate isKnownSource(DataFlow::Node source) { source instanceof DomBasedXss::Source }
|
||||
|
||||
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof DomBasedXss::Sink }
|
||||
|
||||
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
|
||||
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
|
||||
}
|
||||
|
||||
override EndpointType getASinkEndpointType() { result instanceof XssSinkType }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about XSS vulnerabilities.
|
||||
*
|
||||
* This is largely a copy of the taint tracking configuration for the standard XSSThroughDom query,
|
||||
* except additional ATM sinks have been added to the `isSink` predicate.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "DomBasedXssATMConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof DomBasedXss::Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink instanceof DomBasedXss::Sink or
|
||||
any(DomBasedXssATMConfig cfg).isEffectiveSink(sink)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof DomBasedXss::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof DomBasedXss::SanitizerGuard
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
name: codeql/javascript-experimental-atm-lib
|
||||
version: 0.0.0
|
||||
extractor: javascript
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/javascript-all: "*"
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* @name NoSQL database query built from user-controlled sources (boosted)
|
||||
* @description Building a database query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious code by the user.
|
||||
* @kind path-problem
|
||||
* @scored
|
||||
* @problem.severity error
|
||||
* @security-severity 8.8
|
||||
* @id adaptive-threat-modeling/js/nosql-injection
|
||||
* @tags experimental experimental/atm security
|
||||
*/
|
||||
|
||||
import ATM::ResultsInfo
|
||||
import DataFlow::PathGraph
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM
|
||||
|
||||
from
|
||||
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
|
||||
string scoreString
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
|
||||
score = getScoreForFlow(source.getNode(), sink.getNode()) and
|
||||
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"[Score = " + scoreString + "] This may be a NoSQL query depending on $@ " +
|
||||
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
|
||||
"a user-provided value", score
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* @name SQL database query built from user-controlled sources (boosted)
|
||||
* @description Building a database query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious code by the user.
|
||||
* @kind path-problem
|
||||
* @scored
|
||||
* @problem.severity error
|
||||
* @security-severity 8.8
|
||||
* @id adaptive-threat-modeling/js/sql-injection
|
||||
* @tags experimental experimental/atm security
|
||||
*/
|
||||
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM
|
||||
import ATM::ResultsInfo
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
|
||||
string scoreString
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
|
||||
score = getScoreForFlow(source.getNode(), sink.getNode()) and
|
||||
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"[Score = " + scoreString + "] This may be a js/sql result depending on $@ " +
|
||||
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
|
||||
"a user-provided value", score
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* @name Uncontrolled data used in path expression (boosted)
|
||||
* @description Accessing paths influenced by users can allow an attacker to access
|
||||
* unexpected resources.
|
||||
* @kind path-problem
|
||||
* @scored
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @id adaptive-threat-modeling/js/path-injection
|
||||
* @tags experimental experimental/atm security
|
||||
*/
|
||||
|
||||
import ATM::ResultsInfo
|
||||
import DataFlow::PathGraph
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM
|
||||
|
||||
from
|
||||
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
|
||||
string scoreString
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
|
||||
score = getScoreForFlow(source.getNode(), sink.getNode()) and
|
||||
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"[Score = " + scoreString + "] This may be a js/path-injection result depending on $@ " +
|
||||
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
|
||||
"a user-provided value", score
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* For internal use only.
|
||||
*
|
||||
* @name Client-side cross-site scripting (boosted)
|
||||
* @description Writing user input directly to the DOM allows for
|
||||
* a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @scored
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @id adaptive-threat-modeling/js/xss
|
||||
* @tags experimental experimental/atm security
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import ATM::ResultsInfo
|
||||
import DataFlow::PathGraph
|
||||
import experimental.adaptivethreatmodeling.XssATM
|
||||
|
||||
from
|
||||
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
|
||||
string scoreString
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
|
||||
score = getScoreForFlow(source.getNode(), sink.getNode()) and
|
||||
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
|
||||
select sink.getNode(), source, sink,
|
||||
"[Score = " + scoreString + "] This may be a js/xss result depending on $@ " +
|
||||
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
|
||||
"a user-provided value", score
|
||||
@@ -0,0 +1,8 @@
|
||||
- description: ATM boosted Code Scanning queries for JavaScript
|
||||
- queries: .
|
||||
- include:
|
||||
id:
|
||||
- adaptive-threat-modeling/js/nosql-injection
|
||||
- adaptive-threat-modeling/js/sql-injection
|
||||
- adaptive-threat-modeling/js/path-injection
|
||||
- adaptive-threat-modeling/js/xss
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
dependencies: {}
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
@@ -0,0 +1,7 @@
|
||||
name: codeql/javascript-experimental-atm-queries
|
||||
language: javascript
|
||||
version: 0.0.0
|
||||
suites: codeql-suites
|
||||
defaultSuiteFile: codeql-suites/javascript-atm-code-scanning.qls
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-lib: "*"
|
||||
@@ -0,0 +1 @@
|
||||
<queries language="javascript"/>
|
||||
@@ -179,15 +179,7 @@ module DOM {
|
||||
eltName = attr.getElement().getName() and
|
||||
attrName = attr.getName()
|
||||
|
|
||||
(
|
||||
eltName = "script" or
|
||||
eltName = "iframe" or
|
||||
eltName = "embed" or
|
||||
eltName = "video" or
|
||||
eltName = "audio" or
|
||||
eltName = "source" or
|
||||
eltName = "track"
|
||||
) and
|
||||
eltName = ["script", "iframe", "embed", "video", "audio", "source", "track"] and
|
||||
attrName = "src"
|
||||
or
|
||||
(
|
||||
@@ -258,11 +250,11 @@ module DOM {
|
||||
/** Gets a call that queries the DOM for a collection of DOM nodes. */
|
||||
private DataFlow::SourceNode domElementCollection() {
|
||||
exists(string collectionName |
|
||||
collectionName = "getElementsByClassName" or
|
||||
collectionName = "getElementsByName" or
|
||||
collectionName = "getElementsByTagName" or
|
||||
collectionName = "getElementsByTagNameNS" or
|
||||
collectionName = "querySelectorAll"
|
||||
collectionName =
|
||||
[
|
||||
"getElementsByClassName", "getElementsByName", "getElementsByTagName",
|
||||
"getElementsByTagNameNS", "querySelectorAll"
|
||||
]
|
||||
|
|
||||
(
|
||||
result = documentRef().getAMethodCall(collectionName) or
|
||||
@@ -274,11 +266,8 @@ module DOM {
|
||||
/** Gets a call that creates a DOM node or queries the DOM for a DOM node. */
|
||||
private DataFlow::SourceNode domElementCreationOrQuery() {
|
||||
exists(string methodName |
|
||||
methodName = "createElement" or
|
||||
methodName = "createElementNS" or
|
||||
methodName = "createRange" or
|
||||
methodName = "getElementById" or
|
||||
methodName = "querySelector"
|
||||
methodName =
|
||||
["createElement", "createElementNS", "createRange", "getElementById", "querySelector"]
|
||||
|
|
||||
result = documentRef().getAMethodCall(methodName) or
|
||||
result = DataFlow::globalVarRef(methodName).getACall()
|
||||
@@ -465,11 +454,7 @@ module DOM {
|
||||
private class DefaultRange extends Range {
|
||||
DefaultRange() {
|
||||
exists(string propName | this = documentRef().getAPropertyRead(propName) |
|
||||
propName = "documentURI" or
|
||||
propName = "documentURIObject" or
|
||||
propName = "location" or
|
||||
propName = "referrer" or
|
||||
propName = "URL"
|
||||
propName = ["documentURI", "documentURIObject", "location", "referrer", "URL"]
|
||||
)
|
||||
or
|
||||
this = DOM::domValueRef().getAPropertyRead("baseUri")
|
||||
|
||||
@@ -14,7 +14,7 @@ private import semmle.javascript.dataflow.internal.FlowSteps
|
||||
SourceNode getAnEnumeratedArrayElement(SourceNode array) {
|
||||
exists(MethodCallNode call, string name |
|
||||
call = array.getAMethodCall(name) and
|
||||
(name = "forEach" or name = "map") and
|
||||
name = ["forEach", "map"] and
|
||||
result = call.getCallback(0).getParameter(0)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -34,7 +34,7 @@ abstract class Container extends @container {
|
||||
/**
|
||||
* Gets a URL representing the location of this container.
|
||||
*
|
||||
* For more information see [Providing URLs](https://help.semmle.com/QL/learn-ql/ql/locations.html#providing-urls).
|
||||
* For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls).
|
||||
*/
|
||||
abstract string getURL();
|
||||
|
||||
@@ -246,13 +246,7 @@ class File extends Container, @file {
|
||||
* A file type.
|
||||
*/
|
||||
class FileType extends string {
|
||||
FileType() {
|
||||
this = "javascript" or
|
||||
this = "html" or
|
||||
this = "typescript" or
|
||||
this = "json" or
|
||||
this = "yaml"
|
||||
}
|
||||
FileType() { this = ["javascript", "html", "typescript", "json", "yaml"] }
|
||||
|
||||
/**
|
||||
* Holds if this is the JavaScript file type.
|
||||
|
||||
@@ -291,13 +291,7 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
|
||||
|
||||
override predicate isNumbery() {
|
||||
exists(string name | name = getName() |
|
||||
name = "number" or
|
||||
name = "Number" or
|
||||
name = "double" or
|
||||
name = "Double" or
|
||||
name = "int" or
|
||||
name = "integer" or
|
||||
name = "Integer"
|
||||
name = ["number", "Number", "double", "Double", "int", "integer", "Integer"]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import javascript
|
||||
* A location as given by a file, a start line, a start column,
|
||||
* an end line, and an end column.
|
||||
*
|
||||
* For more information about locations see [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
class Location extends @location {
|
||||
/** Gets the file for this location. */
|
||||
@@ -70,7 +70,7 @@ class Location extends @location {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -30,7 +30,11 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
getAnExportFromModule(any(PackageJSON pack | exists(pack.getPackageName())).getMainModule())
|
||||
or
|
||||
// module.exports.bar.baz = result;
|
||||
result = getAValueExportedByPackage().(DataFlow::PropWrite).getRhs()
|
||||
exists(DataFlow::PropWrite write |
|
||||
write = getAValueExportedByPackage() and
|
||||
write.getPropertyName() = publicPropertyName() and
|
||||
result = write.getRhs()
|
||||
)
|
||||
or
|
||||
// class Foo {
|
||||
// bar() {} // <- result
|
||||
@@ -39,15 +43,17 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
exists(DataFlow::SourceNode callee |
|
||||
callee = getAValueExportedByPackage().(DataFlow::NewNode).getCalleeNode().getALocalSource()
|
||||
|
|
||||
result = callee.getAPropertyRead("prototype").getAPropertyWrite().getRhs()
|
||||
result = callee.getAPropertyRead("prototype").getAPropertyWrite(publicPropertyName()).getRhs()
|
||||
or
|
||||
result = callee.(DataFlow::ClassNode).getAnInstanceMethod()
|
||||
result = callee.(DataFlow::ClassNode).getInstanceMethod(publicPropertyName()) and
|
||||
not isPrivateMethodDeclaration(result)
|
||||
)
|
||||
or
|
||||
result = getAValueExportedByPackage().getALocalSource()
|
||||
or
|
||||
// Nested property reads.
|
||||
result = getAValueExportedByPackage().(DataFlow::SourceNode).getAPropertyReference()
|
||||
result =
|
||||
getAValueExportedByPackage().(DataFlow::SourceNode).getAPropertyReference(publicPropertyName())
|
||||
or
|
||||
// module.exports.foo = require("./other-module.js");
|
||||
exists(Module mod |
|
||||
@@ -61,9 +67,12 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
// static baz() {} // <- result
|
||||
// constructor() {} // <- result
|
||||
// };
|
||||
exists(DataFlow::ClassNode cla | cla = getAValueExportedByPackage() |
|
||||
result = cla.getAnInstanceMethod() or
|
||||
result = cla.getAStaticMethod() or
|
||||
exists(DataFlow::ClassNode cla |
|
||||
cla = getAValueExportedByPackage() and
|
||||
not isPrivateMethodDeclaration(result)
|
||||
|
|
||||
result = cla.getInstanceMethod(publicPropertyName()) or
|
||||
result = cla.getStaticMethod(publicPropertyName()) or
|
||||
result = cla.getConstructor()
|
||||
)
|
||||
or
|
||||
@@ -120,7 +129,8 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
or
|
||||
// Object.defineProperty
|
||||
exists(CallToObjectDefineProperty call |
|
||||
[call, call.getBaseObject()] = getAValueExportedByPackage()
|
||||
[call, call.getBaseObject()] = getAValueExportedByPackage() and
|
||||
call.getPropertyName() = publicPropertyName()
|
||||
|
|
||||
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
|
||||
or
|
||||
@@ -164,9 +174,31 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
* Gets an exported node from the module `mod`.
|
||||
*/
|
||||
private DataFlow::Node getAnExportFromModule(Module mod) {
|
||||
result = mod.getAnExportedValue(_)
|
||||
result = mod.getAnExportedValue(publicPropertyName())
|
||||
or
|
||||
result = mod.getABulkExportedNode()
|
||||
or
|
||||
result.analyze().getAValue() = TAbstractModuleObject(mod)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property name that we consider to be public.
|
||||
*
|
||||
* This only allows properties whose first character is a letter or number.
|
||||
*/
|
||||
bindingset[result]
|
||||
private string publicPropertyName() { result.regexpMatch("[a-zA-Z0-9].*") }
|
||||
|
||||
/**
|
||||
* Holds if the given function is part of a private (or protected) method declaration.
|
||||
*/
|
||||
private predicate isPrivateMethodDeclaration(DataFlow::FunctionNode func) {
|
||||
exists(MethodDeclaration decl |
|
||||
decl.getBody() = func.getFunction() and
|
||||
(
|
||||
decl.isPrivate()
|
||||
or
|
||||
decl.isProtected()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -236,10 +236,9 @@ module RangeAnalysis {
|
||||
) {
|
||||
if exists(r.getImmediatePredecessor())
|
||||
then linearDefinitionSum(r.getImmediatePredecessor(), xroot, xsign, yroot, ysign, bias)
|
||||
else
|
||||
if exists(r.asExpr().getIntValue())
|
||||
then none() // do not model constants as sums
|
||||
else (
|
||||
else (
|
||||
not exists(r.asExpr().getIntValue()) and // do not model constants as sums
|
||||
(
|
||||
exists(AddExpr add, int bias1, int bias2 | r.asExpr() = add |
|
||||
// r = r1 + r2
|
||||
linearDefinition(add.getLeftOperand().flow(), xroot, xsign, bias1) and
|
||||
@@ -257,6 +256,7 @@ module RangeAnalysis {
|
||||
linearDefinitionSum(r.asExpr().(NegExpr).getOperand().flow(), xroot, -xsign, yroot, -ysign,
|
||||
-bias)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ class FirstLineOf extends Locatable {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
@@ -43,7 +43,7 @@ class LastLineOf extends Locatable {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -417,7 +417,7 @@ class SsaVariable extends TSsaDefinition {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
@@ -482,7 +482,7 @@ class SsaDefinition extends TSsaDefinition {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
abstract predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -55,13 +55,7 @@ private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInv
|
||||
getNumArgument() = 2 and
|
||||
// Filter out library methods named 'forEach' etc
|
||||
not DataFlow::moduleImport(_).flowsTo(getReceiver()) and
|
||||
exists(string name | name = getMethodName() |
|
||||
name = "filter" or
|
||||
name = "forEach" or
|
||||
name = "map" or
|
||||
name = "some" or
|
||||
name = "every"
|
||||
)
|
||||
getMethodName() = ["filter", "forEach", "map", "some", "every"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
|
||||
|
||||
@@ -24,7 +24,7 @@ class XMLLocatable extends @xmllocatable, TXMLLocatable {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
@@ -108,7 +108,7 @@ class XMLParent extends @xmlparent {
|
||||
}
|
||||
|
||||
/** Gets the text value contained in this XML parent. */
|
||||
string getTextValue() { result = allCharactersString() }
|
||||
string getTextValue() { result = this.allCharactersString() }
|
||||
|
||||
/** Gets a printable representation of this XML parent. */
|
||||
string toString() { result = this.getName() }
|
||||
@@ -119,7 +119,7 @@ class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = getName() }
|
||||
override string toString() { result = this.getName() }
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
@@ -129,14 +129,14 @@ class XMLFile extends XMLParent, File {
|
||||
*
|
||||
* Gets the path of this XML file.
|
||||
*/
|
||||
deprecated string getPath() { result = getAbsolutePath() }
|
||||
deprecated string getPath() { result = this.getAbsolutePath() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
|
||||
*
|
||||
* Gets the path of the folder that contains this XML file.
|
||||
*/
|
||||
deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
|
||||
deprecated string getFolder() { result = this.getParentContainer().getAbsolutePath() }
|
||||
|
||||
/** Gets the encoding of this XML file. */
|
||||
string getEncoding() { xmlEncoding(this, result) }
|
||||
@@ -200,7 +200,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
|
||||
*/
|
||||
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
/** Holds if this XML element has the given `name`. */
|
||||
predicate hasName(string name) { name = getName() }
|
||||
predicate hasName(string name) { name = this.getName() }
|
||||
|
||||
/** Gets the name of this XML element. */
|
||||
override string getName() { xmlElements(this, result, _, _, _) }
|
||||
@@ -239,7 +239,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 = getName() }
|
||||
override string toString() { result = this.getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@ class AbstractValue extends TAbstractValue {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `f`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(string f, int startline, int startcolumn, int endline, int endcolumn) {
|
||||
f = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
|
||||
|
||||
@@ -1776,7 +1776,7 @@ class PathNode extends TPathNode {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -148,7 +148,7 @@ module DataFlow {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
cached
|
||||
predicate hasLocationInfo(
|
||||
@@ -1405,14 +1405,7 @@ module DataFlow {
|
||||
*/
|
||||
class Incompleteness extends string {
|
||||
Incompleteness() {
|
||||
this = "await" or
|
||||
this = "call" or
|
||||
this = "eval" or
|
||||
this = "global" or
|
||||
this = "heap" or
|
||||
this = "import" or
|
||||
this = "namespace" or
|
||||
this = "yield"
|
||||
this = ["await", "call", "eval", "global", "heap", "import", "namespace", "yield"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,7 @@ newtype TypeTag =
|
||||
*/
|
||||
class TypeofTag extends string {
|
||||
TypeofTag() {
|
||||
this = "undefined" or
|
||||
this = "boolean" or
|
||||
this = "number" or
|
||||
this = "string" or
|
||||
this = "function" or
|
||||
this = "object" or
|
||||
this = "symbol"
|
||||
this = ["undefined", "boolean", "number", "string", "function", "object", "symbol"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -924,13 +924,11 @@ module TaintTracking {
|
||||
pred = invoke.getArgument(0) and
|
||||
succ = invoke
|
||||
|
|
||||
name = "Error" or
|
||||
name = "EvalError" or
|
||||
name = "RangeError" or
|
||||
name = "ReferenceError" or
|
||||
name = "SyntaxError" or
|
||||
name = "TypeError" or
|
||||
name = "URIError"
|
||||
name =
|
||||
[
|
||||
"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError",
|
||||
"URIError"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ module Angular2 {
|
||||
/** A value that is about to be promoted to a trusted script value. */
|
||||
private class AngularCodeInjectionSink extends CodeInjection::Sink {
|
||||
AngularCodeInjectionSink() {
|
||||
this = domSanitizer().getAMethodCall(["bypassSecurityTrustScript"]).getArgument(0)
|
||||
this = domSanitizer().getAMethodCall("bypassSecurityTrustScript").getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,15 +177,10 @@ class ModuleApiCallDependencyInjection extends DependencyInjection {
|
||||
* This method excludes the method names that are also present on the AngularJS '$provide' object.
|
||||
*/
|
||||
private int injectableArgPos() {
|
||||
(
|
||||
methodName = "directive" or
|
||||
methodName = "filter" or
|
||||
methodName = "controller" or
|
||||
methodName = "animation"
|
||||
) and
|
||||
methodName = ["directive", "filter", "controller", "animation"] and
|
||||
result = 1
|
||||
or
|
||||
(methodName = "config" or methodName = "run") and
|
||||
methodName = ["config", "run"] and
|
||||
result = 0
|
||||
}
|
||||
|
||||
@@ -199,64 +194,17 @@ class ModuleApiCallDependencyInjection extends DependencyInjection {
|
||||
* (cf. https://docs.angularjs.org/api/ng/directive/).
|
||||
*/
|
||||
private predicate builtinDirective(string name) {
|
||||
name = "ngApp" or
|
||||
name = "ngBind" or
|
||||
name = "ngBindHtml" or
|
||||
name = "ngBindTemplate" or
|
||||
name = "ngBlur" or
|
||||
name = "ngChange" or
|
||||
name = "ngChecked" or
|
||||
name = "ngClass" or
|
||||
name = "ngClassEven" or
|
||||
name = "ngClassOdd" or
|
||||
name = "ngClick" or
|
||||
name = "ngCloak" or
|
||||
name = "ngController" or
|
||||
name = "ngCopy" or
|
||||
name = "ngCsp" or
|
||||
name = "ngCut" or
|
||||
name = "ngDblclick" or
|
||||
name = "ngDisabled" or
|
||||
name = "ngFocus" or
|
||||
name = "ngForm" or
|
||||
name = "ngHide" or
|
||||
name = "ngHref" or
|
||||
name = "ngIf" or
|
||||
name = "ngInclude" or
|
||||
name = "ngInit" or
|
||||
name = "ngJq" or
|
||||
name = "ngKeydown" or
|
||||
name = "ngKeypress" or
|
||||
name = "ngKeyup" or
|
||||
name = "ngList" or
|
||||
name = "ngMaxlength" or
|
||||
name = "ngMinlength" or
|
||||
name = "ngModel" or
|
||||
name = "ngModelOptions" or
|
||||
name = "ngMousedown" or
|
||||
name = "ngMouseenter" or
|
||||
name = "ngMouseleave" or
|
||||
name = "ngMousemove" or
|
||||
name = "ngMouseover" or
|
||||
name = "ngMouseup" or
|
||||
name = "ngNonBindable" or
|
||||
name = "ngOpen" or
|
||||
name = "ngOptions" or
|
||||
name = "ngPaste" or
|
||||
name = "ngPattern" or
|
||||
name = "ngPluralize" or
|
||||
name = "ngReadonly" or
|
||||
name = "ngRepeat" or
|
||||
name = "ngRequired" or
|
||||
name = "ngSelected" or
|
||||
name = "ngShow" or
|
||||
name = "ngSrc" or
|
||||
name = "ngSrcset" or
|
||||
name = "ngStyle" or
|
||||
name = "ngSubmit" or
|
||||
name = "ngSwitch" or
|
||||
name = "ngTransclude" or
|
||||
name = "ngValue"
|
||||
name =
|
||||
[
|
||||
"ngApp", "ngBind", "ngBindHtml", "ngBindTemplate", "ngBlur", "ngChange", "ngChecked",
|
||||
"ngClass", "ngClassEven", "ngClassOdd", "ngClick", "ngCloak", "ngController", "ngCopy",
|
||||
"ngCsp", "ngCut", "ngDblclick", "ngDisabled", "ngFocus", "ngForm", "ngHide", "ngHref", "ngIf",
|
||||
"ngInclude", "ngInit", "ngJq", "ngKeydown", "ngKeypress", "ngKeyup", "ngList", "ngMaxlength",
|
||||
"ngMinlength", "ngModel", "ngModelOptions", "ngMousedown", "ngMouseenter", "ngMouseleave",
|
||||
"ngMousemove", "ngMouseover", "ngMouseup", "ngNonBindable", "ngOpen", "ngOptions", "ngPaste",
|
||||
"ngPattern", "ngPluralize", "ngReadonly", "ngRepeat", "ngRequired", "ngSelected", "ngShow",
|
||||
"ngSrc", "ngSrcset", "ngStyle", "ngSubmit", "ngSwitch", "ngTransclude", "ngValue"
|
||||
]
|
||||
}
|
||||
|
||||
private newtype TDirectiveInstance =
|
||||
@@ -676,10 +624,7 @@ private class JQLiteObject extends JQuery::ObjectSource::Range {
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(ServiceReference element |
|
||||
element.getName() = "$rootElement" or
|
||||
element.getName() = "$document"
|
||||
|
|
||||
exists(ServiceReference element | element.getName() = ["$rootElement", "$document"] |
|
||||
this = element.getAReference()
|
||||
)
|
||||
}
|
||||
@@ -780,23 +725,17 @@ private class BuiltinServiceCall extends AngularJSCall {
|
||||
|
||||
override predicate interpretsArgumentAsCode(Expr e) {
|
||||
exists(ScopeServiceReference scope, string methodName |
|
||||
methodName = "$apply" or
|
||||
methodName = "$applyAsync" or
|
||||
methodName = "$eval" or
|
||||
methodName = "$evalAsync" or
|
||||
methodName = "$watch" or
|
||||
methodName = "$watchCollection" or
|
||||
methodName = "$watchGroup"
|
||||
methodName =
|
||||
[
|
||||
"$apply", "$applyAsync", "$eval", "$evalAsync", "$watch", "$watchCollection",
|
||||
"$watchGroup"
|
||||
]
|
||||
|
|
||||
call = scope.getAMethodCall(methodName) and
|
||||
e = call.getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(ServiceReference service |
|
||||
service.getName() = "$compile" or
|
||||
service.getName() = "$parse" or
|
||||
service.getName() = "$interpolate"
|
||||
|
|
||||
exists(ServiceReference service | service.getName() = ["$compile", "$parse", "$interpolate"] |
|
||||
call = service.getACall() and
|
||||
e = call.getArgument(0)
|
||||
)
|
||||
@@ -952,7 +891,7 @@ class ElementScope extends AngularScope, MkElementScope {
|
||||
DataFlow::SourceNode routeProviderRef() {
|
||||
result = builtinServiceRef("$routeProvider")
|
||||
or
|
||||
exists(string m | m = "when" or m = "otherwise" | result = routeProviderRef().getAMethodCall(m))
|
||||
exists(string m | m = ["when", "otherwise"] | result = routeProviderRef().getAMethodCall(m))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -277,24 +277,11 @@ private module Lexer {
|
||||
override string getPattern() {
|
||||
result =
|
||||
concat(string op |
|
||||
op = "===" or
|
||||
op = "!==" or
|
||||
op = "==" or
|
||||
op = "!=" or
|
||||
op = "<=" or
|
||||
op = ">=" or
|
||||
op = "&&" or
|
||||
op = "||" or
|
||||
op = "*" or
|
||||
op = "!" or
|
||||
op = "=" or
|
||||
op = "<" or
|
||||
op = ">" or
|
||||
op = "+" or
|
||||
op = "-" or
|
||||
op = "/" or
|
||||
op = "%" or
|
||||
op = "|"
|
||||
op =
|
||||
[
|
||||
"===", "!==", "==", "!=", "<=", ">=", "&&", "||", "*", "!", "=", "<", ">", "+", "-",
|
||||
"/", "%", "|"
|
||||
]
|
||||
|
|
||||
"\\Q" + op + "\\E", "|" order by op.length() desc
|
||||
)
|
||||
|
||||
@@ -103,25 +103,12 @@ module AsyncPackage {
|
||||
|
||||
IterationCall() {
|
||||
this = memberVariant(name).getACall() and
|
||||
(
|
||||
name = "concat" or
|
||||
name = "detect" or
|
||||
name = "each" or
|
||||
name = "eachOf" or
|
||||
name = "forEach" or
|
||||
name = "forEachOf" or
|
||||
name = "every" or
|
||||
name = "filter" or
|
||||
name = "groupBy" or
|
||||
name = "map" or
|
||||
name = "mapValues" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "reject" or
|
||||
name = "some" or
|
||||
name = "sortBy" or
|
||||
name = "transform"
|
||||
)
|
||||
name =
|
||||
[
|
||||
"concat", "detect", "each", "eachOf", "forEach", "forEachOf", "every", "filter",
|
||||
"groupBy", "map", "mapValues", "reduce", "reduceRight", "reject", "some", "sortBy",
|
||||
"transform"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,10 +163,7 @@ module AsyncPackage {
|
||||
pred = getLastParameter(iteratee).getACall().getArgument(i) and
|
||||
succ = final.getParameter(i) and
|
||||
exists(string name | name = call.getName() |
|
||||
name = "concat" or
|
||||
name = "map" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight"
|
||||
name = ["concat", "map", "reduce", "reduceRight"]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ module FunctionCompositionCall {
|
||||
/** A call whose arguments are functions `f,g,h` which are composed into `f(g(h(...))` */
|
||||
private class RightToLeft extends WithArrayOverloading {
|
||||
RightToLeft() {
|
||||
this = DataFlow::moduleImport(["compose-function"]).getACall()
|
||||
this = DataFlow::moduleImport("compose-function").getACall()
|
||||
or
|
||||
this =
|
||||
DataFlow::moduleMember(["redux", "ramda", "@reduxjs/toolkit", "recompose"], "compose")
|
||||
|
||||
@@ -408,14 +408,7 @@ private module Forge {
|
||||
this = mod.getAPropertyRead("cipher").getAMemberCall(createName) and
|
||||
getArgument(0).asExpr().mayHaveStringValue(cipherName) and
|
||||
cipherName = cipherPrefix + "-" + cipherSuffix and
|
||||
(
|
||||
cipherSuffix = "CBC" or
|
||||
cipherSuffix = "CFB" or
|
||||
cipherSuffix = "CTR" or
|
||||
cipherSuffix = "ECB" or
|
||||
cipherSuffix = "GCM" or
|
||||
cipherSuffix = "OFB"
|
||||
) and
|
||||
cipherSuffix = ["CBC", "CFB", "CTR", "ECB", "GCM", "OFB"] and
|
||||
algorithmName = cipherPrefix and
|
||||
key = getArgument(1)
|
||||
)
|
||||
|
||||
@@ -11,13 +11,7 @@ module EventEmitter {
|
||||
}
|
||||
|
||||
/** Gets the name of a method on `EventEmitter` that registers an event handler. */
|
||||
string on() {
|
||||
result = "addListener" or
|
||||
result = "on" or
|
||||
result = "once" or
|
||||
result = "prependListener" or
|
||||
result = "prependOnceListener"
|
||||
}
|
||||
string on() { result = ["addListener", "on", "once", "prependListener", "prependOnceListener"] }
|
||||
|
||||
/**
|
||||
* Gets a node that refers to an EventEmitter object.
|
||||
|
||||
@@ -378,23 +378,11 @@ module Express {
|
||||
*/
|
||||
private predicate isChainableResponseMethodCall(RouteHandler handler, MethodCallExpr call) {
|
||||
exists(string name | call.calls(handler.getAResponseExpr(), name) |
|
||||
name = "append" or
|
||||
name = "attachment" or
|
||||
name = "clearCookie" or
|
||||
name = "contentType" or
|
||||
name = "cookie" or
|
||||
name = "format" or
|
||||
name = "header" or
|
||||
name = "json" or
|
||||
name = "jsonp" or
|
||||
name = "links" or
|
||||
name = "location" or
|
||||
name = "send" or
|
||||
name = "sendStatus" or
|
||||
name = "set" or
|
||||
name = "status" or
|
||||
name = "type" or
|
||||
name = "vary"
|
||||
name =
|
||||
[
|
||||
"append", "attachment", "location", "send", "sendStatus", "set", "status", "type", "vary",
|
||||
"clearCookie", "contentType", "cookie", "format", "header", "json", "jsonp", "links"
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -429,12 +429,7 @@ private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
|
||||
this =
|
||||
DataFlow::moduleMember("node-dir",
|
||||
any(string s |
|
||||
s = "readFiles" or
|
||||
s = "readFilesStream" or
|
||||
s = "files" or
|
||||
s = "promiseFiles" or
|
||||
s = "subdirs" or
|
||||
s = "paths"
|
||||
s = ["readFiles", "readFilesStream", "files", "promiseFiles", "subdirs", "paths"]
|
||||
)).getACall()
|
||||
)
|
||||
or
|
||||
|
||||
@@ -81,29 +81,12 @@ module HTTP {
|
||||
*/
|
||||
class RequestMethodName extends string {
|
||||
RequestMethodName() {
|
||||
this = "CHECKOUT" or
|
||||
this = "COPY" or
|
||||
this = "DELETE" or
|
||||
this = "GET" or
|
||||
this = "HEAD" or
|
||||
this = "LOCK" or
|
||||
this = "MERGE" or
|
||||
this = "MKACTIVITY" or
|
||||
this = "MKCOL" or
|
||||
this = "MOVE" or
|
||||
this = "M-SEARCH" or
|
||||
this = "NOTIFY" or
|
||||
this = "OPTIONS" or
|
||||
this = "PATCH" or
|
||||
this = "POST" or
|
||||
this = "PURGE" or
|
||||
this = "PUT" or
|
||||
this = "REPORT" or
|
||||
this = "SEARCH" or
|
||||
this = "SUBSCRIBE" or
|
||||
this = "TRACE" or
|
||||
this = "UNLOCK" or
|
||||
this = "UNSUBSCRIBE"
|
||||
this =
|
||||
[
|
||||
"CHECKOUT", "COPY", "DELETE", "GET", "HEAD", "LOCK", "MERGE", "MKACTIVITY", "MKCOL",
|
||||
"MOVE", "M-SEARCH", "NOTIFY", "OPTIONS", "PATCH", "POST", "PURGE", "PUT", "REPORT",
|
||||
"SEARCH", "SUBSCRIBE", "TRACE", "UNLOCK", "UNSUBSCRIBE"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,14 +94,7 @@ module HTTP {
|
||||
* such as for `GET` and `HEAD` requests.
|
||||
*/
|
||||
predicate isSafe() {
|
||||
this = "GET" or
|
||||
this = "HEAD" or
|
||||
this = "OPTIONS" or
|
||||
this = "PRI" or
|
||||
this = "PROPFIND" or
|
||||
this = "REPORT" or
|
||||
this = "SEARCH" or
|
||||
this = "TRACE"
|
||||
this = ["GET", "HEAD", "OPTIONS", "PRI", "PROPFIND", "REPORT", "SEARCH", "TRACE"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,13 +453,7 @@ module HTTP {
|
||||
* Headers are never considered third-party controllable by this predicate, although the
|
||||
* third party does have some control over the the Referer and Origin headers.
|
||||
*/
|
||||
predicate isThirdPartyControllable() {
|
||||
exists(string kind | kind = getKind() |
|
||||
kind = "parameter" or
|
||||
kind = "url" or
|
||||
kind = "body"
|
||||
)
|
||||
}
|
||||
predicate isThirdPartyControllable() { getKind() = ["parameter", "url", "body"] }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,312 +47,50 @@ module LodashUnderscore {
|
||||
*/
|
||||
private predicate isLodashMember(string name) {
|
||||
// Can be generated using Object.keys(require('lodash'))
|
||||
name = "templateSettings" or
|
||||
name = "after" or
|
||||
name = "ary" or
|
||||
name = "assign" or
|
||||
name = "assignIn" or
|
||||
name = "assignInWith" or
|
||||
name = "assignWith" or
|
||||
name = "at" or
|
||||
name = "before" or
|
||||
name = "bind" or
|
||||
name = "bindAll" or
|
||||
name = "bindKey" or
|
||||
name = "castArray" or
|
||||
name = "chain" or
|
||||
name = "chunk" or
|
||||
name = "compact" or
|
||||
name = "concat" or
|
||||
name = "cond" or
|
||||
name = "conforms" or
|
||||
name = "constant" or
|
||||
name = "countBy" or
|
||||
name = "create" or
|
||||
name = "curry" or
|
||||
name = "curryRight" or
|
||||
name = "debounce" or
|
||||
name = "defaults" or
|
||||
name = "defaultsDeep" or
|
||||
name = "defer" or
|
||||
name = "delay" or
|
||||
name = "difference" or
|
||||
name = "differenceBy" or
|
||||
name = "differenceWith" or
|
||||
name = "drop" or
|
||||
name = "dropRight" or
|
||||
name = "dropRightWhile" or
|
||||
name = "dropWhile" or
|
||||
name = "fill" or
|
||||
name = "filter" or
|
||||
name = "flatMap" or
|
||||
name = "flatMapDeep" or
|
||||
name = "flatMapDepth" or
|
||||
name = "flatten" or
|
||||
name = "flattenDeep" or
|
||||
name = "flattenDepth" or
|
||||
name = "flip" or
|
||||
name = "flow" or
|
||||
name = "flowRight" or
|
||||
name = "fromPairs" or
|
||||
name = "functions" or
|
||||
name = "functionsIn" or
|
||||
name = "groupBy" or
|
||||
name = "initial" or
|
||||
name = "intersection" or
|
||||
name = "intersectionBy" or
|
||||
name = "intersectionWith" or
|
||||
name = "invert" or
|
||||
name = "invertBy" or
|
||||
name = "invokeMap" or
|
||||
name = "iteratee" or
|
||||
name = "keyBy" or
|
||||
name = "keys" or
|
||||
name = "keysIn" or
|
||||
name = "map" or
|
||||
name = "mapKeys" or
|
||||
name = "mapValues" or
|
||||
name = "matches" or
|
||||
name = "matchesProperty" or
|
||||
name = "memoize" or
|
||||
name = "merge" or
|
||||
name = "mergeWith" or
|
||||
name = "method" or
|
||||
name = "methodOf" or
|
||||
name = "mixin" or
|
||||
name = "negate" or
|
||||
name = "nthArg" or
|
||||
name = "omit" or
|
||||
name = "omitBy" or
|
||||
name = "once" or
|
||||
name = "orderBy" or
|
||||
name = "over" or
|
||||
name = "overArgs" or
|
||||
name = "overEvery" or
|
||||
name = "overSome" or
|
||||
name = "partial" or
|
||||
name = "partialRight" or
|
||||
name = "partition" or
|
||||
name = "pick" or
|
||||
name = "pickBy" or
|
||||
name = "property" or
|
||||
name = "propertyOf" or
|
||||
name = "pull" or
|
||||
name = "pullAll" or
|
||||
name = "pullAllBy" or
|
||||
name = "pullAllWith" or
|
||||
name = "pullAt" or
|
||||
name = "range" or
|
||||
name = "rangeRight" or
|
||||
name = "rearg" or
|
||||
name = "reject" or
|
||||
name = "remove" or
|
||||
name = "rest" or
|
||||
name = "reverse" or
|
||||
name = "sampleSize" or
|
||||
name = "set" or
|
||||
name = "setWith" or
|
||||
name = "shuffle" or
|
||||
name = "slice" or
|
||||
name = "sortBy" or
|
||||
name = "sortedUniq" or
|
||||
name = "sortedUniqBy" or
|
||||
name = "split" or
|
||||
name = "spread" or
|
||||
name = "tail" or
|
||||
name = "take" or
|
||||
name = "takeRight" or
|
||||
name = "takeRightWhile" or
|
||||
name = "takeWhile" or
|
||||
name = "tap" or
|
||||
name = "throttle" or
|
||||
name = "thru" or
|
||||
name = "toArray" or
|
||||
name = "toPairs" or
|
||||
name = "toPairsIn" or
|
||||
name = "toPath" or
|
||||
name = "toPlainObject" or
|
||||
name = "transform" or
|
||||
name = "unary" or
|
||||
name = "union" or
|
||||
name = "unionBy" or
|
||||
name = "unionWith" or
|
||||
name = "uniq" or
|
||||
name = "uniqBy" or
|
||||
name = "uniqWith" or
|
||||
name = "unset" or
|
||||
name = "unzip" or
|
||||
name = "unzipWith" or
|
||||
name = "update" or
|
||||
name = "updateWith" or
|
||||
name = "values" or
|
||||
name = "valuesIn" or
|
||||
name = "without" or
|
||||
name = "words" or
|
||||
name = "wrap" or
|
||||
name = "xor" or
|
||||
name = "xorBy" or
|
||||
name = "xorWith" or
|
||||
name = "zip" or
|
||||
name = "zipObject" or
|
||||
name = "zipObjectDeep" or
|
||||
name = "zipWith" or
|
||||
name = "entries" or
|
||||
name = "entriesIn" or
|
||||
name = "extend" or
|
||||
name = "extendWith" or
|
||||
name = "add" or
|
||||
name = "attempt" or
|
||||
name = "camelCase" or
|
||||
name = "capitalize" or
|
||||
name = "ceil" or
|
||||
name = "clamp" or
|
||||
name = "clone" or
|
||||
name = "cloneDeep" or
|
||||
name = "cloneDeepWith" or
|
||||
name = "cloneWith" or
|
||||
name = "conformsTo" or
|
||||
name = "deburr" or
|
||||
name = "defaultTo" or
|
||||
name = "divide" or
|
||||
name = "endsWith" or
|
||||
name = "eq" or
|
||||
name = "escape" or
|
||||
name = "escapeRegExp" or
|
||||
name = "every" or
|
||||
name = "find" or
|
||||
name = "findIndex" or
|
||||
name = "findKey" or
|
||||
name = "findLast" or
|
||||
name = "findLastIndex" or
|
||||
name = "findLastKey" or
|
||||
name = "floor" or
|
||||
name = "forEach" or
|
||||
name = "forEachRight" or
|
||||
name = "forIn" or
|
||||
name = "forInRight" or
|
||||
name = "forOwn" or
|
||||
name = "forOwnRight" or
|
||||
name = "get" or
|
||||
name = "gt" or
|
||||
name = "gte" or
|
||||
name = "has" or
|
||||
name = "hasIn" or
|
||||
name = "head" or
|
||||
name = "identity" or
|
||||
name = "includes" or
|
||||
name = "indexOf" or
|
||||
name = "inRange" or
|
||||
name = "invoke" or
|
||||
name = "isArguments" or
|
||||
name = "isArray" or
|
||||
name = "isArrayBuffer" or
|
||||
name = "isArrayLike" or
|
||||
name = "isArrayLikeObject" or
|
||||
name = "isBoolean" or
|
||||
name = "isBuffer" or
|
||||
name = "isDate" or
|
||||
name = "isElement" or
|
||||
name = "isEmpty" or
|
||||
name = "isEqual" or
|
||||
name = "isEqualWith" or
|
||||
name = "isError" or
|
||||
name = "isFinite" or
|
||||
name = "isFunction" or
|
||||
name = "isInteger" or
|
||||
name = "isLength" or
|
||||
name = "isMap" or
|
||||
name = "isMatch" or
|
||||
name = "isMatchWith" or
|
||||
name = "isNaN" or
|
||||
name = "isNative" or
|
||||
name = "isNil" or
|
||||
name = "isNull" or
|
||||
name = "isNumber" or
|
||||
name = "isObject" or
|
||||
name = "isObjectLike" or
|
||||
name = "isPlainObject" or
|
||||
name = "isRegExp" or
|
||||
name = "isSafeInteger" or
|
||||
name = "isSet" or
|
||||
name = "isString" or
|
||||
name = "isSymbol" or
|
||||
name = "isTypedArray" or
|
||||
name = "isUndefined" or
|
||||
name = "isWeakMap" or
|
||||
name = "isWeakSet" or
|
||||
name = "join" or
|
||||
name = "kebabCase" or
|
||||
name = "last" or
|
||||
name = "lastIndexOf" or
|
||||
name = "lowerCase" or
|
||||
name = "lowerFirst" or
|
||||
name = "lt" or
|
||||
name = "lte" or
|
||||
name = "max" or
|
||||
name = "maxBy" or
|
||||
name = "mean" or
|
||||
name = "meanBy" or
|
||||
name = "min" or
|
||||
name = "minBy" or
|
||||
name = "stubArray" or
|
||||
name = "stubFalse" or
|
||||
name = "stubObject" or
|
||||
name = "stubString" or
|
||||
name = "stubTrue" or
|
||||
name = "multiply" or
|
||||
name = "nth" or
|
||||
name = "noConflict" or
|
||||
name = "noop" or
|
||||
name = "now" or
|
||||
name = "pad" or
|
||||
name = "padEnd" or
|
||||
name = "padStart" or
|
||||
name = "parseInt" or
|
||||
name = "random" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "repeat" or
|
||||
name = "replace" or
|
||||
name = "result" or
|
||||
name = "round" or
|
||||
name = "runInContext" or
|
||||
name = "sample" or
|
||||
name = "size" or
|
||||
name = "snakeCase" or
|
||||
name = "some" or
|
||||
name = "sortedIndex" or
|
||||
name = "sortedIndexBy" or
|
||||
name = "sortedIndexOf" or
|
||||
name = "sortedLastIndex" or
|
||||
name = "sortedLastIndexBy" or
|
||||
name = "sortedLastIndexOf" or
|
||||
name = "startCase" or
|
||||
name = "startsWith" or
|
||||
name = "subtract" or
|
||||
name = "sum" or
|
||||
name = "sumBy" or
|
||||
name = "template" or
|
||||
name = "times" or
|
||||
name = "toFinite" or
|
||||
name = "toInteger" or
|
||||
name = "toLength" or
|
||||
name = "toLower" or
|
||||
name = "toNumber" or
|
||||
name = "toSafeInteger" or
|
||||
name = "toString" or
|
||||
name = "toUpper" or
|
||||
name = "trim" or
|
||||
name = "trimEnd" or
|
||||
name = "trimStart" or
|
||||
name = "truncate" or
|
||||
name = "unescape" or
|
||||
name = "uniqueId" or
|
||||
name = "upperCase" or
|
||||
name = "upperFirst" or
|
||||
name = "each" or
|
||||
name = "eachRight" or
|
||||
name = "first"
|
||||
name =
|
||||
[
|
||||
"templateSettings", "after", "ary", "assign", "assignIn", "assignInWith", "assignWith",
|
||||
"at", "before", "bind", "bindAll", "bindKey", "castArray", "chain", "chunk", "compact",
|
||||
"concat", "cond", "conforms", "constant", "countBy", "create", "curry", "curryRight",
|
||||
"debounce", "defaults", "defaultsDeep", "defer", "delay", "difference", "differenceBy",
|
||||
"differenceWith", "drop", "dropRight", "dropRightWhile", "dropWhile", "fill", "filter",
|
||||
"flatMap", "flatMapDeep", "flatMapDepth", "flatten", "flattenDeep", "flattenDepth", "flip",
|
||||
"flow", "flowRight", "fromPairs", "functions", "functionsIn", "groupBy", "initial",
|
||||
"intersection", "intersectionBy", "intersectionWith", "invert", "invertBy", "invokeMap",
|
||||
"iteratee", "keyBy", "keys", "keysIn", "map", "mapKeys", "mapValues", "matches",
|
||||
"matchesProperty", "memoize", "merge", "mergeWith", "method", "methodOf", "mixin", "negate",
|
||||
"nthArg", "omit", "omitBy", "once", "orderBy", "over", "overArgs", "overEvery", "overSome",
|
||||
"partial", "partialRight", "partition", "pick", "pickBy", "property", "propertyOf", "pull",
|
||||
"pullAll", "pullAllBy", "pullAllWith", "pullAt", "range", "rangeRight", "rearg", "reject",
|
||||
"remove", "rest", "reverse", "sampleSize", "set", "setWith", "shuffle", "slice", "sortBy",
|
||||
"sortedUniq", "sortedUniqBy", "split", "spread", "tail", "take", "takeRight",
|
||||
"takeRightWhile", "takeWhile", "tap", "throttle", "thru", "toArray", "toPairs", "toPairsIn",
|
||||
"toPath", "toPlainObject", "transform", "unary", "union", "unionBy", "unionWith", "uniq",
|
||||
"uniqBy", "uniqWith", "unset", "unzip", "unzipWith", "update", "updateWith", "values",
|
||||
"valuesIn", "without", "words", "wrap", "xor", "xorBy", "xorWith", "zip", "zipObject",
|
||||
"zipObjectDeep", "zipWith", "entries", "entriesIn", "extend", "extendWith", "add",
|
||||
"attempt", "camelCase", "capitalize", "ceil", "clamp", "clone", "cloneDeep",
|
||||
"cloneDeepWith", "cloneWith", "conformsTo", "deburr", "defaultTo", "divide", "endsWith",
|
||||
"eq", "escape", "escapeRegExp", "every", "find", "findIndex", "findKey", "findLast",
|
||||
"findLastIndex", "findLastKey", "floor", "forEach", "forEachRight", "forIn", "forInRight",
|
||||
"forOwn", "forOwnRight", "get", "gt", "gte", "has", "hasIn", "head", "identity", "includes",
|
||||
"indexOf", "inRange", "invoke", "isArguments", "isArray", "isArrayBuffer", "isArrayLike",
|
||||
"isArrayLikeObject", "isBoolean", "isBuffer", "isDate", "isElement", "isEmpty", "isEqual",
|
||||
"isEqualWith", "isError", "isFinite", "isFunction", "isInteger", "isLength", "isMap",
|
||||
"isMatch", "isMatchWith", "isNaN", "isNative", "isNil", "isNull", "isNumber", "isObject",
|
||||
"isObjectLike", "isPlainObject", "isRegExp", "isSafeInteger", "isSet", "isString",
|
||||
"isSymbol", "isTypedArray", "isUndefined", "isWeakMap", "isWeakSet", "join", "kebabCase",
|
||||
"last", "lastIndexOf", "lowerCase", "lowerFirst", "lt", "lte", "max", "maxBy", "mean",
|
||||
"meanBy", "min", "minBy", "stubArray", "stubFalse", "stubObject", "stubString", "stubTrue",
|
||||
"multiply", "nth", "noConflict", "noop", "now", "pad", "padEnd", "padStart", "parseInt",
|
||||
"random", "reduce", "reduceRight", "repeat", "replace", "result", "round", "runInContext",
|
||||
"sample", "size", "snakeCase", "some", "sortedIndex", "sortedIndexBy", "sortedIndexOf",
|
||||
"sortedLastIndex", "sortedLastIndexBy", "sortedLastIndexOf", "startCase", "startsWith",
|
||||
"subtract", "sum", "sumBy", "template", "times", "toFinite", "toInteger", "toLength",
|
||||
"toLower", "toNumber", "toSafeInteger", "toString", "toUpper", "trim", "trimEnd",
|
||||
"trimStart", "truncate", "unescape", "uniqueId", "upperCase", "upperFirst", "each",
|
||||
"eachRight", "first"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,27 +101,15 @@ module LodashUnderscore {
|
||||
exists(DataFlow::CallNode call, string name |
|
||||
// Members ending with By, With, or While indicate that they are a variant of
|
||||
// another function that takes a callback.
|
||||
name.matches("%By") or
|
||||
name.matches("%With") or
|
||||
name.matches("%While") or
|
||||
name.matches(["%By", "%With", "%While"])
|
||||
or
|
||||
// Other members that don't fit the above pattern.
|
||||
name = "each" or
|
||||
name = "eachRight" or
|
||||
name = "every" or
|
||||
name = "filter" or
|
||||
name = "find" or
|
||||
name = "findLast" or
|
||||
name = "flatMap" or
|
||||
name = "flatMapDeep" or
|
||||
name = "flatMapDepth" or
|
||||
name = "forEach" or
|
||||
name = "forEachRight" or
|
||||
name = "partition" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "replace" or
|
||||
name = "some" or
|
||||
name = "transform"
|
||||
name =
|
||||
[
|
||||
"each", "eachRight", "every", "filter", "find", "findLast", "flatMap", "flatMapDeep",
|
||||
"flatMapDepth", "forEach", "forEachRight", "partition", "reduce", "reduceRight",
|
||||
"replace", "some", "transform"
|
||||
]
|
||||
|
|
||||
call = member(name).getACall() and
|
||||
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
|
||||
@@ -461,91 +187,30 @@ private class LodashCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode:
|
||||
this = LodashUnderscore::member(name).getACall() and
|
||||
getNumArgument() = argumentCount
|
||||
|
|
||||
(
|
||||
name = "bind" or
|
||||
name = "callback" or
|
||||
name = "iteratee"
|
||||
) and
|
||||
name = ["bind", "callback", "iteratee"] and
|
||||
callbackIndex = 0 and
|
||||
contextIndex = 1 and
|
||||
argumentCount = 2
|
||||
or
|
||||
(
|
||||
name = "all" or
|
||||
name = "any" or
|
||||
name = "collect" or
|
||||
name = "countBy" or
|
||||
name = "detect" or
|
||||
name = "dropRightWhile" or
|
||||
name = "dropWhile" or
|
||||
name = "each" or
|
||||
name = "eachRight" or
|
||||
name = "every" or
|
||||
name = "filter" or
|
||||
name = "find" or
|
||||
name = "findIndex" or
|
||||
name = "findKey" or
|
||||
name = "findLast" or
|
||||
name = "findLastIndex" or
|
||||
name = "findLastKey" or
|
||||
name = "forEach" or
|
||||
name = "forEachRight" or
|
||||
name = "forIn" or
|
||||
name = "forInRight" or
|
||||
name = "groupBy" or
|
||||
name = "indexBy" or
|
||||
name = "map" or
|
||||
name = "mapKeys" or
|
||||
name = "mapValues" or
|
||||
name = "max" or
|
||||
name = "min" or
|
||||
name = "omit" or
|
||||
name = "partition" or
|
||||
name = "pick" or
|
||||
name = "reject" or
|
||||
name = "remove" or
|
||||
name = "select" or
|
||||
name = "some" or
|
||||
name = "sortBy" or
|
||||
name = "sum" or
|
||||
name = "takeRightWhile" or
|
||||
name = "takeWhile" or
|
||||
name = "tap" or
|
||||
name = "thru" or
|
||||
name = "times" or
|
||||
name = "unzipWith" or
|
||||
name = "zipWith"
|
||||
) and
|
||||
name =
|
||||
[
|
||||
"all", "any", "collect", "countBy", "detect", "dropRightWhile", "dropWhile", "each",
|
||||
"eachRight", "every", "filter", "find", "findIndex", "findKey", "findLast",
|
||||
"findLastIndex", "findLastKey", "forEach", "forEachRight", "forIn", "forInRight",
|
||||
"groupBy", "indexBy", "map", "mapKeys", "mapValues", "max", "min", "omit", "partition",
|
||||
"pick", "reject", "remove", "select", "some", "sortBy", "sum", "takeRightWhile",
|
||||
"takeWhile", "tap", "thru", "times", "unzipWith", "zipWith"
|
||||
] and
|
||||
callbackIndex = 1 and
|
||||
contextIndex = 2 and
|
||||
argumentCount = 3
|
||||
or
|
||||
(
|
||||
name = "foldl" or
|
||||
name = "foldr" or
|
||||
name = "inject" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "transform"
|
||||
) and
|
||||
name = ["foldl", "foldr", "inject", "reduce", "reduceRight", "transform"] and
|
||||
callbackIndex = 1 and
|
||||
contextIndex = 3 and
|
||||
argumentCount = 4
|
||||
or
|
||||
(
|
||||
name = "sortedlastIndex"
|
||||
or
|
||||
name = "assign"
|
||||
or
|
||||
name = "eq"
|
||||
or
|
||||
name = "extend"
|
||||
or
|
||||
name = "merge"
|
||||
or
|
||||
name = "sortedIndex" and
|
||||
name = "uniq"
|
||||
) and
|
||||
name = ["sortedlastIndex", "assign", "eq", "extend", "merge", "sortedIndex", "uniq"] and
|
||||
callbackIndex = 2 and
|
||||
contextIndex = 3 and
|
||||
argumentCount = 4
|
||||
|
||||
@@ -18,19 +18,11 @@ abstract class LoggerCall extends DataFlow::CallNode {
|
||||
* Gets a log level name that is used in RFC5424, `npm`, `console`.
|
||||
*/
|
||||
string getAStandardLoggerMethodName() {
|
||||
result = "crit" or
|
||||
result = "dir" or
|
||||
result = "debug" or
|
||||
result = "error" or
|
||||
result = "emerg" or
|
||||
result = "fatal" or
|
||||
result = "info" or
|
||||
result = "log" or
|
||||
result = "notice" or
|
||||
result = "silly" or
|
||||
result = "trace" or
|
||||
result = "verbose" or
|
||||
result = "warn"
|
||||
result =
|
||||
[
|
||||
"crit", "dir", "trace", "verbose", "warn", "debug", "error", "emerg", "fatal", "info", "log",
|
||||
"notice", "silly"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ module MooTools {
|
||||
predicate interpretsNodeAsHtml(DataFlow::Node node) {
|
||||
exists(Element e |
|
||||
node = e.getAnElementPropertyValue("html") or
|
||||
node = e.getAMethodCall(["appendHtml"]).getArgument(0)
|
||||
node = e.getAMethodCall("appendHtml").getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,28 +252,13 @@ private module Mongoose {
|
||||
* Holds if Model method `name` returns a Query.
|
||||
*/
|
||||
predicate returnsQuery(string name) {
|
||||
name = "$where" or
|
||||
name = "count" or
|
||||
name = "countDocuments" or
|
||||
name = "deleteMany" or
|
||||
name = "deleteOne" or
|
||||
name = "find" or
|
||||
name = "findById" or
|
||||
name = "findByIdAndDelete" or
|
||||
name = "findByIdAndRemove" or
|
||||
name = "findByIdAndUpdate" or
|
||||
name = "findOne" or
|
||||
name = "findOneAndDelete" or
|
||||
name = "findOneAndRemove" or
|
||||
name = "findOneAndReplace" or
|
||||
name = "findOneAndUpdate" or
|
||||
name = "geosearch" or
|
||||
name = "remove" or
|
||||
name = "replaceOne" or
|
||||
name = "update" or
|
||||
name = "updateMany" or
|
||||
name = "updateOne" or
|
||||
name = "where"
|
||||
name =
|
||||
[
|
||||
"$where", "count", "findOne", "findOneAndDelete", "findOneAndRemove",
|
||||
"findOneAndReplace", "findOneAndUpdate", "geosearch", "remove", "replaceOne", "update",
|
||||
"updateMany", "countDocuments", "updateOne", "where", "deleteMany", "deleteOne", "find",
|
||||
"findById", "findByIdAndDelete", "findByIdAndRemove", "findByIdAndUpdate"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,117 +332,34 @@ private module Mongoose {
|
||||
*/
|
||||
predicate interpretsArgumentAsQuery(string name, int n) {
|
||||
n = 0 and
|
||||
(
|
||||
name = "and" or
|
||||
name = "count" or
|
||||
name = "countDocuments" or
|
||||
name = "deleteMany" or
|
||||
name = "deleteOne" or
|
||||
name = "elemMatch" or
|
||||
name = "find" or
|
||||
name = "findOne" or
|
||||
name = "findOneAndDelete" or
|
||||
name = "findOneAndRemove" or
|
||||
name = "findOneAndReplace" or
|
||||
name = "findOneAndUpdate" or
|
||||
name = "merge" or
|
||||
name = "nor" or
|
||||
name = "or" or
|
||||
name = "remove" or
|
||||
name = "replaceOne" or
|
||||
name = "setQuery" or
|
||||
name = "setUpdate" or
|
||||
name = "update" or
|
||||
name = "updateMany" or
|
||||
name = "updateOne" or
|
||||
name = "where"
|
||||
)
|
||||
name =
|
||||
[
|
||||
"and", "count", "findOneAndReplace", "findOneAndUpdate", "merge", "nor", "or", "remove",
|
||||
"replaceOne", "setQuery", "setUpdate", "update", "countDocuments", "updateMany",
|
||||
"updateOne", "where", "deleteMany", "deleteOne", "elemMatch", "find", "findOne",
|
||||
"findOneAndDelete", "findOneAndRemove"
|
||||
]
|
||||
or
|
||||
n = 1 and
|
||||
(
|
||||
name = "distinct" or
|
||||
name = "findOneAndUpdate" or
|
||||
name = "update" or
|
||||
name = "updateMany" or
|
||||
name = "updateOne"
|
||||
)
|
||||
name = ["distinct", "findOneAndUpdate", "update", "updateMany", "updateOne"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if Query method `name` returns a Query.
|
||||
*/
|
||||
predicate returnsQuery(string name) {
|
||||
name = "$where" or
|
||||
name = "J" or
|
||||
name = "all" or
|
||||
name = "and" or
|
||||
name = "batchsize" or
|
||||
name = "box" or
|
||||
name = "center" or
|
||||
name = "centerSphere" or
|
||||
name = "circle" or
|
||||
name = "collation" or
|
||||
name = "comment" or
|
||||
name = "count" or
|
||||
name = "countDocuments" or
|
||||
name = "distinct" or
|
||||
name = "elemMatch" or
|
||||
name = "equals" or
|
||||
name = "error" or
|
||||
name = "estimatedDocumentCount" or
|
||||
name = "exists" or
|
||||
name = "explain" or
|
||||
name = "find" or
|
||||
name = "findById" or
|
||||
name = "findOne" or
|
||||
name = "findOneAndRemove" or
|
||||
name = "findOneAndUpdate" or
|
||||
name = "geometry" or
|
||||
name = "get" or
|
||||
name = "gt" or
|
||||
name = "gte" or
|
||||
name = "hint" or
|
||||
name = "in" or
|
||||
name = "intersects" or
|
||||
name = "lean" or
|
||||
name = "limit" or
|
||||
name = "lt" or
|
||||
name = "lte" or
|
||||
name = "map" or
|
||||
name = "map" or
|
||||
name = "maxDistance" or
|
||||
name = "maxTimeMS" or
|
||||
name = "maxscan" or
|
||||
name = "mod" or
|
||||
name = "ne" or
|
||||
name = "near" or
|
||||
name = "nearSphere" or
|
||||
name = "nin" or
|
||||
name = "or" or
|
||||
name = "orFail" or
|
||||
name = "polygon" or
|
||||
name = "populate" or
|
||||
name = "read" or
|
||||
name = "readConcern" or
|
||||
name = "regexp" or
|
||||
name = "remove" or
|
||||
name = "select" or
|
||||
name = "session" or
|
||||
name = "set" or
|
||||
name = "setOptions" or
|
||||
name = "setQuery" or
|
||||
name = "setUpdate" or
|
||||
name = "size" or
|
||||
name = "skip" or
|
||||
name = "slaveOk" or
|
||||
name = "slice" or
|
||||
name = "snapshot" or
|
||||
name = "sort" or
|
||||
name = "update" or
|
||||
name = "w" or
|
||||
name = "where" or
|
||||
name = "within" or
|
||||
name = "wtimeout"
|
||||
name =
|
||||
[
|
||||
"$where", "J", "comment", "count", "countDocuments", "distinct", "elemMatch", "equals",
|
||||
"error", "estimatedDocumentCount", "exists", "explain", "all", "find", "findById",
|
||||
"findOne", "findOneAndRemove", "findOneAndUpdate", "geometry", "get", "gt", "gte",
|
||||
"hint", "and", "in", "intersects", "lean", "limit", "lt", "lte", "map", "map",
|
||||
"maxDistance", "maxTimeMS", "batchsize", "maxscan", "mod", "ne", "near", "nearSphere",
|
||||
"nin", "or", "orFail", "polygon", "populate", "box", "read", "readConcern", "regexp",
|
||||
"remove", "select", "session", "set", "setOptions", "setQuery", "setUpdate", "center",
|
||||
"size", "skip", "slaveOk", "slice", "snapshot", "sort", "update", "w", "where",
|
||||
"within", "centerSphere", "wtimeout", "circle", "collation"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -523,14 +523,11 @@ module NodeJSLib {
|
||||
/** A write to the file system. */
|
||||
private class NodeJSFileSystemAccessWrite extends FileSystemWriteAccess, NodeJSFileSystemAccess {
|
||||
NodeJSFileSystemAccessWrite() {
|
||||
methodName = "appendFile" or
|
||||
methodName = "appendFileSync" or
|
||||
methodName = "write" or
|
||||
methodName = "writeFile" or
|
||||
methodName = "writeFileSync" or
|
||||
methodName = "writeSync" or
|
||||
methodName = "link" or
|
||||
methodName = "linkSync"
|
||||
methodName =
|
||||
[
|
||||
"appendFile", "appendFileSync", "write", "writeFile", "writeFileSync", "writeSync",
|
||||
"link", "linkSync"
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
@@ -699,13 +696,7 @@ module NodeJSLib {
|
||||
)
|
||||
or
|
||||
shell = false and
|
||||
(
|
||||
methodName = "execFile" or
|
||||
methodName = "execFileSync" or
|
||||
methodName = "spawn" or
|
||||
methodName = "spawnSync" or
|
||||
methodName = "fork"
|
||||
)
|
||||
methodName = ["execFile", "execFileSync", "spawn", "spawnSync", "fork"]
|
||||
) and
|
||||
// all of the above methods take the command as their first argument
|
||||
result = getParameter(0).getARhs()
|
||||
@@ -716,18 +707,12 @@ module NodeJSLib {
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument(true) }
|
||||
|
||||
override DataFlow::Node getArgumentList() {
|
||||
(
|
||||
methodName = "execFile" or
|
||||
methodName = "execFileSync" or
|
||||
methodName = "fork" or
|
||||
methodName = "spawn" or
|
||||
methodName = "spawnSync"
|
||||
) and
|
||||
methodName = ["execFile", "execFileSync", "fork", "spawn", "spawnSync"] and
|
||||
// all of the above methods take the argument list as their second argument
|
||||
result = getParameter(1).getARhs()
|
||||
}
|
||||
|
||||
override predicate isSync() { "Sync" = methodName.suffix(methodName.length() - 4) }
|
||||
override predicate isSync() { methodName.matches("%Sync") }
|
||||
|
||||
override DataFlow::Node getOptionsArg() {
|
||||
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
|
||||
|
||||
@@ -11,17 +11,11 @@ module PkgCloud {
|
||||
private predicate takesConfigurationObject(DataFlow::InvokeNode invk, int i) {
|
||||
exists(DataFlow::ModuleImportNode mod, DataFlow::SourceNode receiver, string type |
|
||||
mod.getPath() = "pkgcloud" and
|
||||
(
|
||||
type = "compute" or
|
||||
type = "storage" or
|
||||
type = "database" or
|
||||
type = "dns" or
|
||||
type = "blockstorage" or
|
||||
type = "loadbalancer" or
|
||||
type = "network" or
|
||||
type = "orchestration" or
|
||||
type = "cdn"
|
||||
) and
|
||||
type =
|
||||
[
|
||||
"compute", "storage", "database", "dns", "blockstorage", "loadbalancer", "network",
|
||||
"orchestration", "cdn"
|
||||
] and
|
||||
(
|
||||
// require('pkgcloud').compute
|
||||
receiver = mod.getAPropertyRead(type)
|
||||
|
||||
@@ -80,17 +80,7 @@ module ShellJS {
|
||||
*/
|
||||
private class ShellJSGenericFileAccess extends FileSystemAccess, ShellJSCall {
|
||||
ShellJSGenericFileAccess() {
|
||||
name = "cd" or
|
||||
name = "cp" or
|
||||
name = "chmod" or
|
||||
name = "pushd" or
|
||||
name = "find" or
|
||||
name = "ls" or
|
||||
name = "ln" or
|
||||
name = "mkdir" or
|
||||
name = "mv" or
|
||||
name = "rm" or
|
||||
name = "touch"
|
||||
name = ["cd", "cp", "touch", "chmod", "pushd", "find", "ls", "ln", "mkdir", "mv", "rm"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
|
||||
@@ -110,13 +100,7 @@ module ShellJS {
|
||||
* A file system access that returns the contents of a file.
|
||||
*/
|
||||
private class ShellJSRead extends FileSystemReadAccess, ShellJSCall {
|
||||
ShellJSRead() {
|
||||
name = "cat" or
|
||||
name = "head" or
|
||||
name = "sort" or
|
||||
name = "tail" or
|
||||
name = "uniq"
|
||||
}
|
||||
ShellJSRead() { name = ["cat", "head", "sort", "tail", "uniq"] }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = getAnArgument() }
|
||||
|
||||
|
||||
@@ -34,14 +34,7 @@ private class LibraryFormatter extends PrintfStyleCall {
|
||||
returns = false and
|
||||
mod = "console" and
|
||||
(
|
||||
(
|
||||
meth = "debug" or
|
||||
meth = "error" or
|
||||
meth = "info" or
|
||||
meth = "log" or
|
||||
meth = "trace" or
|
||||
meth = "warn"
|
||||
) and
|
||||
meth = ["debug", "error", "info", "log", "trace", "warn"] and
|
||||
formatIndex = 0
|
||||
or
|
||||
meth = "assert" and formatIndex = 1
|
||||
|
||||
@@ -107,9 +107,7 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
|
||||
*/
|
||||
bindingset[name]
|
||||
private boolean getSync(string name) {
|
||||
if name.suffix(name.length() - 4) = "Sync" or name.suffix(name.length() - 4) = "sync"
|
||||
then result = true
|
||||
else result = false
|
||||
if name.matches("%Sync") or name.matches("%sync") then result = true else result = false
|
||||
}
|
||||
|
||||
private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::InvokeNode {
|
||||
|
||||
@@ -9,26 +9,11 @@ module Templating {
|
||||
* Gets a string that is a known template delimiter.
|
||||
*/
|
||||
string getADelimiter() {
|
||||
result = "<%" or
|
||||
result = "%>" or
|
||||
result = "{{" or
|
||||
result = "}}" or
|
||||
result = "{%" or
|
||||
result = "%}" or
|
||||
result = "<@" or
|
||||
result = "@>" or
|
||||
result = "<#" or
|
||||
result = "#>" or
|
||||
result = "{#" or
|
||||
result = "#}" or
|
||||
result = "{$" or
|
||||
result = "$}" or
|
||||
result = "[%" or
|
||||
result = "%]" or
|
||||
result = "[[" or
|
||||
result = "]]" or
|
||||
result = "<?" or
|
||||
result = "?>"
|
||||
result =
|
||||
[
|
||||
"<%", "%>", "{#", "#}", "{$", "$}", "[%", "%]", "[[", "]]", "<?", "?>", "{{", "}}", "{%",
|
||||
"%}", "<@", "@>", "<#", "#>"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,7 +151,7 @@ module Vue {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
@@ -640,7 +640,7 @@ module Vue {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -441,19 +441,11 @@ module JQuery {
|
||||
* arguments as HTML.
|
||||
*/
|
||||
predicate isMethodArgumentInterpretedAsHtml(string name) {
|
||||
name = "after" or
|
||||
name = "append" or
|
||||
name = "appendTo" or
|
||||
name = "before" or
|
||||
name = "html" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prepend" or
|
||||
name = "prependTo" or
|
||||
name = "replaceWith" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
name =
|
||||
[
|
||||
"after", "append", "wrap", "wrapAll", "wrapInner", "appendTo", "before", "html",
|
||||
"insertAfter", "insertBefore", "prepend", "prependTo", "replaceWith"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -461,13 +453,7 @@ module JQuery {
|
||||
* arguments as a selector.
|
||||
*/
|
||||
predicate isMethodArgumentInterpretedAsSelector(string name) {
|
||||
name = "appendTo" or
|
||||
name = "insertAfter" or
|
||||
name = "insertBefore" or
|
||||
name = "prependTo" or
|
||||
name = "wrap" or
|
||||
name = "wrapAll" or
|
||||
name = "wrapInner"
|
||||
name = ["appendTo", "insertAfter", "insertBefore", "prependTo", "wrap", "wrapAll", "wrapInner"]
|
||||
}
|
||||
|
||||
module DollarSource {
|
||||
|
||||
@@ -131,7 +131,7 @@ class XUnitAnnotation extends Expr {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -153,90 +153,36 @@ class JSLintOptions extends JSLintDirective {
|
||||
private string jsLintImplicitGlobal(string category) {
|
||||
// cf. http://www.jslint.com/help.html#global
|
||||
category = "browser" and
|
||||
(
|
||||
result = "clearInterval" or
|
||||
result = "clearTimeout" or
|
||||
result = "document" or
|
||||
result = "event" or
|
||||
result = "frames" or
|
||||
result = "history" or
|
||||
result = "Image" or
|
||||
result = "location" or
|
||||
result = "name" or
|
||||
result = "navigator" or
|
||||
result = "Option" or
|
||||
result = "parent" or
|
||||
result = "screen" or
|
||||
result = "setInterval" or
|
||||
result = "setTimeout" or
|
||||
result = "window" or
|
||||
result = "XMLHttpRequest"
|
||||
)
|
||||
result =
|
||||
[
|
||||
"clearInterval", "clearTimeout", "Option", "parent", "screen", "setInterval", "setTimeout",
|
||||
"window", "XMLHttpRequest", "document", "event", "frames", "history", "Image", "location",
|
||||
"name", "navigator"
|
||||
]
|
||||
or
|
||||
category = "devel" and
|
||||
(
|
||||
result = "alert" or
|
||||
result = "confirm" or
|
||||
result = "console" or
|
||||
result = "Debug" or
|
||||
result = "opera" or
|
||||
result = "prompt" or
|
||||
result = "WSH"
|
||||
)
|
||||
result = ["alert", "confirm", "console", "Debug", "opera", "prompt", "WSH"]
|
||||
or
|
||||
category = "node" and
|
||||
(
|
||||
result = "Buffer" or
|
||||
result = "clearInterval" or
|
||||
result = "clearTimeout" or
|
||||
result = "console" or
|
||||
result = "exports" or
|
||||
result = "result" or
|
||||
result = "module" or
|
||||
result = "process" or
|
||||
result = "querystring" or
|
||||
result = "require" or
|
||||
result = "setInterval" or
|
||||
result = "setTimeout" or
|
||||
result = "__filename" or
|
||||
result = "__dirname"
|
||||
)
|
||||
result =
|
||||
[
|
||||
"Buffer", "clearInterval", "setInterval", "setTimeout", "__filename", "__dirname",
|
||||
"clearTimeout", "console", "exports", "result", "module", "process", "querystring", "require"
|
||||
]
|
||||
or
|
||||
category = "couch" and
|
||||
(
|
||||
result = "emit" or
|
||||
result = "getRow" or
|
||||
result = "isArray" or
|
||||
result = "log" or
|
||||
result = "provides" or
|
||||
result = "registerType" or
|
||||
result = "require" or
|
||||
result = "send" or
|
||||
result = "start" or
|
||||
result = "sum" or
|
||||
result = "toJSON"
|
||||
)
|
||||
result =
|
||||
[
|
||||
"emit", "getRow", "toJSON", "isArray", "log", "provides", "registerType", "require", "send",
|
||||
"start", "sum"
|
||||
]
|
||||
or
|
||||
category = "rhino" and
|
||||
(
|
||||
result = "defineClass" or
|
||||
result = "deserialize" or
|
||||
result = "gc" or
|
||||
result = "help" or
|
||||
result = "load" or
|
||||
result = "loadClass" or
|
||||
result = "print" or
|
||||
result = "quit" or
|
||||
result = "readFile" or
|
||||
result = "readUrl" or
|
||||
result = "runCommand" or
|
||||
result = "seal" or
|
||||
result = "serialize" or
|
||||
result = "spawn" or
|
||||
result = "sync" or
|
||||
result = "toint32" or
|
||||
result = "version"
|
||||
)
|
||||
result =
|
||||
[
|
||||
"defineClass", "deserialize", "runCommand", "seal", "serialize", "spawn", "sync", "toint32",
|
||||
"version", "gc", "help", "load", "loadClass", "print", "quit", "readFile", "readUrl"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,68 +15,35 @@
|
||||
*/
|
||||
private module AlgorithmNames {
|
||||
predicate isStrongHashingAlgorithm(string name) {
|
||||
name = "DSA" or
|
||||
name = "ED25519" or
|
||||
name = "ES256" or
|
||||
name = "ECDSA256" or
|
||||
name = "ES384" or
|
||||
name = "ECDSA384" or
|
||||
name = "ES512" or
|
||||
name = "ECDSA512" or
|
||||
name = "SHA2" or
|
||||
name = "SHA224" or
|
||||
name = "SHA256" or
|
||||
name = "SHA384" or
|
||||
name = "SHA512" or
|
||||
name = "SHA3"
|
||||
name =
|
||||
[
|
||||
"DSA", "ED25519", "ES256", "ECDSA256", "ES384", "ECDSA384", "ES512", "ECDSA512", "SHA2",
|
||||
"SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "SHA3224", "SHA3256", "SHA3384", "SHA3512"
|
||||
]
|
||||
}
|
||||
|
||||
predicate isWeakHashingAlgorithm(string name) {
|
||||
name = "HAVEL128" or
|
||||
name = "MD2" or
|
||||
name = "MD4" or
|
||||
name = "MD5" or
|
||||
name = "PANAMA" or
|
||||
name = "RIPEMD" or
|
||||
name = "RIPEMD128" or
|
||||
name = "RIPEMD256" or
|
||||
name = "RIPEMD160" or
|
||||
name = "RIPEMD320" or
|
||||
name = "SHA0" or
|
||||
name = "SHA1"
|
||||
name =
|
||||
[
|
||||
"HAVEL128", "MD2", "MD4", "MD5", "PANAMA", "RIPEMD", "RIPEMD128", "RIPEMD256", "RIPEMD160",
|
||||
"RIPEMD320", "SHA0", "SHA1"
|
||||
]
|
||||
}
|
||||
|
||||
predicate isStrongEncryptionAlgorithm(string name) {
|
||||
name = "AES" or
|
||||
name = "AES128" or
|
||||
name = "AES192" or
|
||||
name = "AES256" or
|
||||
name = "AES512" or
|
||||
name = "RSA" or
|
||||
name = "RABBIT" or
|
||||
name = "BLOWFISH"
|
||||
name = ["AES", "AES128", "AES192", "AES256", "AES512", "RSA", "RABBIT", "BLOWFISH"]
|
||||
}
|
||||
|
||||
predicate isWeakEncryptionAlgorithm(string name) {
|
||||
name = "DES" or
|
||||
name = "3DES" or
|
||||
name = "TRIPLEDES" or
|
||||
name = "TDEA" or
|
||||
name = "TRIPLEDEA" or
|
||||
name = "ARC2" or
|
||||
name = "RC2" or
|
||||
name = "ARC4" or
|
||||
name = "RC4" or
|
||||
name = "ARCFOUR" or
|
||||
name = "ARC5" or
|
||||
name = "RC5"
|
||||
name =
|
||||
[
|
||||
"DES", "3DES", "TRIPLEDES", "TDEA", "TRIPLEDEA", "ARC2", "RC2", "ARC4", "RC4", "ARCFOUR",
|
||||
"ARC5", "RC5"
|
||||
]
|
||||
}
|
||||
|
||||
predicate isStrongPasswordHashingAlgorithm(string name) {
|
||||
name = "ARGON2" or
|
||||
name = "PBKDF2" or
|
||||
name = "BCRYPT" or
|
||||
name = "SCRYPT"
|
||||
name = ["ARGON2", "PBKDF2", "BCRYPT", "SCRYPT"]
|
||||
}
|
||||
|
||||
predicate isWeakPasswordHashingAlgorithm(string name) { none() }
|
||||
|
||||
@@ -29,20 +29,11 @@ module TaintedUrlSuffix {
|
||||
/** Holds for `pred -> succ` is a step of form `x -> x.p` */
|
||||
private predicate isSafeLocationProp(DataFlow::PropRead read) {
|
||||
// Ignore properties that refer to the scheme, domain, port, auth, or path.
|
||||
exists(string name | name = read.getPropertyName() |
|
||||
name = "protocol" or
|
||||
name = "scheme" or
|
||||
name = "host" or
|
||||
name = "hostname" or
|
||||
name = "domain" or
|
||||
name = "origin" or
|
||||
name = "port" or
|
||||
name = "path" or
|
||||
name = "pathname" or
|
||||
name = "username" or
|
||||
name = "password" or
|
||||
name = "auth"
|
||||
)
|
||||
read.getPropertyName() =
|
||||
[
|
||||
"protocol", "scheme", "host", "hostname", "domain", "origin", "port", "path", "pathname",
|
||||
"username", "password", "auth"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -303,14 +303,11 @@ module PrettyPrintCatCall {
|
||||
bindingset[str]
|
||||
private string createSimplifiedStringConcat(string str) {
|
||||
// Remove an initial ""+ (e.g. in `""+file`)
|
||||
if str.prefix(5) = "\"\" + "
|
||||
if str.matches("\"\" + %")
|
||||
then result = str.suffix(5)
|
||||
else
|
||||
// prettify `${newpath}` to just newpath
|
||||
if
|
||||
str.prefix(3) = "`${" and
|
||||
str.suffix(str.length() - 2) = "}`" and
|
||||
not str.suffix(3).matches("%{%")
|
||||
if str.matches("`${%") and str.matches("%}`") and not str.suffix(3).matches("%{%")
|
||||
then result = str.prefix(str.length() - 2).suffix(3)
|
||||
else result = str
|
||||
}
|
||||
|
||||
@@ -88,12 +88,7 @@ module ClientSideUrlRedirect {
|
||||
class LocationSink extends Sink, DataFlow::ValueNode {
|
||||
LocationSink() {
|
||||
// A call to a `window.navigate` or `window.open`
|
||||
exists(string name |
|
||||
name = "navigate" or
|
||||
name = "open" or
|
||||
name = "openDialog" or
|
||||
name = "showModalDialog"
|
||||
|
|
||||
exists(string name | name = ["navigate", "open", "openDialog", "showModalDialog"] |
|
||||
this = DataFlow::globalVarRef(name).getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
@@ -102,7 +97,7 @@ module ClientSideUrlRedirect {
|
||||
locationCall = DOM::locationRef().getAMethodCall(name) and
|
||||
this = locationCall.getArgument(0)
|
||||
|
|
||||
name = "replace" or name = "assign"
|
||||
name = ["replace", "assign"]
|
||||
)
|
||||
or
|
||||
// An assignment to `location`
|
||||
@@ -113,7 +108,7 @@ module ClientSideUrlRedirect {
|
||||
pw = DOM::locationRef().getAPropertyWrite(propName) and
|
||||
this = pw.getRhs()
|
||||
|
|
||||
propName = "href" or propName = "protocol" or propName = "hostname"
|
||||
propName = ["href", "protocol", "hostname"]
|
||||
)
|
||||
or
|
||||
// A redirection using the AngularJS `$location` service
|
||||
@@ -153,9 +148,8 @@ module ClientSideUrlRedirect {
|
||||
*/
|
||||
class SrcAttributeUrlSink extends ScriptUrlSink, DataFlow::ValueNode {
|
||||
SrcAttributeUrlSink() {
|
||||
exists(DOM::AttributeDefinition attr, string eltName |
|
||||
attr.getElement().getName() = eltName and
|
||||
(eltName = "script" or eltName = "iframe") and
|
||||
exists(DOM::AttributeDefinition attr |
|
||||
attr.getElement().getName() = ["script", "iframe"] and
|
||||
attr.getName() = "src" and
|
||||
this = attr.getValueNode()
|
||||
)
|
||||
|
||||
@@ -88,14 +88,7 @@ class DomMethodCallExpr extends MethodCallExpr {
|
||||
name = "setAttributeNS" and argPos = 2
|
||||
) and
|
||||
// restrict to potentially dangerous attributes
|
||||
exists(string attr |
|
||||
attr = "action" or
|
||||
attr = "formaction" or
|
||||
attr = "href" or
|
||||
attr = "src" or
|
||||
attr = "xlink:href" or
|
||||
attr = "data"
|
||||
|
|
||||
exists(string attr | attr = ["action", "formaction", "href", "src", "xlink:href", "data"] |
|
||||
getArgument(argPos - 1).getStringValue().toLowerCase() = attr
|
||||
)
|
||||
)
|
||||
|
||||
@@ -115,66 +115,18 @@ module LoopBoundInjection {
|
||||
* Holds if `name` is a method from lodash vulnerable to a DoS attack if called with a tainted object.
|
||||
*/
|
||||
predicate loopableLodashMethod(string name) {
|
||||
name = "chunk" or
|
||||
name = "compact" or
|
||||
name = "difference" or
|
||||
name = "differenceBy" or
|
||||
name = "differenceWith" or
|
||||
name = "drop" or
|
||||
name = "dropRight" or
|
||||
name = "dropRightWhile" or
|
||||
name = "dropWhile" or
|
||||
name = "fill" or
|
||||
name = "findIndex" or
|
||||
name = "findLastIndex" or
|
||||
name = "flatten" or
|
||||
name = "flattenDeep" or
|
||||
name = "flattenDepth" or
|
||||
name = "initial" or
|
||||
name = "intersection" or
|
||||
name = "intersectionBy" or
|
||||
name = "intersectionWith" or
|
||||
name = "join" or
|
||||
name = "remove" or
|
||||
name = "reverse" or
|
||||
name = "slice" or
|
||||
name = "sortedUniq" or
|
||||
name = "sortedUniqBy" or
|
||||
name = "tail" or
|
||||
name = "union" or
|
||||
name = "unionBy" or
|
||||
name = "unionWith" or
|
||||
name = "uniqBy" or
|
||||
name = "unzip" or
|
||||
name = "unzipWith" or
|
||||
name = "without" or
|
||||
name = "zip" or
|
||||
name = "zipObject" or
|
||||
name = "zipObjectDeep" or
|
||||
name = "zipWith" or
|
||||
name = "countBy" or
|
||||
name = "each" or
|
||||
name = "forEach" or
|
||||
name = "eachRight" or
|
||||
name = "forEachRight" or
|
||||
name = "filter" or
|
||||
name = "find" or
|
||||
name = "findLast" or
|
||||
name = "flatMap" or
|
||||
name = "flatMapDeep" or
|
||||
name = "flatMapDepth" or
|
||||
name = "forEach" or
|
||||
name = "forEachRight" or
|
||||
name = "groupBy" or
|
||||
name = "invokeMap" or
|
||||
name = "keyBy" or
|
||||
name = "map" or
|
||||
name = "orderBy" or
|
||||
name = "partition" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "reject" or
|
||||
name = "sortBy"
|
||||
name =
|
||||
[
|
||||
"chunk", "compact", "difference", "differenceBy", "differenceWith", "drop", "dropRight",
|
||||
"dropRightWhile", "dropWhile", "fill", "findIndex", "findLastIndex", "flatten",
|
||||
"flattenDeep", "flattenDepth", "initial", "intersection", "intersectionBy",
|
||||
"intersectionWith", "join", "remove", "reverse", "slice", "sortedUniq", "sortedUniqBy",
|
||||
"tail", "union", "unionBy", "unionWith", "uniqBy", "unzip", "unzipWith", "without", "zip",
|
||||
"zipObject", "zipObjectDeep", "zipWith", "countBy", "each", "forEach", "eachRight",
|
||||
"forEachRight", "filter", "find", "findLast", "flatMap", "flatMapDeep", "flatMapDepth",
|
||||
"forEach", "forEachRight", "groupBy", "invokeMap", "keyBy", "map", "orderBy", "partition",
|
||||
"reduce", "reduceRight", "reject", "sortBy"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -751,13 +751,11 @@ module TaintedPath {
|
||||
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"
|
||||
argumentlessMethodName =
|
||||
[
|
||||
"toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim",
|
||||
"trimLeft", "trimRight"
|
||||
]
|
||||
|
|
||||
name = argumentlessMethodName
|
||||
)
|
||||
|
||||
@@ -39,11 +39,7 @@ module TypeConfusionThroughParameterTampering {
|
||||
private class StringArrayAmbiguousMethodCall extends Sink {
|
||||
StringArrayAmbiguousMethodCall() {
|
||||
exists(string name, DataFlow::MethodCallNode mc |
|
||||
name = "concat" or
|
||||
name = "includes" or
|
||||
name = "indexOf" or
|
||||
name = "lastIndexOf" or
|
||||
name = "slice"
|
||||
name = ["concat", "includes", "indexOf", "lastIndexOf", "slice"]
|
||||
|
|
||||
mc.calls(this, name) and
|
||||
// ignore patterns that are innocent in practice
|
||||
|
||||
@@ -67,14 +67,7 @@ module PolynomialReDoS {
|
||||
|
|
||||
this = mcn.getArgument(0) and
|
||||
regexp = mcn.getReceiver() and
|
||||
(
|
||||
name = "match" or
|
||||
name = "split" or
|
||||
name = "matchAll" or
|
||||
name = "replace" or
|
||||
name = "replaceAll" or
|
||||
name = "search"
|
||||
)
|
||||
name = ["match", "split", "matchAll", "replace", "replaceAll", "search"]
|
||||
or
|
||||
this = mcn.getReceiver() and
|
||||
regexp = mcn.getArgument(0) and
|
||||
|
||||
@@ -139,8 +139,6 @@ class RegExpRoot extends RegExpTerm {
|
||||
predicate isRelevant() {
|
||||
// there is at least one repetition
|
||||
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
|
||||
// there are no lookbehinds
|
||||
not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and
|
||||
// is actually used as a RegExp
|
||||
isUsedAsRegExp() and
|
||||
// not excluded for library specific reasons
|
||||
@@ -479,7 +477,7 @@ private module CharacterClasses {
|
||||
result = ["0", "9"]
|
||||
or
|
||||
cc.getValue() = "s" and
|
||||
result = [" "]
|
||||
result = " "
|
||||
or
|
||||
cc.getValue() = "w" and
|
||||
result = ["a", "Z", "_", "0", "9"]
|
||||
@@ -492,7 +490,7 @@ private module CharacterClasses {
|
||||
result = "9"
|
||||
or
|
||||
cc.getValue() = "s" and
|
||||
result = [" "]
|
||||
result = " "
|
||||
or
|
||||
cc.getValue() = "w" and
|
||||
result = "a"
|
||||
|
||||
1207
javascript/ql/lib/tutorial.qll
Normal file
1207
javascript/ql/lib/tutorial.qll
Normal file
File diff suppressed because it is too large
Load Diff
@@ -63,7 +63,7 @@ class SuppressionScope extends @locatable {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -71,13 +71,7 @@ predicate isCompatibleRequestedService(InjectableFunctionServiceRequest request,
|
||||
isRunMethod(request) or
|
||||
isControllerFunction(request)
|
||||
) and
|
||||
(
|
||||
kind = "value" or
|
||||
kind = "service" or
|
||||
kind = "factory" or
|
||||
kind = "constant" or
|
||||
kind = "provider-value"
|
||||
)
|
||||
kind = ["value", "service", "factory", "constant", "provider-value"]
|
||||
or
|
||||
isControllerFunction(request) and
|
||||
kind = "controller-only"
|
||||
|
||||
@@ -127,7 +127,7 @@ class CommentedOutCode extends Comment {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.LocalObjects
|
||||
import UnusedVariable
|
||||
import Declarations.UnusedVariable
|
||||
import UnusedParameter
|
||||
import Expressions.ExprHasNoEffect
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import UnusedVariable
|
||||
import Declarations.UnusedVariable
|
||||
|
||||
/**
|
||||
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import ExprHasNoEffect
|
||||
import Expressions.ExprHasNoEffect
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
from Expr e
|
||||
|
||||
@@ -22,7 +22,7 @@ class IdentifierPart extends string {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import Clones
|
||||
import DOMProperties
|
||||
import Expressions.DOMProperties
|
||||
|
||||
/**
|
||||
* Gets a description of expression `e`, which is assumed to be the left-hand
|
||||
|
||||
@@ -29,7 +29,7 @@ class OmittedArrayElement extends ArrayExpr {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -68,7 +68,7 @@ class SpuriousArguments extends Expr {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import UnusedIndexVariable
|
||||
import LanguageFeatures.UnusedIndexVariable
|
||||
|
||||
from RelationalComparison rel, Variable idx, Variable v
|
||||
where unusedIndexVariable(rel, idx, v)
|
||||
|
||||
@@ -76,13 +76,11 @@ class StateUpdateVolatileMethod extends Function {
|
||||
// - componentsWillMount
|
||||
// - componentsDidMount
|
||||
exists(ReactComponent c |
|
||||
methodName = "componentDidUnmount" or
|
||||
methodName = "componentDidUpdate" or
|
||||
methodName = "componentWillUpdate" or
|
||||
methodName = "getDefaultProps" or
|
||||
methodName = "getInitialState" or
|
||||
methodName = "render" or
|
||||
methodName = "shouldComponentUpdate"
|
||||
methodName =
|
||||
[
|
||||
"componentDidUnmount", "componentDidUpdate", "componentWillUpdate", "getDefaultProps",
|
||||
"getInitialState", "render", "shouldComponentUpdate"
|
||||
]
|
||||
|
|
||||
this = c.getInstanceMethod(methodName)
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ class RegExpPatternMistake extends TRegExpPatternMistake {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* command-line injection vulnerabilities.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.8
|
||||
* @security-severity 6.3
|
||||
* @precision medium
|
||||
* @id js/indirect-command-line-injection
|
||||
* @tags correctness
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* environment may cause subtle bugs or vulnerabilities.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.8
|
||||
* @security-severity 6.3
|
||||
* @precision high
|
||||
* @id js/shell-command-injection-from-environment
|
||||
* @tags correctness
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* user to change the meaning of the command.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.8
|
||||
* @security-severity 6.3
|
||||
* @precision high
|
||||
* @id js/shell-command-constructed-from-input
|
||||
* @tags correctness
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Using the `cat` process to read a file is unnecessarily complex, inefficient, unportable, and can lead to subtle bugs, or even security vulnerabilities.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.8
|
||||
* @security-severity 6.3
|
||||
* @precision high
|
||||
* @id js/unnecessary-use-of-cat
|
||||
* @tags correctness
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
* code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id js/code-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
* external/cwe/cwe-095
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-116
|
||||
*/
|
||||
|
||||
@@ -79,14 +79,11 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
|
||||
or
|
||||
// flow through string methods
|
||||
exists(DataFlow::MethodCallNode mc, string m |
|
||||
m = "replace" or
|
||||
m = "replaceAll" or
|
||||
m = "slice" or
|
||||
m = "substr" or
|
||||
m = "substring" or
|
||||
m = "toLowerCase" or
|
||||
m = "toUpperCase" or
|
||||
m = "trim"
|
||||
m =
|
||||
[
|
||||
"replace", "replaceAll", "slice", "substr", "substring", "toLowerCase", "toUpperCase",
|
||||
"trim"
|
||||
]
|
||||
|
|
||||
mc = nd and m = mc.getMethodName() and allBackslashesEscaped(mc.getReceiver())
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user