Merge branch 'main' of github.com:github/codeql into htmlReg

This commit is contained in:
Erik Krogh Kristensen
2021-10-26 14:46:27 +02:00
2275 changed files with 216237 additions and 23741 deletions

View File

@@ -1,4 +1,4 @@
name: codeql-javascript-examples
name: codeql/javascript-examples
version: 0.0.3
dependencies:
codeql/javascript-all: "*"

View File

@@ -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.

View File

@@ -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 }
}

View File

@@ -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") + "]"
)
}
}
}

View File

@@ -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);
}

View File

@@ -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(_)
}
}

View File

@@ -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
)
}

View File

@@ -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 = ""
)
}

View File

@@ -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)
}
}

View File

@@ -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 }
}

View File

@@ -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 }
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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())
)
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,6 @@
name: codeql/javascript-experimental-atm-lib
version: 0.0.0
extractor: javascript
library: true
dependencies:
codeql/javascript-all: "*"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
---
dependencies: {}
compiled: false
lockVersion: 1.0.0

View File

@@ -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: "*"

View File

@@ -0,0 +1 @@
<queries language="javascript"/>

View File

@@ -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")

View File

@@ -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

View File

@@ -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.

View File

@@ -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"]
)
}

View File

@@ -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

View File

@@ -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()
)
)
}

View File

@@ -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)
)
)
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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() }
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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"]
}
}

View File

@@ -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"]
}
}

View File

@@ -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"
]
)
}
}

View File

@@ -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)
}
}

View File

@@ -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))
}
/**

View File

@@ -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
)

View File

@@ -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"]
)
)
}

View File

@@ -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")

View File

@@ -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)
)

View File

@@ -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.

View File

@@ -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"
]
)
}

View File

@@ -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

View File

@@ -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"] }
}
/**

View File

@@ -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

View File

@@ -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"
]
}
/**

View File

@@ -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)
)
}
}

View File

@@ -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"
]
}
/**

View File

@@ -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

View File

@@ -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)

View File

@@ -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() }

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 =
[
"<%", "%>", "{#", "#}", "{$", "$}", "[%", "%]", "[[", "]]", "<?", "?>", "{{", "}}", "{%",
"%}", "<@", "@>", "<#", "#>"
]
}
/**

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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"
]
}
/**

View File

@@ -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() }

View File

@@ -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"
]
}
/**

View File

@@ -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
}

View File

@@ -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()
)

View File

@@ -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
)
)

View File

@@ -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"
]
}
/**

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -10,7 +10,7 @@
import javascript
import semmle.javascript.dataflow.LocalObjects
import UnusedVariable
import Declarations.UnusedVariable
import UnusedParameter
import Expressions.ExprHasNoEffect

View File

@@ -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

View File

@@ -13,7 +13,7 @@
*/
import javascript
import ExprHasNoEffect
import Expressions.ExprHasNoEffect
import semmle.javascript.RestrictedLocations
from Expr e

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -10,7 +10,7 @@
*/
import javascript
import UnusedIndexVariable
import LanguageFeatures.UnusedIndexVariable
from RelationalComparison rel, Variable idx, Variable v
where unusedIndexVariable(rel, idx, v)

View File

@@ -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)
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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