Compare commits

...

28 Commits

Author SHA1 Message Date
Max Schaefer
4ad212a5b5 Revert "JS: Recognize DomSanitizer from @angular/core"
This reverts commit ff1d0cc4c7.
2022-06-27 13:43:44 +00:00
Henry Mercer
394b147412 Remove NoSQL sinks since September 2018 2022-06-27 13:43:44 +00:00
Esben Sparre Andreasen
f555d3c723 Remove additional Xss sinks 2022-06-27 13:43:44 +00:00
Esben Sparre Andreasen
17ed1b71e8 Remove additional SQL sinks 2022-06-27 13:43:44 +00:00
Esben Sparre Andreasen
e2286b0380 Remove additional path-injection sinks 2022-06-27 13:43:43 +00:00
Esben Sparre Andreasen
5d14aecde6 Remove pseudo-properties 2022-06-27 13:43:43 +00:00
Esben Sparre Andreasen
1324491689 Remove 2020 sinks from SqlInjection.ql 2022-06-27 13:43:43 +00:00
Esben Sparre Andreasen
9c626d2729 Remove 2020 sinks from Xss.ql 2022-06-27 13:43:43 +00:00
Esben Sparre Andreasen
78cea8ac62 Remove 2020 sinks from TaintedPath.ql 2022-06-27 13:43:43 +00:00
tombolton
549082a2de separate training data extraction into individual queries 2022-06-23 17:28:02 +01:00
tombolton
d515984929 remove CodeInjection from ATM 2022-06-21 15:57:28 +01:00
tombolton
75dc3322d3 add Xss endpoint filters to XssThroughDom 2022-06-21 14:41:57 +01:00
tombolton
2771d3471b update XssThroughDom with Eriks recent changes 2022-05-25 14:44:14 +01:00
tombolton
07251ac35c replace StoredXss with CodeInjection in alert counting query 2022-05-25 14:44:14 +01:00
tombolton
c397a98922 remove additional XssThroughDom import 2022-05-25 14:44:14 +01:00
tombolton
dadfbb886a fix case in ExtractEndpointData.qll 2022-05-25 14:44:13 +01:00
tombolton
27f50d6118 update docstrings of CodeInjection and XssThroughDom queries 2022-05-25 14:44:13 +01:00
tombolton
a71f10494f explicitly include individual boosted queries in the ATM suite 2022-05-25 14:44:13 +01:00
tombolton
63626fdc67 add XssThroughDomATM.ql 2022-05-25 14:44:13 +01:00
tombolton
be6f6f5298 use new module names based on depreciation warning 2022-05-25 14:44:12 +01:00
tombolton
9ef4bf5441 fix case in CodeInjectionATM.qll 2022-05-25 14:44:12 +01:00
tombolton
a7d385cf99 add XssThroughDom and CodeInjection to mapping query 2022-05-25 14:44:12 +01:00
tombolton
adb4fc324f add XssThroughDom and CodeInjection to ExtractEndpointData.qll 2022-05-25 14:44:12 +01:00
tombolton
5f5e86c2b2 add XssThroughDom and CodeInjection to Queries.qll 2022-05-25 14:44:11 +01:00
tombolton
0c4dc1a143 add CodeInjection sink to the endpoint types 2022-05-25 14:44:11 +01:00
tombolton
de1bc89099 add CodeInjection extraction and evaluation queries 2022-05-25 14:44:11 +01:00
tombolton
f2f6379054 fix docstrings in XssThroughDom queries 2022-05-25 14:44:10 +01:00
tombolton
f2a0c38232 add XssThroughDom extraction and evaluation queries 2022-05-25 14:44:10 +01:00
29 changed files with 758 additions and 1680 deletions

View File

@@ -0,0 +1,153 @@
/**
* Provides a taint-tracking configuration for reasoning about
* cross-site scripting vulnerabilities through the DOM.
* Is boosted by ATM.
*/
import javascript
import AdaptiveThreatModeling
import StandardEndpointFilters as StandardEndpointFilters
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.heuristics.SyntacticHeuristics
private import semmle.javascript.security.dataflow.XssThroughDomCustomizations::XssThroughDom
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
private import semmle.javascript.security.dataflow.UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin as UnsafeJQuery
/**
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
*/
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
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
call.getCalleeName() = "setState"
) and
result = "setState calls ought to be safe in react applications"
or
// Require XSS sink candidates to be (a) arguments to external library calls (possibly
// indirectly), or (b) heuristic sinks.
//
// Heuristic sinks 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.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
not (
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
)
) and
result = "not a direct argument to a likely external library call or a heuristic sink"
}
}
class XssThroughDomAtmConfig extends ATMConfig {
XssThroughDomAtmConfig() { this = "XssThroughDomAtmConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof 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 through the DOM.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "XssThroughDomAtmConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) {
(sink instanceof DomBasedXss::Sink or any(XssThroughDomAtmConfig 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 TypeTestGuard or
guard instanceof UnsafeJQuery::PropertyPresenceSanitizer or
guard instanceof UnsafeJQuery::NumberGuard or
guard instanceof PrefixStringSanitizer or
guard instanceof QuoteGuard or
guard instanceof ContainsHtmlGuard
}
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
}
}
/**
* A test of form `typeof x === "something"`, preventing `x` from being a string in some cases.
*
* This sanitizer helps prune infeasible paths in type-overloaded functions.
*/
class TypeTestGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
Expr operand;
boolean polarity;
TypeTestGuard() {
exists(TypeofTag tag | TaintTracking::isTypeofGuard(astNode, operand, tag) |
// typeof x === "string" sanitizes `x` when it evaluates to false
tag = "string" and
polarity = astNode.getPolarity().booleanNot()
or
// typeof x === "object" sanitizes `x` when it evaluates to true
tag != "string" and
polarity = astNode.getPolarity()
)
}
override predicate sanitizes(boolean outcome, Expr e) {
polarity = outcome and
e = operand
}
}
private import semmle.javascript.security.dataflow.Xss::Shared as Shared
private class PrefixStringSanitizer extends TaintTracking::SanitizerGuardNode,
DomBasedXss::PrefixStringSanitizer {
PrefixStringSanitizer() { this = this }
}
private class PrefixString extends DataFlow::FlowLabel, DomBasedXss::PrefixString {
PrefixString() { this = this }
}
private class QuoteGuard extends TaintTracking::SanitizerGuardNode, Shared::QuoteGuard {
QuoteGuard() { this = this }
}
private class ContainsHtmlGuard extends TaintTracking::SanitizerGuardNode, Shared::ContainsHtmlGuard {
ContainsHtmlGuard() { this = this }
}

View File

@@ -0,0 +1,26 @@
/**
* XssThroughDom.ql
*
* Version of the standard XSS through DOM query with an output relation ready to plug into the evaluation
* pipeline.
*
* Standard query: javascript/ql/src/Security/CWE-079/XssThroughDom.ql
*/
import semmle.javascript.security.dataflow.XssThroughDomQuery
import EndToEndEvaluation as EndToEndEvaluation
from
DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink, string filePathSink,
int startLineSink, int endLineSink, int startColumnSink, int endColumnSink, string filePathSource,
int startLineSource, int endLineSource, int startColumnSource, int endColumnSource
where
cfg instanceof Configuration and
cfg.hasFlow(source, sink) and
not EndToEndEvaluation::isFlowExcluded(source, sink) and
sink.hasLocationInfo(filePathSink, startLineSink, startColumnSink, endLineSink, endColumnSink) and
source
.hasLocationInfo(filePathSource, startLineSource, startColumnSource, endLineSource,
endColumnSource)
select source, startLineSource, startColumnSource, endLineSource, endColumnSource, filePathSource,
sink, startLineSink, startColumnSink, endLineSink, endColumnSink, filePathSink

View File

@@ -0,0 +1,28 @@
/**
* XssThroughDomATM.ql
*
* Version of the boosted XSS through DOM query with an output relation ready to plug into the evaluation
* pipeline.
*/
import ATM::ResultsInfo
import EndToEndEvaluation as EndToEndEvaluation
import experimental.adaptivethreatmodeling.XssThroughDomATM
from
DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink, string filePathSink,
int startLineSink, int endLineSink, int startColumnSink, int endColumnSink, string filePathSource,
int startLineSource, int endLineSource, int startColumnSource, int endColumnSource, float score
where
cfg.hasFlow(source, sink) and
not EndToEndEvaluation::isFlowExcluded(source, sink) and
not isFlowLikelyInBaseQuery(source, sink) and
sink.hasLocationInfo(filePathSink, startLineSink, startColumnSink, endLineSink, endColumnSink) and
source
.hasLocationInfo(filePathSource, startLineSource, startColumnSource, endLineSource,
endColumnSource) and
getScoreForFlow(source, sink) = score
select source, startLineSource, startColumnSource, endLineSource, endColumnSource, filePathSource,
sink, startLineSink, startColumnSink, endLineSink, endColumnSink, filePathSink, score order by
score desc, startLineSource, startColumnSource, endLineSource, endColumnSource, filePathSource,
startLineSink, startColumnSink, endLineSink, endColumnSink, filePathSink

View File

@@ -0,0 +1,30 @@
/**
* XssThroughDomATMLite.ql
*
* Arbitrarily ranked version of the boosted XSS query with an output relation ready to plug into
* the evaluation pipeline. This is useful (a) for evaluating the performance of endpoint filters,
* and (b) as a baseline to compare the model against.
*/
import javascript
import ATM::ResultsInfo
import EndToEndEvaluation as EndToEndEvaluation
import experimental.adaptivethreatmodeling.XssThroughDomATM
from
DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink, string filePathSink,
int startLineSink, int endLineSink, int startColumnSink, int endColumnSink, string filePathSource,
int startLineSource, int endLineSource, int startColumnSource, int endColumnSource, float score
where
cfg.hasFlow(source, sink) and
not EndToEndEvaluation::isFlowExcluded(source, sink) and
not isFlowLikelyInBaseQuery(source, sink) and
sink.hasLocationInfo(filePathSink, startLineSink, startColumnSink, endLineSink, endColumnSink) and
source
.hasLocationInfo(filePathSource, startLineSource, startColumnSource, endLineSource,
endColumnSource) and
score = 0
select source, startLineSource, startColumnSource, endLineSource, endColumnSource, filePathSource,
sink, startLineSink, startColumnSink, endLineSink, endColumnSink, filePathSink, score order by
score desc, startLineSource, startColumnSource, endLineSource, endColumnSource, filePathSource,
startLineSink, startColumnSink, endLineSink, endColumnSink, filePathSink

View File

@@ -10,11 +10,11 @@
*/
import javascript
import semmle.javascript.security.dataflow.CodeInjectionQuery as CodeInjection
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
import semmle.javascript.security.dataflow.TaintedPathQuery as TaintedPath
import semmle.javascript.security.dataflow.DomBasedXssQuery as DomBasedXss
import semmle.javascript.security.dataflow.StoredXssQuery as StoredXss
import semmle.javascript.security.dataflow.XssThroughDomQuery as XssThroughDom
import evaluation.EndToEndEvaluation
@@ -29,7 +29,7 @@ select numAlerts(any(NosqlInjection::Configuration cfg)) as numNosqlAlerts,
numAlerts(any(SqlInjection::Configuration cfg)) as numSqlAlerts,
numAlerts(any(TaintedPath::Configuration cfg)) as numTaintedPathAlerts,
numAlerts(any(DomBasedXss::Configuration cfg)) as numXssAlerts,
numAlerts(any(StoredXss::Configuration cfg)) as numStoredXssAlerts,
numAlerts(any(CodeInjection::Configuration cfg)) as numCodeInjectionAlerts,
numAlerts(any(XssThroughDom::Configuration cfg)) as numXssThroughDomAlerts,
count(DataFlow::Node sink |
exists(NosqlInjection::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
@@ -44,8 +44,8 @@ select numAlerts(any(NosqlInjection::Configuration cfg)) as numNosqlAlerts,
exists(DomBasedXss::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
) as numXssSinks,
count(DataFlow::Node sink |
exists(StoredXss::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
) as numStoredXssSinks,
exists(CodeInjection::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
) as numCodeInjectionSinks,
count(DataFlow::Node sink |
exists(XssThroughDom::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
) as numXssThroughDomSinks

View File

@@ -14,43 +14,13 @@ import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
import experimental.adaptivethreatmodeling.EndpointScoring as EndpointScoring
import experimental.adaptivethreatmodeling.EndpointTypes
import experimental.adaptivethreatmodeling.FilteringReasons
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import Labels
import NoFeaturizationRestrictionsConfig
import Queries
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof NosqlInjectionQuery and
result instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
or
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::SqlInjectionAtmConfig
or
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathAtmConfig
or
query instanceof XssQuery and result instanceof XssATM::DomBasedXssAtmConfig
}
/** DEPRECATED: Alias for getAtmCfg */
deprecated ATMConfig getATMCfg(Query query) { result = getAtmCfg(query) }
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionATM::Configuration
or
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::Configuration
or
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
or
query instanceof XssQuery and result instanceof XssATM::Configuration
}
/** Gets a known sink for the specified query. */
private DataFlow::Node getASink(Query query) {
getAtmCfg(query).isKnownSink(result) and
private DataFlow::Node getASink(AtmConfig atmConfig) {
atmConfig.isKnownSink(result) and
// Only consider the source code for the project being analyzed.
exists(result.getFile().getRelativePath())
}
@@ -73,10 +43,10 @@ private DataFlow::Node getANotASink(NotASinkReason reason) {
* In other words, this is an endpoint that is not `Sink`, `NotASink`, or `LikelyNotASink` for the
* specified query.
*/
private DataFlow::Node getAnUnknown(Query query) {
getAtmCfg(query).isEffectiveSink(result) and
private DataFlow::Node getAnUnknown(AtmConfig atmConfig) {
atmConfig.isEffectiveSink(result) and
// Effective sinks should exclude sinks but this is a defensive requirement
not result = getASink(query) and
not result = getASink(atmConfig) and
// Effective sinks should exclude NotASink but for some queries (e.g. Xss) this is currently not always the case and
// so this is a defensive requirement
not result = getANotASink(_) and
@@ -85,16 +55,18 @@ private DataFlow::Node getAnUnknown(Query query) {
}
/** Gets the query-specific sink label for the given endpoint, if such a label exists. */
private EndpointLabel getSinkLabelForEndpoint(DataFlow::Node endpoint, Query query) {
endpoint = getASink(query) and result instanceof SinkLabel
private EndpointLabel getSinkLabelForEndpoint(DataFlow::Node endpoint, AtmConfig atmConfig) {
endpoint = getASink(atmConfig) and result instanceof SinkLabel
or
endpoint = getANotASink(_) and result instanceof NotASinkLabel
or
endpoint = getAnUnknown(query) and result instanceof UnknownLabel
endpoint = getAnUnknown(atmConfig) and result instanceof UnknownLabel
}
/** Gets an endpoint that should be extracted. */
DataFlow::Node getAnEndpoint(Query query) { exists(getSinkLabelForEndpoint(result, query)) }
DataFlow::Node getAnEndpoint(AtmConfig atmConfig) {
exists(getSinkLabelForEndpoint(result, atmConfig))
}
/**
* Endpoints and associated metadata.
@@ -107,11 +79,12 @@ DataFlow::Node getAnEndpoint(Query query) { exists(getSinkLabelForEndpoint(resul
* for technical information about the design of this predicate.
*/
predicate endpoints(
DataFlow::Node endpoint, string queryName, string key, string value, string valueType
DataFlow::Node endpoint, AtmConfig atmConfig, string queryName, string key, string value,
string valueType
) {
exists(Query query |
// Only provide metadata for labelled endpoints, since we do not extract all endpoints.
endpoint = getAnEndpoint(query) and
endpoint = getAnEndpoint(atmConfig) and
queryName = query.getName() and
(
// Holds if there is a taint flow path from a known source to the endpoint
@@ -136,7 +109,7 @@ predicate endpoints(
or
// The label for this query, considering the endpoint as a sink.
key = "sinkLabel" and
value = getSinkLabelForEndpoint(endpoint, query).getEncoding() and
value = getSinkLabelForEndpoint(endpoint, atmConfig).getEncoding() and
valueType = "string"
or
// The reason, or reasons, why the endpoint was labeled NotASink for this query.
@@ -155,8 +128,10 @@ predicate endpoints(
* `endpoint`. To preserve compatibility with the data pipeline, this relation will instead set
* `featureValue` to the empty string in this case.
*/
predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
endpoints(endpoint, _, _, _, _) and
predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, string featureName, string featureValue
) {
endpoints(endpoint, atmConfig, _, _, _, _) and
(
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
or
@@ -183,17 +158,19 @@ module FlowFromSource {
*/
private class Configuration extends DataFlow::Configuration {
Query q;
AtmConfig atmConfig;
DataFlow::Configuration dataFlowConfig;
Configuration() { this = getDataFlowCfg(q) }
Configuration() { this = dataFlowConfig }
Query getQuery() { result = q }
/** The sinks are the endpoints we're extracting. */
override predicate isSink(DataFlow::Node sink) { sink = getAnEndpoint(q) }
override predicate isSink(DataFlow::Node sink) { sink = getAnEndpoint(atmConfig) }
/** The sinks are the endpoints we're extracting. */
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink = getAnEndpoint(q) and exists(lbl)
sink = getAnEndpoint(atmConfig) and exists(lbl)
}
}
}

View File

@@ -1,26 +0,0 @@
/*
* For internal use only.
*
* Extracts training data we can use to train ML models for ML-powered queries.
*/
import javascript
import ExtractEndpointData as ExtractEndpointData
query predicate endpoints(
DataFlow::Node endpoint, string queryName, string key, string value, string valueType
) {
ExtractEndpointData::endpoints(endpoint, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, queryName, "sinkLabel", ["Sink", "NotASink"], "string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, queryName, "isExcludedFromEndToEndEvaluation", "false",
"boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, queryName, "isConstantExpression", "false", "boolean")
}
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
endpoints(endpoint, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, featureName, featureValue)
}

View File

@@ -4,10 +4,11 @@
* Maps ML-powered queries to their `EndpointType` for clearer labelling while evaluating ML model during training.
*/
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomATM
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
from string queryName, AtmConfig c, EndpointType e
@@ -23,6 +24,8 @@ where
c instanceof TaintedPathATM::TaintedPathAtmConfig
or
queryName = "Xss" and c instanceof XssATM::DomBasedXssAtmConfig
or
queryName = "XssThroughDom" and c instanceof XssThroughDomATM::XssThroughDomAtmConfig
) and
e = c.getASinkEndpointType()
select queryName, e.getEncoding() as label

View File

@@ -0,0 +1,51 @@
/*
* For internal use only.
*
* Extracts training data for NosqlInjection we can use to train ML models for a ML-powered version.
*/
import experimental.adaptivethreatmodeling.ATMConfig
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import javascript
import Queries
import ExtractEndpointData as ExtractEndpointData
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof NosqlInjectionQuery and
result instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
}
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionATM::Configuration
}
query predicate endpoints(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string queryName, string key, string value, string valueType
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
queryName = query.getName() and
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "sinkLabel", ["Sink", "NotASink"],
"string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isExcludedFromEndToEndEvaluation",
"false", "boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isConstantExpression", "false",
"boolean")
}
query predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string featureName, string featureValue
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
endpoints(endpoint, atmConfig, dataFlowConfig, _, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, atmConfig, featureName, featureValue)
}

View File

@@ -0,0 +1,51 @@
/*
* For internal use only.
*
* Extracts training data for SqlInjection we can use to train ML models for a ML-powered version.
*/
import experimental.adaptivethreatmodeling.ATMConfig
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import javascript
import Queries
import ExtractEndpointData as ExtractEndpointData
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof SqlInjectionQuery and
result instanceof SqlInjectionATM::SqlInjectionAtmConfig
}
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::Configuration
}
query predicate endpoints(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string queryName, string key, string value, string valueType
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
queryName = query.getName() and
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "sinkLabel", ["Sink", "NotASink"],
"string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isExcludedFromEndToEndEvaluation",
"false", "boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isConstantExpression", "false",
"boolean")
}
query predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string featureName, string featureValue
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
endpoints(endpoint, atmConfig, dataFlowConfig, _, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, atmConfig, featureName, featureValue)
}

View File

@@ -0,0 +1,51 @@
/*
* For internal use only.
*
* Extracts training data for TaintedPath we can use to train ML models for a ML-powered version.
*/
import experimental.adaptivethreatmodeling.ATMConfig
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import javascript
import Queries
import ExtractEndpointData as ExtractEndpointData
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof TaintedPathQuery and
result instanceof TaintedPathATM::TaintedPathAtmConfig
}
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
}
query predicate endpoints(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string queryName, string key, string value, string valueType
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
queryName = query.getName() and
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "sinkLabel", ["Sink", "NotASink"],
"string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isExcludedFromEndToEndEvaluation",
"false", "boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isConstantExpression", "false",
"boolean")
}
query predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string featureName, string featureValue
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
endpoints(endpoint, atmConfig, dataFlowConfig, _, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, atmConfig, featureName, featureValue)
}

View File

@@ -0,0 +1,51 @@
/*
* For internal use only.
*
* Extracts training data for XssThroughDom we can use to train ML models for a ML-powered version.
*/
import experimental.adaptivethreatmodeling.ATMConfig
import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomATM
import javascript
import Queries
import ExtractEndpointData as ExtractEndpointData
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof XssThroughDomQuery and
result instanceof XssThroughDomATM::XssThroughDomAtmConfig
}
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof XssThroughDomQuery and result instanceof XssThroughDomATM::Configuration
}
query predicate endpoints(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string queryName, string key, string value, string valueType
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
queryName = query.getName() and
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "sinkLabel", ["Sink", "NotASink"],
"string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isExcludedFromEndToEndEvaluation",
"false", "boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isConstantExpression", "false",
"boolean")
}
query predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string featureName, string featureValue
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
endpoints(endpoint, atmConfig, dataFlowConfig, _, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, atmConfig, featureName, featureValue)
}

View File

@@ -0,0 +1,51 @@
/*
* For internal use only.
*
* Extracts training data for Xss we can use to train ML models for a ML-powered version.
*/
import experimental.adaptivethreatmodeling.ATMConfig
import experimental.adaptivethreatmodeling.XssATM as XssATM
import javascript
import Queries
import ExtractEndpointData as ExtractEndpointData
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof XssQuery and
result instanceof XssATM::DomBasedXssAtmConfig
}
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof XssQuery and result instanceof XssATM::Configuration
}
query predicate endpoints(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string queryName, string key, string value, string valueType
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
queryName = query.getName() and
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, key, value, valueType) and
// only select endpoints that are either Sink or NotASink
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "sinkLabel", ["Sink", "NotASink"],
"string") and
// do not select endpoints filtered out by end-to-end evaluation
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isExcludedFromEndToEndEvaluation",
"false", "boolean") and
// only select endpoints that can be part of a tainted flow
ExtractEndpointData::endpoints(endpoint, atmConfig, queryName, "isConstantExpression", "false",
"boolean")
}
query predicate tokenFeatures(
DataFlow::Node endpoint, AtmConfig atmConfig, DataFlow::Configuration dataFlowConfig, Query query,
string featureName, string featureValue
) {
atmConfig = getAtmCfg(query) and
dataFlowConfig = getDataFlowCfg(query) and
endpoints(endpoint, atmConfig, dataFlowConfig, _, _, _, _, _) and
ExtractEndpointData::tokenFeatures(endpoint, atmConfig, featureName, featureValue)
}

View File

@@ -5,10 +5,12 @@
*/
newtype TQuery =
TCodeInjectionQuery() or
TNosqlInjectionQuery() or
TSqlInjectionQuery() or
TTaintedPathQuery() or
TXssQuery()
TXssQuery() or
TXssThroughDomQuery()
abstract class Query extends TQuery {
abstract string getName();
@@ -31,3 +33,7 @@ class TaintedPathQuery extends Query, TTaintedPathQuery {
class XssQuery extends Query, TXssQuery {
override string getName() { result = "Xss" }
}
class XssThroughDomQuery extends Query, TXssThroughDomQuery {
override string getName() { result = "XssThroughDom" }
}

View File

@@ -0,0 +1,29 @@
/**
* For internal use only.
*
* @name Code injection (experimental)
* @description Interpreting unsanitized user input as code allows a malicious user arbitrary code execution.
* @kind path-problem
* @scored
* @problem.severity error
* @security-severity 9.3
* @id js/ml-powered/code-injection
* @tags experimental security external/cwe/cwe-094 external/cwe/cwe-095 external/cwe/cwe-079 external/cwe/cwe-116
*/
import experimental.adaptivethreatmodeling.CodeInjectionATM
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/code-injection result depending on $@ " +
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
"a user-provided value", score

View File

@@ -0,0 +1,28 @@
/**
* For internal use only.
*
* @name DOM text reinterpreted as HTML (experimental)
* @description Reinterpreting text from the DOM as HTML can lead to a cross-site scripting vulnerability.
* @kind path-problem
* @scored
* @problem.severity error
* @security-severity 6.1
* @id js/ml-powered/xss-through-dom
* @tags experimental security external/cwe/cwe-079 external/cwe/cwe-116
*/
import experimental.adaptivethreatmodeling.XssThroughDomATM
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,
"(Experimental) $@ may be reinterpreted as HTML without escaping meta-characters. Identified using machine learning.",
source.getNode(), "DOM text"

View File

@@ -1,2 +1,7 @@
- description: ATM boosted Code Scanning queries for JavaScript
- queries: .
- include:
query filename: NosqlInjectionATM.ql
query filename: SqlInjectionATM.ql
query filename: TaintedPathATM.ql
query filename: XssATM.ql

View File

@@ -354,35 +354,6 @@ module DOM {
call.getNumArgument() = 1 and
unique(InferredType t | t = getArgumentTypeFromJQueryMethodGet(call)) = TTNumber()
)
or
// A `this` node from a callback given to a `$().each(callback)` call.
// purposely not using JQuery::MethodCall to avoid `jquery.each()`.
exists(DataFlow::CallNode eachCall | eachCall = JQuery::objectRef().getAMethodCall("each") |
this = DataFlow::thisNode(eachCall.getCallback(0).getFunction()) or
this = eachCall.getABoundCallbackParameter(0, 1)
)
or
// A read of an array-element from a JQuery object. E.g. `$("#foo")[0]`
exists(DataFlow::PropRead read |
read = this and read = JQuery::objectRef().getAPropertyRead()
|
unique(InferredType t | t = read.getPropertyNameExpr().analyze().getAType()) = TTNumber()
)
or
// A receiver node of an event handler on a DOM node
exists(DataFlow::SourceNode domNode, DataFlow::FunctionNode eventHandler |
// NOTE: we do not use `getABoundFunctionValue()`, since bound functions tend to have
// a different receiver anyway
eventHandler = domNode.getAPropertySource(any(string n | n.matches("on%")))
or
eventHandler =
domNode.getAMethodCall("addEventListener").getArgument(1).getAFunctionValue()
|
domNode = domValueRef() and
this = eventHandler.getReceiver()
)
or
this = DataFlow::thisNode(any(EventHandlerCode evt))
}
}
}
@@ -416,11 +387,6 @@ module DOM {
or
t.start() and
result = domValueRef().getAMethodCall(["item", "namedItem"])
or
t.startInProp("target") and
result = domEventSource()
or
exists(DataFlow::TypeTracker t2 | result = domValueRef(t2).track(t2, t))
}
/** Gets a data flow node that may refer to a value from the DOM. */

View File

@@ -183,12 +183,12 @@ module Promises {
/**
* Gets the pseudo-field used to describe resolved values in a promise.
*/
string valueProp() { result = "$PromiseResolveField$" }
string valueProp() { none() }
/**
* Gets the pseudo-field used to describe rejected values in a promise.
*/
string errorProp() { result = "$PromiseRejectField$" }
string errorProp() { none() }
}
/**

View File

@@ -756,10 +756,10 @@ private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
*/
module PseudoProperties {
bindingset[s]
private string pseudoProperty(string s) { result = "$" + s + "$" }
private string pseudoProperty(string s) { none() }
bindingset[s, v]
private string pseudoProperty(string s, string v) { result = "$" + s + "|" + v + "$" }
private string pseudoProperty(string s, string v) { none() }
/**
* Gets a pseudo-property for the location of elements in a `Set`

View File

@@ -136,7 +136,7 @@ module Angular2 {
/** Gets a reference to a `DomSanitizer` object. */
DataFlow::SourceNode domSanitizer() {
result.hasUnderlyingType(["@angular/platform-browser", "@angular/core"], "DomSanitizer")
result.hasUnderlyingType("@angular/platform-browser", "DomSanitizer")
}
/** A value that is about to be promoted to a trusted HTML or CSS value. */

View File

@@ -927,28 +927,6 @@ module Express {
override string getCredentialsKind() { result = kind }
}
/** A call to `response.sendFile`, considered as a file system access. */
private class ResponseSendFileAsFileSystemAccess extends FileSystemReadAccess,
DataFlow::MethodCallNode {
ResponseSendFileAsFileSystemAccess() {
exists(string name | name = "sendFile" or name = "sendfile" |
this.calls(any(ResponseExpr res).flow(), name)
)
}
override DataFlow::Node getADataNode() { none() }
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getRootPathArgument() {
result = this.(DataFlow::CallNode).getOptionArgument(1, "root")
}
override predicate isUpwardNavigationRejected(DataFlow::Node argument) {
argument = this.getAPathArgument()
}
}
/**
* A function that flows to a route setup.
*/

View File

@@ -4,23 +4,6 @@
import javascript
/**
* A call that can produce a file name.
*/
abstract private class FileNameProducer extends DataFlow::Node {
/**
* Gets a file name produced by this producer.
*/
abstract DataFlow::Node getAFileName();
}
/**
* A node that contains a file name, and is produced by a `ProducesFileNames`.
*/
private class ProducedFileName extends FileNameSource {
ProducedFileName() { this = any(FileNameProducer producer).getAFileName() }
}
/**
* A file name from the `walk-sync` library.
*/
@@ -118,319 +101,3 @@ private API::Node fastGlobFileName() {
private class FastGlobFileNameSource extends FileNameSource {
FastGlobFileNameSource() { this = fastGlobFileName().getAnImmediateUse() }
}
/**
* Classes and predicates for modeling the `fstream` library (https://www.npmjs.com/package/fstream).
*/
private module FStream {
/**
* Gets a reference to a method in the `fstream` library.
*/
private DataFlow::SourceNode getAnFStreamProperty(boolean writer) {
exists(DataFlow::SourceNode mod, string readOrWrite, string subMod |
mod = DataFlow::moduleImport("fstream") and
(
readOrWrite = "Reader" and writer = false
or
readOrWrite = "Writer" and writer = true
) and
subMod = ["File", "Dir", "Link", "Proxy"]
|
result = mod.getAPropertyRead(readOrWrite) or
result = mod.getAPropertyRead(readOrWrite).getAPropertyRead(subMod) or
result = mod.getAPropertyRead(subMod).getAPropertyRead(readOrWrite)
)
}
/**
* An invocation of a method defined in the `fstream` library.
*/
private class FStream extends FileSystemAccess, DataFlow::InvokeNode {
boolean writer;
FStream() { this = getAnFStreamProperty(writer).getAnInvocation() }
override DataFlow::Node getAPathArgument() {
result = this.getOptionArgument(0, "path")
or
not exists(this.getOptionArgument(0, "path")) and
result = this.getArgument(0)
}
}
/**
* An invocation of an `fstream` method that writes to a file.
*/
private class FStreamWriter extends FileSystemWriteAccess, FStream {
FStreamWriter() { writer = true }
override DataFlow::Node getADataNode() { none() }
}
/**
* An invocation of an `fstream` method that reads a file.
*/
private class FStreamReader extends FileSystemReadAccess, FStream {
FStreamReader() { writer = false }
override DataFlow::Node getADataNode() { none() }
}
}
/**
* A call to the library `write-file-atomic`.
*/
private class WriteFileAtomic extends FileSystemWriteAccess, DataFlow::CallNode {
WriteFileAtomic() {
this = DataFlow::moduleImport("write-file-atomic").getACall()
or
this = DataFlow::moduleMember("write-file-atomic", "sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getADataNode() { result = this.getArgument(1) }
}
/**
* A call to the library `recursive-readdir`.
*/
private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, API::CallNode {
RecursiveReadDir() { this = API::moduleImport("recursive-readdir").getACall() }
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() }
private API::Node trackFileSource() {
result = this.getParameter([1 .. 2]).getParameter(1)
or
not exists(this.getCallback([1 .. 2])) and result = this.getReturn().getPromised()
}
}
/**
* Classes and predicates for modeling the `jsonfile` library (https://www.npmjs.com/package/jsonfile).
*/
private module JsonFile {
/**
* A reader for JSON files.
*/
class JsonFileReader extends FileSystemReadAccess, API::CallNode {
JsonFileReader() {
this = API::moduleImport("jsonfile").getMember(["readFile", "readFileSync"]).getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() }
private API::Node trackRead() {
this.getCalleeName() = "readFile" and
(
result = this.getParameter([1 .. 2]).getParameter(1)
or
not exists(this.getCallback([1 .. 2])) and result = this.getReturn().getPromised()
)
or
this.getCalleeName() = "readFileSync" and
result = this.getReturn()
}
}
/** DEPRECATED: Alias for JsonFileReader */
deprecated class JSONFileReader = JsonFileReader;
/**
* A writer for JSON files.
*/
class JsonFileWriter extends FileSystemWriteAccess, DataFlow::CallNode {
JsonFileWriter() {
this =
DataFlow::moduleMember("jsonfile", any(string s | s = "writeFile" or s = "writeFileSync"))
.getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getADataNode() { result = this.getArgument(1) }
}
/** DEPRECATED: Alias for JsonFileWriter */
deprecated class JSONFileWriter = JsonFileWriter;
}
/**
* A call to the library `load-json-file`.
*/
private class LoadJsonFile extends FileSystemReadAccess, API::CallNode {
LoadJsonFile() {
this = API::moduleImport("load-json-file").getACall()
or
this = API::moduleImport("load-json-file").getMember("sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() }
private API::Node trackRead() {
this.getCalleeName() = "sync" and result = this.getReturn()
or
not this.getCalleeName() = "sync" and result = this.getReturn().getPromised()
}
}
/**
* A call to the library `write-json-file`.
*/
private class WriteJsonFile extends FileSystemWriteAccess, DataFlow::CallNode {
WriteJsonFile() {
this = DataFlow::moduleImport("write-json-file").getACall()
or
this = DataFlow::moduleMember("write-json-file", "sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getADataNode() { result = this.getArgument(1) }
}
/**
* A call to the library `walkdir`.
*/
private class WalkDir extends FileNameProducer, FileSystemAccess, API::CallNode {
WalkDir() {
this = API::moduleImport("walkdir").getACall()
or
this = API::moduleImport("walkdir").getMember("sync").getACall()
or
this = API::moduleImport("walkdir").getMember("async").getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() }
private API::Node trackFileSource() {
not this.getCalleeName() = ["sync", "async"] and
(
result = this.getParameter(this.getNumArgument() - 1).getParameter(0)
or
result = this.getReturn().getMember(EventEmitter::on()).getParameter(1).getParameter(0)
)
or
this.getCalleeName() = "sync" and result = this.getReturn()
or
this.getCalleeName() = "async" and result = this.getReturn().getPromised()
}
}
/**
* A call to the library `globule`.
*/
private class Globule extends FileNameProducer, FileSystemAccess, DataFlow::CallNode {
Globule() {
this = DataFlow::moduleMember("globule", "find").getACall()
or
this = DataFlow::moduleMember("globule", "match").getACall()
or
this = DataFlow::moduleMember("globule", "isMatch").getACall()
or
this = DataFlow::moduleMember("globule", "mapping").getACall()
or
this = DataFlow::moduleMember("globule", "findMapping").getACall()
}
override DataFlow::Node getAPathArgument() {
(this.getCalleeName() = "match" or this.getCalleeName() = "isMatch") and
result = this.getArgument(1)
or
this.getCalleeName() = "mapping" and
(
result = this.getAnArgument() and
not exists(result.getALocalSource().getAPropertyWrite("src"))
or
result = this.getAnArgument().getALocalSource().getAPropertyWrite("src").getRhs()
)
}
override DataFlow::Node getAFileName() {
result = this and
(
this.getCalleeName() = "find" or
this.getCalleeName() = "match" or
this.getCalleeName() = "findMapping" or
this.getCalleeName() = "mapping"
)
}
}
/**
* A file system access made by a NodeJS library.
* This class models multiple NodeJS libraries that access files.
*/
private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
int pathArgument; // The index of the path argument.
LibraryAccess() {
pathArgument = 0 and
(
this = DataFlow::moduleImport("path-exists").getACall()
or
this = DataFlow::moduleImport("rimraf").getACall()
or
this = DataFlow::moduleImport("readdirp").getACall()
or
this = DataFlow::moduleImport("walker").getACall()
or
this =
DataFlow::moduleMember("node-dir",
["readFiles", "readFilesStream", "files", "promiseFiles", "subdirs", "paths"]).getACall()
)
or
pathArgument = 0 and
this =
DataFlow::moduleMember("vinyl-fs", any(string s | s = "src" or s = "dest" or s = "symlink"))
.getACall()
or
pathArgument = [0 .. 1] and
(
this = DataFlow::moduleImport("ncp").getACall() or
this = DataFlow::moduleMember("ncp", "ncp").getACall()
)
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArgument) }
}
/**
* A call to the library [`chokidar`](https://www.npmjs.com/package/chokidar), where a call to `on` receives file names.
*/
class Chokidar extends FileNameProducer, FileSystemAccess, API::CallNode {
Chokidar() { this = API::moduleImport("chokidar").getMember("watch").getACall() }
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getAFileName() {
exists(DataFlow::CallNode onCall, int pathIndex |
onCall = this.getAChainedMethodCall("on") and
if onCall.getArgument(0).mayHaveStringValue("all") then pathIndex = 1 else pathIndex = 0
|
result = onCall.getCallback(1).getParameter(pathIndex)
)
}
}
/**
* A call to the [`mkdirp`](https://www.npmjs.com/package/mkdirp) library.
*/
private class Mkdirp extends FileSystemAccess, API::CallNode {
Mkdirp() {
this = API::moduleImport("mkdirp").getACall()
or
this = API::moduleImport("mkdirp").getMember("sync").getACall()
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
}

View File

@@ -16,135 +16,117 @@ module NoSql {
/** DEPRECATED: Alias for NoSql */
deprecated module NoSQL = NoSql;
/**
* Gets a value that has been assigned to the "$where" property of an object that flows to `queryArg`.
*/
private DataFlow::Node getADollarWhereProperty(API::Node queryArg) {
result = queryArg.getMember("$where").getARhs()
}
/**
* Provides classes modeling the MongoDB library.
*/
private module MongoDB {
/**
* Gets an access to `mongodb.MongoClient` or a database.
*
* In Mongo version 2.x, a client and a database handle were the same concept, but in 3.x
* they were separated. To handle everything with a single model, we treat them as the same here.
* Gets an import of MongoDB.
*/
private API::Node getAMongoClientOrDatabase() {
result = API::moduleImport("mongodb").getMember("MongoClient")
DataFlow::ModuleImportNode mongodb() { result.getPath() = "mongodb" }
/**
* Gets an access to `mongodb.MongoClient`.
*/
private DataFlow::SourceNode getAMongoClient(DataFlow::TypeTracker t) {
t.start() and
result = mongodb().getAPropertyRead("MongoClient")
or
result = getAMongoClientOrDatabase().getMember("db").getReturn()
exists(DataFlow::TypeTracker t2 | result = getAMongoClient(t2).track(t2, t))
}
/**
* Gets an access to `mongodb.MongoClient`.
*/
DataFlow::SourceNode getAMongoClient() { result = getAMongoClient(DataFlow::TypeTracker::end()) }
/** Gets a data flow node that leads to a `connect` callback. */
private DataFlow::SourceNode getAMongoDbCallback(DataFlow::TypeBackTracker t) {
t.start() and
result = getAMongoClient().getAMemberCall("connect").getArgument(1).getALocalSource()
or
result = getAMongoClientOrDatabase().getMember("connect").getLastParameter().getParameter(1)
exists(DataFlow::TypeBackTracker t2 | result = getAMongoDbCallback(t2).backtrack(t2, t))
}
/** Gets a data flow node that leads to a `connect` callback. */
private DataFlow::FunctionNode getAMongoDbCallback() {
result = getAMongoDbCallback(DataFlow::TypeBackTracker::end())
}
/**
* Gets an expression that may refer to a MongoDB database connection.
*/
private DataFlow::SourceNode getAMongoDb(DataFlow::TypeTracker t) {
t.start() and
result = getAMongoDbCallback().getParameter(1)
or
exists(DataFlow::TypeTracker t2 | result = getAMongoDb(t2).track(t2, t))
}
/**
* Gets an expression that may refer to a MongoDB database connection.
*/
DataFlow::SourceNode getAMongoDb() { result = getAMongoDb(DataFlow::TypeTracker::end()) }
/**
* A data flow node that may hold a MongoDB collection.
*/
abstract class Collection extends DataFlow::SourceNode { }
/**
* A collection resulting from calling `Db.collection(...)`.
*/
private class CollectionFromDb extends Collection {
CollectionFromDb() {
this = getAMongoDb().getAMethodCall("collection")
or
this = getAMongoDb().getAMethodCall("collection").getCallback(1).getParameter(0)
}
}
/**
* A collection based on the type `mongodb.Collection`.
*
* Note that this also covers `mongoose` models since they are subtypes
* of `mongodb.Collection`.
*/
private class CollectionFromType extends Collection {
CollectionFromType() { hasUnderlyingType("mongodb", "Collection") }
}
/** Gets a data flow node referring to a MongoDB collection. */
private API::Node getACollection() {
// A collection resulting from calling `Db.collection(...)`.
exists(API::Node collection |
collection = getAMongoClientOrDatabase().getMember("collection").getReturn()
|
result = collection
or
result = collection.getParameter(1).getParameter(0)
)
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
t.start() and
result instanceof Collection
or
// note that this also covers `mongoose` models since they are subtypes of `mongodb.Collection`
result = API::Node::ofType("mongodb", "Collection")
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
}
/** Gets a data flow node referring to a MongoDB collection. */
DataFlow::SourceNode getACollection() { result = getACollection(DataFlow::TypeTracker::end()) }
/** A call to a MongoDB query method. */
private class QueryCall extends DatabaseAccess, API::CallNode {
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
int queryArgIdx;
QueryCall() {
exists(string method |
CollectionMethodSignatures::interpretsArgumentAsQuery(method, queryArgIdx) and
this = getACollection().getMember(method).getACall()
exists(string m | this = getACollection().getAMethodCall(m) |
m = "count" and queryArgIdx = 0
or
m = "distinct" and queryArgIdx = 1
or
m = "find" and queryArgIdx = 0
)
}
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(this.getParameter(queryArgIdx))
}
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
}
/**
* An expression that is interpreted as a MongoDB query.
*/
class Query extends NoSql::Query {
QueryCall qc;
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
/**
* Provides signatures for the Collection methods.
*/
module CollectionMethodSignatures {
/**
* Holds if Collection method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
// FilterQuery
(
name = "aggregate" and n = 0
or
name = "count" and n = 0
or
name = "countDocuments" and n = 0
or
name = "deleteMany" and n = 0
or
name = "deleteOne" and n = 0
or
name = "distinct" and n = 1
or
name = "find" and n = 0
or
name = "findOne" and n = 0
or
name = "findOneAndDelete" and n = 0
or
name = "findOneAndRemove" and n = 0
or
name = "findOneAndReplace" and n = 0
or
name = "findOneAndUpdate" and n = 0
or
name = "remove" and n = 0
or
name = "replaceOne" and n = 0
or
name = "update" and n = 0
or
name = "updateMany" and n = 0
or
name = "updateOne" and n = 0
)
or
// UpdateQuery
(
name = "findOneAndUpdate" and n = 1
or
name = "update" and n = 1
or
name = "updateMany" and n = 1
or
name = "updateOne" and n = 1
)
}
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
}
}
@@ -155,342 +137,20 @@ private module Mongoose {
/**
* Gets an import of Mongoose.
*/
API::Node getAMongooseInstance() { result = API::moduleImport("mongoose") }
DataFlow::ModuleImportNode getAMongooseInstance() { result.getPath() = "mongoose" }
/**
* Gets a reference to `mongoose.createConnection`.
* Gets a call to `mongoose.createConnection`.
*/
API::Node createConnection() { result = getAMongooseInstance().getMember("createConnection") }
/**
* A Mongoose function.
*/
abstract private class MongooseFunction extends API::Node {
/**
* Gets the API-graph node for the result from this function (if the function returns a `Query`).
*/
abstract API::Node getQueryReturn();
/**
* Holds if this function returns a `Query` that evaluates to one or
* more Documents (`asArray` is false if it evaluates to a single
* Document).
*/
abstract predicate returnsDocumentQuery(boolean asArray);
/**
* Gets an argument that this function interprets as a query.
*/
abstract API::Node getQueryArgument();
DataFlow::CallNode createConnection() {
result = getAMongooseInstance().getAMemberCall("createConnection")
}
/**
* Provides classes modeling the Mongoose Model class
* A Mongoose collection object.
*/
module Model {
private class ModelFunction extends MongooseFunction {
string methodName;
ModelFunction() { this = getModelObject().getMember(methodName) }
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
/**
* Gets a API-graph node referring to a Mongoose Model object.
*/
private API::Node getModelObject() {
result = getAMongooseInstance().getMember("model").getReturn()
or
exists(API::Node conn | conn = createConnection().getReturn() |
result = conn.getMember("model").getReturn() or
result = conn.getMember("models").getAMember()
)
or
result = API::Node::ofType("mongoose", "Model")
}
/**
* Provides signatures for the Model methods.
*/
module MethodSignatures {
/**
* Holds if Model method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
// implement lots of the MongoDB collection interface
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(name, n)
or
name = "find" + ["ById", "One"] + "AndUpdate" and n = 1
or
name in ["delete" + ["Many", "One"], "geoSearch", "remove", "replaceOne", "where"] and
n = 0
or
name in [
"find" + ["", "ById", "One"],
"find" + ["ById", "One"] + "And" + ["Delete", "Remove", "Update"],
"update" + ["", "Many", "One"]
] and
n = 0
}
/**
* Holds if Model method `name` returns a Query.
*/
predicate returnsQuery(string name) {
name =
[
"$where", "count", "findOne", "findOneAndDelete", "findOneAndRemove",
"findOneAndReplace", "findOneAndUpdate", "geosearch", "remove", "replaceOne", "update",
"updateMany", "countDocuments", "updateOne", "where", "deleteMany", "deleteOne", "find",
"findById", "findByIdAndDelete", "findByIdAndRemove", "findByIdAndUpdate"
]
}
/**
* Holds if Document method `name` returns a query that results in
* one or more documents, the documents are wrapped in an array
* if `asArray` is true.
*/
predicate returnsDocumentQuery(string name, boolean asArray) {
asArray = false and name = "findOne"
or
asArray = true and name = "find"
}
}
}
/**
* Provides classes modeling the Mongoose Query class
*/
module Query {
private class QueryFunction extends MongooseFunction {
string methodName;
QueryFunction() { this = getAMongooseQuery().getMember(methodName) }
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
private class NewQueryFunction extends MongooseFunction {
NewQueryFunction() { this = getAMongooseInstance().getMember("Query") }
override API::Node getQueryReturn() { result = this.getInstance() }
override predicate returnsDocumentQuery(boolean asArray) { none() }
override API::Node getQueryArgument() { result = this.getParameter(2) }
}
/**
* Gets a data flow node referring to a Mongoose query object.
*/
API::Node getAMongooseQuery() {
result = any(MongooseFunction f).getQueryReturn()
or
result = API::Node::ofType("mongoose", "Query")
or
result =
getAMongooseQuery()
.getMember(any(string name | MethodSignatures::returnsQuery(name)))
.getReturn()
}
/**
* Provides signatures for the Query methods.
*/
module MethodSignatures {
/**
* Holds if Query method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
n = 0 and
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", "findOneAndUpdate", "update", "updateMany", "updateOne"]
}
/**
* Holds if Query method `name` returns a Query.
*/
predicate returnsQuery(string name) {
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"
]
}
/**
* Holds if Query method `name` returns a query that results in
* one or more documents, the documents are wrapped in an array
* if `asArray` is true.
*/
predicate returnsDocumentQuery(string name, boolean asArray) {
asArray = false and name = "findOne"
or
asArray = true and name = "find"
}
}
}
/**
* Provides classes modeling the Mongoose Document class
*/
module Document {
private class DocumentFunction extends MongooseFunction {
string methodName;
DocumentFunction() { this = getAMongooseDocument().getMember(methodName) }
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
/**
* A Mongoose Document that is retrieved from the backing database.
*/
class RetrievedDocument extends API::Node {
RetrievedDocument() {
exists(boolean asArray, API::Node param |
exists(MongooseFunction func |
func.returnsDocumentQuery(asArray) and
param = func.getLastParameter().getParameter(1)
)
or
exists(API::Node f |
f = Query::getAMongooseQuery().getMember("then") and
param = f.getParameter(0).getParameter(0)
or
f = Query::getAMongooseQuery().getMember("exec") and
param = f.getParameter(0).getParameter(1)
|
exists(DataFlow::MethodCallNode pred |
// limitation: look at the previous method call
Query::MethodSignatures::returnsDocumentQuery(pred.getMethodName(), asArray) and
pred.getAMethodCall() = f.getACall()
)
)
|
asArray = false and this = param
or
asArray = true and
// limitation: look for direct accesses
this = param.getUnknownMember()
)
}
}
/**
* Gets a data flow node referring to a Mongoose Document object.
*/
private API::Node getAMongooseDocument() {
result instanceof RetrievedDocument
or
result = API::Node::ofType("mongoose", "Document")
or
result =
getAMongooseDocument()
.getMember(any(string name | MethodSignatures::returnsDocument(name)))
.getReturn()
}
private module MethodSignatures {
/**
* Holds if Document method `name` returns a Query.
*/
predicate returnsQuery(string name) {
// Documents are subtypes of Models
Model::MethodSignatures::returnsQuery(name) or
name = "replaceOne" or
name = "update" or
name = "updateOne"
}
/**
* Holds if Document method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string name, int n) {
// Documents are subtypes of Models
Model::MethodSignatures::interpretsArgumentAsQuery(name, n)
or
n = 0 and
(
name = "replaceOne" or
name = "update" or
name = "updateOne"
)
}
/**
* Holds if Document method `name` returns a query that results in
* one or more documents, the documents are wrapped in an array
* if `asArray` is true.
*/
predicate returnsDocumentQuery(string name, boolean asArray) {
// Documents are subtypes of Models
Model::MethodSignatures::returnsDocumentQuery(name, asArray)
}
/**
* Holds if Document method `name` returns a Document.
*/
predicate returnsDocument(string name) {
name = ["depopulate", "init", "populate", "overwrite"]
}
}
class Model extends MongoDB::Collection {
Model() { this = getAMongooseInstance().getAMemberCall("model") }
}
/**
@@ -500,9 +160,7 @@ private module Mongoose {
string kind;
Credentials() {
exists(string prop |
this = createConnection().getParameter(3).getMember(prop).getARhs().asExpr()
|
exists(string prop | this = createConnection().getOptionArgument(3, prop).asExpr() |
prop = "user" and kind = "user name"
or
prop = "pass" and kind = "password"
@@ -511,308 +169,4 @@ private module Mongoose {
override string getCredentialsKind() { result = kind }
}
/**
* An expression that is interpreted as a (part of a) MongoDB query.
*/
class MongoDBQueryPart extends NoSql::Query {
MongooseFunction f;
MongoDBQueryPart() { this = f.getQueryArgument().getARhs().asExpr() }
override DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(f.getQueryArgument())
}
}
/**
* An evaluation of a MongoDB query.
*/
class ShorthandQueryEvaluation extends DatabaseAccess, DataFlow::InvokeNode {
MongooseFunction f;
ShorthandQueryEvaluation() {
this = f.getACall() and
// shorthand for execution: provide a callback
exists(f.getQueryReturn()) and
exists(this.getCallback(this.getNumArgument() - 1))
}
override DataFlow::Node getAQueryArgument() {
// NB: the complete information is not easily accessible for deeply chained calls
f.getQueryArgument().getARhs() = result
}
override DataFlow::Node getAResult() {
result = this.getCallback(this.getNumArgument() - 1).getParameter(1)
}
}
class ExplicitQueryEvaluation extends DatabaseAccess, DataFlow::CallNode {
string member;
ExplicitQueryEvaluation() {
// explicit execution using a Query method call
member = ["exec", "then", "catch"] and
Query::getAMongooseQuery().getMember(member).getACall() = this
}
private int resultParamIndex() {
member = "then" and result = 0
or
member = "exec" and result = 1
}
override DataFlow::Node getAResult() {
result = this.getCallback(_).getParameter(this.resultParamIndex())
}
override DataFlow::Node getAQueryArgument() {
// NB: the complete information is not easily accessible for deeply chained calls
none()
}
}
}
/**
* Provides classes modeling the Minimongo library.
*/
private module Minimongo {
/**
* Provides signatures for the Collection methods.
*/
module CollectionMethodSignatures {
/**
* Holds if Collection method `name` interprets parameter `n` as a query.
*/
predicate interpretsArgumentAsQuery(string m, int queryArgIdx) {
// implements most of the MongoDB interface
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
}
}
/** A call to a Minimongo query method. */
private class QueryCall extends DatabaseAccess, API::CallNode {
int queryArgIdx;
QueryCall() {
exists(string m |
this =
API::moduleImport("minimongo")
.getAMember()
.getReturn()
.getAMember()
.getMember(m)
.getACall() and
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
}
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(this.getParameter(queryArgIdx))
}
}
/**
* An expression that is interpreted as a Minimongo query.
*/
class Query extends NoSql::Query {
QueryCall qc;
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
}
/**
* Provides classes modeling the MarsDB library.
*/
private module MarsDB {
private class MarsDBAccess extends DatabaseAccess, DataFlow::CallNode {
string method;
MarsDBAccess() {
this =
API::moduleImport("marsdb")
.getMember("Collection")
.getInstance()
.getMember(method)
.getACall()
}
string getMethod() { result = method }
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() { none() }
}
/** A call to a MarsDB query method. */
private class QueryCall extends MarsDBAccess, API::CallNode {
int queryArgIdx;
QueryCall() {
exists(string m |
this.getMethod() = m and
// implements parts of the Minimongo interface
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
}
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() { result = this.getArgument(queryArgIdx) }
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(this.getParameter(queryArgIdx))
}
}
/**
* An expression that is interpreted as a MarsDB query.
*/
class Query extends NoSql::Query {
QueryCall qc;
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
}
/**
* Provides classes modeling the `Node Redis` library.
*
* Redis is an in-memory key-value store and not a database,
* but `Node Redis` can be exploited similarly to a NoSQL database by giving a method an array as argument instead of a string.
* As an example the below two invocations of `client.set` are equivalent:
*
* ```
* const redis = require("redis");
* const client = redis.createClient();
* client.set("key", "value");
* client.set(["key", "value"]);
* ```
*
* ioredis is a very similar library. However, ioredis does not support array arguments in the same way, and is therefore not vulnerable to the same kind of type confusion.
*/
private module Redis {
/**
* Gets a `Node Redis` client.
*/
private API::Node client() {
result = API::moduleImport("redis").getMember("createClient").getReturn()
or
result = API::moduleImport("redis").getMember("RedisClient").getInstance()
or
result = client().getMember("duplicate").getReturn()
or
result = client().getMember("duplicate").getLastParameter().getParameter(1)
}
/**
* Gets a (possibly chained) reference to a batch operation object.
* These have the same API as a redis client, except the calls are chained, and the sequence is terminated with a `.exec` call.
*/
private API::Node multi() {
result = client().getMember(["multi", "batch"]).getReturn()
or
result = multi().getAMember().getReturn()
}
/**
* Gets a `Node Redis` client instance. Either a client created using `createClient()`, or a batch operation object.
*/
private API::Node redis() { result = [client(), multi()] }
/**
* Provides signatures for the query methods from Node Redis.
*/
module QuerySignatures {
/**
* Holds if `method` interprets parameter `argIndex` as a key, and a later parameter determines a value/field.
* Thereby the method is vulnerable if parameter `argIndex` is unexpectedly an array instead of a string, as an attacker can control arguments to Redis that the attacker was not supposed to control.
*
* Only setters and similar methods are included.
* For getter-like methods it is not generally possible to gain access "outside" of where you are supposed to have access,
* it is at most possible to get a Redis call to return more results than expected (e.g. by adding more members to [`geohash`](https://redis.io/commands/geohash)).
*/
predicate argumentIsAmbiguousKey(string method, int argIndex) {
method =
[
"set", "publish", "append", "bitfield", "decrby", "getset", "hincrby", "hincrbyfloat",
"hset", "hsetnx", "incrby", "incrbyfloat", "linsert", "lpush", "lpushx", "lset", "ltrim",
"rename", "renamenx", "rpushx", "setbit", "setex", "smove", "zincrby", "zinterstore",
"hdel", "lpush", "pfadd", "rpush", "sadd", "sdiffstore", "srem"
] and
argIndex = 0
or
method = ["bitop", "hmset", "mset", "msetnx", "geoadd"] and
argIndex in [0 .. any(DataFlow::InvokeNode invk).getNumArgument() - 1]
}
}
/**
* An expression that is interpreted as a key in a Node Redis call.
*/
class RedisKeyArgument extends NoSql::Query {
RedisKeyArgument() {
exists(string method, int argIndex |
QuerySignatures::argumentIsAmbiguousKey(method, argIndex) and
this = redis().getMember(method).getParameter(argIndex).getARhs().asExpr()
)
}
}
/**
* An access to a database through redis
*/
class RedisDatabaseAccess extends DatabaseAccess, DataFlow::CallNode {
RedisDatabaseAccess() { this = redis().getMember(_).getACall() }
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() { none() }
}
}
/**
* Provides classes modeling the `ioredis` library.
*
* ```
* import Redis from 'ioredis'
* let client = new Redis(...)
* ```
*/
private module IoRedis {
/**
* Gets an `ioredis` client.
*/
API::Node ioredis() { result = API::moduleImport("ioredis").getInstance() }
/**
* An access to a database through ioredis
*/
class IoRedisDatabaseAccess extends DatabaseAccess, DataFlow::CallNode {
IoRedisDatabaseAccess() { this = ioredis().getMember(_).getACall() }
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() { none() }
}
}

View File

@@ -493,56 +493,11 @@ module NodeJSLib {
*/
module FS {
/**
* Gets a member `member` from module `fs` or its drop-in replacements `graceful-fs`, `fs-extra`, `original-fs`.
* A member `member` from module `fs`.
*/
DataFlow::SourceNode moduleMember(string member) {
result = fsModule(DataFlow::TypeTracker::end()).getAPropertyRead(member)
}
private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) {
exists(string moduleName |
moduleName = ["mz/fs", "original-fs", "fs-extra", "graceful-fs", "fs"]
|
result = DataFlow::moduleImport(moduleName)
or
// extra support for flexible names
result.asExpr().(Require).getArgument(0).mayHaveStringValue(moduleName)
) and
t.start()
or
t.start() and
result = DataFlow::moduleMember("fs", "promises")
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = fsModule(t2) |
result = pred.track(t2, t)
or
t.continue() = t2 and
exists(Promisify::PromisifyAllCall promisifyAllCall |
result = promisifyAllCall and
pred.flowsTo(promisifyAllCall.getArgument(0))
)
or
// const fs = require('fs');
// let fs_copy = methods.reduce((obj, method) => {
// obj[method] = fs[method];
// return obj;
// }, {});
t.continue() = t2 and
exists(
DataFlow::MethodCallNode call, DataFlow::ParameterNode obj, DataFlow::SourceNode method
|
call.getMethodName() = "reduce" and
result = call and
obj = call.getABoundCallbackParameter(0, 0) and
obj.flowsTo(any(DataFlow::FunctionNode f).getAReturn()) and
exists(DataFlow::PropWrite write, DataFlow::PropRead read |
write = obj.getAPropertyWrite() and
method.flowsToExpr(write.getPropertyNameExpr()) and
method.flowsToExpr(read.getPropertyNameExpr()) and
read.getBase().getALocalSource() = fsModule(t2) and
write.getRhs() = maybePromisified(read)
)
)
exists(string moduleName | moduleName = ["fs"] |
result = DataFlow::moduleMember(moduleName, member)
)
}
}
@@ -553,7 +508,7 @@ module NodeJSLib {
private class NodeJSFileSystemAccess extends FileSystemAccess, DataFlow::CallNode {
string methodName;
NodeJSFileSystemAccess() { this = maybePromisified(FS::moduleMember(methodName)).getACall() }
NodeJSFileSystemAccess() { this = FS::moduleMember(methodName).getACall() }
/**
* Gets the name of the called method.

View File

@@ -32,54 +32,38 @@ module SQL {
* Provides classes modeling the (API compatible) `mysql` and `mysql2` packages.
*/
private module MySql {
private string moduleName() { result = ["mysql", "mysql2", "mysql2/promise"] }
private DataFlow::SourceNode mysql() { result = DataFlow::moduleImport(["mysql", "mysql2"]) }
/** Gets the package name `mysql` or `mysql2`. */
API::Node mysql() { result = API::moduleImport(moduleName()) }
private DataFlow::CallNode createPool() { result = mysql().getAMemberCall("createPool") }
/** Gets a reference to `mysql.createConnection`. */
API::Node createConnection() {
result = mysql().getMember(["createConnection", "createConnectionPromise"])
/** Gets a reference to a MySQL pool. */
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
t.start() and
result = createPool()
}
/** Gets a reference to `mysql.createPool`. */
API::Node createPool() { result = mysql().getMember(["createPool", "createPoolCluster"]) }
/** Gets a reference to a MySQL pool. */
private DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) }
/** Gets a node that contains a MySQL pool created using `mysql.createPool()`. */
API::Node pool() {
result = createPool().getReturn()
or
result = pool().getMember("on").getReturn()
or
result = API::Node::ofType(moduleName(), ["Pool", "PoolCluster"])
}
/** Gets a call to `mysql.createConnection`. */
DataFlow::CallNode createConnection() { result = mysql().getAMemberCall("createConnection") }
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
API::Node connection() {
result = createConnection().getReturn()
or
result = createConnection().getReturn().getPromised()
or
result = pool().getMember("getConnection").getParameter(0).getParameter(1)
or
result = pool().getMember("getConnection").getPromised()
or
exists(API::CallNode call |
call = pool().getMember("on").getACall() and
call.getArgument(0).getStringValue() = ["connection", "acquire", "release"] and
result = call.getParameter(1).getParameter(0)
/** Gets a reference to a MySQL connection instance. */
private DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
t.start() and
(
result = createConnection()
or
result = pool().getAMethodCall("getConnection").getABoundCallbackParameter(0, 1)
)
or
result = API::Node::ofType(moduleName(), ["Connection", "PoolConnection"])
}
/** Gets a reference to a MySQL connection instance. */
DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) }
/** A call to the MySql `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() {
exists(API::Node recv | recv = pool() or recv = connection() |
this = recv.getMember(["query", "execute"]).getACall()
)
}
QueryCall() { this = [pool(), connection()].getAMethodCall("query") }
override DataFlow::Node getAResult() { result = this.getCallback(_).getParameter(1) }
@@ -96,7 +80,7 @@ private module MySql {
/** A call to the `escape` or `escapeId` method that performs SQL sanitization. */
class EscapingSanitizer extends SQL::SqlSanitizer, MethodCallExpr {
EscapingSanitizer() {
this = [mysql(), pool(), connection()].getMember(["escape", "escapeId"]).getACall().asExpr() and
this = [mysql(), pool(), connection()].getAMethodCall(["escape", "escapeId"]).asExpr() and
input = this.getArgument(0) and
output = this
}
@@ -107,9 +91,8 @@ private module MySql {
string kind;
Credentials() {
exists(API::Node callee, string prop |
callee in [createConnection(), createPool()] and
this = callee.getParameter(0).getMember(prop).getARhs().asExpr() and
exists(string prop |
this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and
(
prop = "user" and kind = "user name"
or
@@ -126,61 +109,23 @@ private module MySql {
* Provides classes modeling the PostgreSQL packages, such as `pg` and `pg-promise`.
*/
private module Postgres {
API::Node pg() {
result = API::moduleImport("pg")
or
result = pgpMain().getMember("pg")
}
/** Gets a reference to the `Client` constructor in the `pg` package, for example `require('pg').Client`. */
API::Node newClient() { result = pg().getMember("Client") }
/** Gets a freshly created Postgres client instance. */
API::Node client() {
result = newClient().getInstance()
or
// pool.connect(function(err, client) { ... })
result = pool().getMember("connect").getParameter(0).getParameter(1)
or
// await pool.connect()
result = pool().getMember("connect").getReturn().getPromised()
or
result = pgpConnection().getMember("client")
or
exists(API::CallNode call |
call = pool().getMember("on").getACall() and
call.getArgument(0).getStringValue() = ["connect", "acquire"] and
result = call.getParameter(1).getParameter(0)
)
or
result = client().getMember("on").getReturn()
or
result = API::Node::ofType("pg", ["Client", "PoolClient"])
}
/** Gets a constructor that when invoked constructs a new connection pool. */
API::Node newPool() {
/** Gets an expression that constructs a new connection pool. */
DataFlow::InvokeNode newPool() {
// new require('pg').Pool()
result = pg().getMember("Pool")
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Pool")
or
// new require('pg-pool')
result = API::moduleImport("pg-pool")
result = DataFlow::moduleImport("pg-pool").getAnInstantiation()
}
/** Gets an API node that refers to a connection pool. */
API::Node pool() {
result = newPool().getInstance()
or
result = pgpDatabase().getMember("$pool")
or
result = pool().getMember("on").getReturn()
or
result = API::Node::ofType("pg", "Pool")
/** Gets a creation of a Postgres client. */
DataFlow::InvokeNode newClient() {
result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client")
}
/** A call to the Postgres `query` method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = [client(), pool()].getMember("query").getACall() }
QueryCall() { this = [newClient(), newPool()].getAMethodCall("query") }
override DataFlow::Node getAResult() {
this.getNumArgument() = 2 and
@@ -209,11 +154,7 @@ private module Postgres {
string kind;
Credentials() {
exists(string prop |
this = [newClient(), newPool()].getParameter(0).getMember(prop).getARhs().asExpr()
or
this = pgPromise().getParameter(0).getMember(prop).getARhs().asExpr()
|
exists(string prop | this = [newClient(), newPool()].getOptionArgument(0, prop).asExpr() |
prop = "user" and kind = "user name"
or
prop = "password" and kind = prop
@@ -358,35 +299,30 @@ private module Postgres {
*/
private module Sqlite {
/** Gets a reference to the `sqlite3` module. */
API::Node sqlite() {
result = API::moduleImport("sqlite3")
DataFlow::SourceNode sqlite() {
result = DataFlow::moduleImport("sqlite3")
or
result = sqlite().getMember("verbose").getReturn()
result = sqlite().getAMemberCall("verbose")
}
/** Gets an expression that constructs or returns a Sqlite database instance. */
API::Node database() {
/** Gets an expression that constructs a Sqlite database instance. */
DataFlow::SourceNode newDb() {
// new require('sqlite3').Database()
result = sqlite().getMember("Database").getInstance()
or
// chained call
result = getAChainingQueryCall()
or
result = API::Node::ofType("sqlite3", "Database")
result = sqlite().getAConstructorInvocation("Database")
}
/** Gets a call to a query method on a Sqlite database instance that returns the same instance. */
private API::Node getAChainingQueryCall() {
result = database().getMember(["all", "each", "exec", "get", "run"]).getReturn()
/** Gets a data flow node referring to a Sqlite database instance. */
private DataFlow::SourceNode db(DataFlow::TypeTracker t) {
t.start() and
result = newDb()
}
/** Gets a data flow node referring to a Sqlite database instance. */
DataFlow::SourceNode db() { result = db(DataFlow::TypeTracker::end()) }
/** A call to a Sqlite query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() {
this = getAChainingQueryCall().getAnImmediateUse()
or
this = database().getMember("prepare").getACall()
}
QueryCall() { this = db().getAMethodCall(["all", "each", "exec", "get", "prepare", "run"]) }
override DataFlow::Node getAResult() {
result = this.getCallback(1).getParameter(1) or
@@ -401,200 +337,3 @@ private module Sqlite {
QueryString() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
}
}
/**
* Provides classes modeling the `mssql` package.
*/
private module MsSql {
/** Gets a reference to the `mssql` module. */
API::Node mssql() { result = API::moduleImport("mssql") }
/** Gets a node referring to an instance of the given class. */
API::Node mssqlClass(string name) {
result = mssql().getMember(name).getInstance()
or
result = API::Node::ofType("mssql", name)
}
/** Gets an API node referring to a Request object. */
API::Node request() {
result = mssqlClass("Request")
or
result = request().getMember(["input", "replaceInput", "output", "replaceOutput"]).getReturn()
or
result = [transaction(), pool()].getMember("request").getReturn()
}
/** Gets an API node referring to a Transaction object. */
API::Node transaction() {
result = mssqlClass("Transaction")
or
result = pool().getMember("transaction").getReturn()
}
/** Gets a API node referring to a ConnectionPool object. */
API::Node pool() { result = mssqlClass("ConnectionPool") }
/** A tagged template evaluated as a query. */
private class QueryTemplateExpr extends DatabaseAccess, DataFlow::ValueNode, DataFlow::SourceNode {
override TaggedTemplateExpr astNode;
QueryTemplateExpr() {
mssql().getMember("query").getAUse() = DataFlow::valueNode(astNode.getTag())
}
override DataFlow::Node getAResult() {
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() {
result = DataFlow::valueNode(astNode.getTemplate().getAnElement())
}
}
/** A call to a MsSql query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
QueryCall() { this = [mssql(), request()].getMember(["query", "batch"]).getACall() }
override DataFlow::Node getAResult() {
result = this.getCallback(1).getParameter(1)
or
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
}
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
}
/** An expression that is passed to a method that interprets it as SQL. */
class QueryString extends SQL::SqlString {
QueryString() {
exists(DatabaseAccess dba | dba instanceof QueryTemplateExpr or dba instanceof QueryCall |
this = dba.getAQueryArgument().asExpr()
)
}
}
/** An element of a query template, which is automatically sanitized. */
class QueryTemplateSanitizer extends SQL::SqlSanitizer {
QueryTemplateSanitizer() {
this = any(QueryTemplateExpr qte).getAQueryArgument().asExpr() and
input = this and
output = this
}
}
/** An expression that is passed as user name or password when creating a client or a pool. */
class Credentials extends CredentialsExpr {
string kind;
Credentials() {
exists(API::Node callee, string prop |
(
callee = mssql().getMember("connect")
or
callee = mssql().getMember("ConnectionPool")
) and
this = callee.getParameter(0).getMember(prop).getARhs().asExpr() and
(
prop = "user" and kind = "user name"
or
prop = "password" and kind = prop
)
)
}
override string getCredentialsKind() { result = kind }
}
}
/**
* Provides classes modeling the `sequelize` package.
*/
private module Sequelize {
class SequelizeModel extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
row =
[
"sequelize;;sequelize-typescript;;", //
"sequelize;Sequelize;sequelize;default;", //
"sequelize;Sequelize;sequelize;;Instance",
"sequelize;Sequelize;sequelize;;Member[Sequelize].Instance",
]
}
}
class SequelizeSink extends ModelInput::SinkModelCsv {
override predicate row(string row) {
row =
[
"sequelize;Sequelize;Member[query].Argument[0];sql-injection",
"sequelize;Sequelize;Member[query].Argument[0].Member[query];sql-injection",
"sequelize;;Member[literal,asIs].Argument[0];sql-injection",
"sequelize;;Argument[1];credentials[user name]",
"sequelize;;Argument[2];credentials[password]",
"sequelize;;Argument[0..].Member[username];credentials[user name]",
"sequelize;;Argument[0..].Member[password];credentials[password]"
]
}
}
class SequelizeSource extends ModelInput::SourceModelCsv {
override predicate row(string row) {
row = "sequelize;Sequelize;Member[query].ReturnValue.Awaited;database-access-result"
}
}
}
private module SpannerCsv {
class SpannerTypes extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1; type1; package2; type2; path
row =
[
"@google-cloud/spanner;;@google-cloud/spanner;;Member[Spanner]",
"@google-cloud/spanner;Database;@google-cloud/spanner;;ReturnValue.Member[instance].ReturnValue.Member[database].ReturnValue",
"@google-cloud/spanner;v1.SpannerClient;@google-cloud/spanner;;Member[v1].Member[SpannerClient].Instance",
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[runTransaction,runTransactionAsync,getTransaction].Argument[0..1].Parameter[1]",
"@google-cloud/spanner;Transaction;@google-cloud/spanner;Database;Member[getTransaction].ReturnValue.Awaited",
"@google-cloud/spanner;Snapshot;@google-cloud/spanner;Database;Member[getSnapshot].Argument[0..1].Parameter[1]",
"@google-cloud/spanner;Snapshot;@google-cloud/spanner;Database;Member[getSnapshot].ReturnValue.Awaited",
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[batchTransaction].ReturnValue",
"@google-cloud/spanner;BatchTransaction;@google-cloud/spanner;Database;Member[createBatchTransaction].ReturnValue.Awaited",
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;Database;Member[run,runPartitionedUpdate,runStream]",
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;Transaction;Member[run,runStream,runUpdate]",
"@google-cloud/spanner;~SqlExecutorDirect;@google-cloud/spanner;BatchTransaction;Member[createQueryPartitions]",
"@google-cloud/spanner;~SpannerObject;@google-cloud/spanner;v1.SpannerClient;",
"@google-cloud/spanner;~SpannerObject;@google-cloud/spanner;Database;",
"@google-cloud/spanner;~SpannerObject;@google-cloud/spanner;Transaction;",
"@google-cloud/spanner;~SpannerObject;@google-cloud/spanner;Snapshot;",
]
}
}
class SpannerSinks extends ModelInput::SinkModelCsv {
override predicate row(string row) {
// package; type; path; kind
row =
[
"@google-cloud/spanner;~SqlExecutorDirect;Argument[0];sql-injection",
"@google-cloud/spanner;~SqlExecutorDirect;Argument[0].Member[sql];sql-injection",
"@google-cloud/spanner;Transaction;Member[batchUpdate].Argument[0];sql-injection",
"@google-cloud/spanner;Transaction;Member[batchUpdate].Argument[0].ArrayElement.Member[sql];sql-injection",
"@google-cloud/spanner;v1.SpannerClient;Member[executeSql,executeStreamingSql].Argument[0].Member[sql];sql-injection",
]
}
}
class SpannerSources extends ModelInput::SourceModelCsv {
override predicate row(string row) {
row =
[
"@google-cloud/spanner;~SpannerObject;Member[executeSql].Argument[0..].Parameter[1];database-access-result",
"@google-cloud/spanner;~SpannerObject;Member[executeSql].ReturnValue.Awaited.Member[0];database-access-result",
"@google-cloud/spanner;~SpannerObject;Member[run].ReturnValue.Awaited;database-access-result",
"@google-cloud/spanner;~SpannerObject;Member[run].Argument[0..].Parameter[1];database-access-result",
]
}
}
}

View File

@@ -34,25 +34,8 @@ module ParseTorrent {
/**
* An access to user-controlled torrent information.
*/
class UserControlledTorrentInfo extends RemoteFlowSource instanceof DataFlow::PropRead {
UserControlledTorrentInfo() {
exists(API::Node read |
read = any(ParsedTorrent t).asApiNode().getAMember() and
this = read.getAnImmediateUse()
|
exists(string prop |
not (
prop = "private" or
prop = "infoHash" or
prop = "length"
// "pieceLength" and "lastPieceLength" are not guaranteed to be numbers as of commit ae3ad15d
) and
super.getPropertyName() = prop
)
or
not exists(super.getPropertyName())
)
}
class UserControlledTorrentInfo extends RemoteFlowSource {
UserControlledTorrentInfo() { none() }
override string getSourceType() { result = "torrent information" }
}

View File

@@ -428,8 +428,6 @@ module JQuery {
private DataFlow::SourceNode dollar(DataFlow::TypeTracker t) {
t.start() and
result = dollarSource()
or
exists(DataFlow::TypeTracker t2 | result = dollar(t2).track(t2, t))
}
/**
@@ -463,14 +461,6 @@ module JQuery {
}
}
/**
* A `this` node in a JQuery plugin function, which is a JQuery object.
*/
private class JQueryPluginThisObject extends Range {
JQueryPluginThisObject() {
this = DataFlow::thisNode(any(JQueryPluginMethod method).getFunction())
}
}
}
/** Gets a source of jQuery objects from the AST-based `JQueryObject` class. */

View File

@@ -55,7 +55,7 @@ module TaintedPath {
* There are currently four flow labels, representing the different combinations of
* normalization and absoluteness.
*/
abstract class PosixPath extends DataFlow::FlowLabel {
class PosixPath extends DataFlow::FlowLabel {
Normalization normalization;
Relativeness relativeness;
@@ -113,7 +113,7 @@ module TaintedPath {
/**
* A flow label representing an array of path elements that may include "..".
*/
abstract class SplitPath extends DataFlow::FlowLabel {
class SplitPath extends DataFlow::FlowLabel {
SplitPath() { this = "splitPath" }
}
}
@@ -218,12 +218,12 @@ module TaintedPath {
output = this
or
// non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g.
this instanceof StringReplaceCall and
input = this.getReceiver() and
this.getCalleeName() = "replace" and
input = getReceiver() and
output = this and
not exists(RegExpLiteral literal, RegExpTerm term |
this.(StringReplaceCall).getRegExp().asExpr() = literal and
this.(StringReplaceCall).isGlobal() and
getArgument(0).getALocalSource().asExpr() = literal and
literal.isGlobal() and
literal.getRoot() = term
|
term.getAMatchedString() = "/" or
@@ -247,15 +247,16 @@ module TaintedPath {
/**
* A call that removes all instances of "../" in the prefix of the string.
*/
class DotDotSlashPrefixRemovingReplace extends StringReplaceCall {
class DotDotSlashPrefixRemovingReplace extends DataFlow::CallNode {
DataFlow::Node input;
DataFlow::Node output;
DotDotSlashPrefixRemovingReplace() {
input = this.getReceiver() and
this.getCalleeName() = "replace" and
input = getReceiver() and
output = this and
exists(RegExpLiteral literal, RegExpTerm term |
this.getRegExp().asExpr() = literal and
getArgument(0).getALocalSource().asExpr() = literal and
(term instanceof RegExpStar or term instanceof RegExpPlus) and
term.getChild(0) = getADotDotSlashMatcher()
|
@@ -297,16 +298,17 @@ module TaintedPath {
/**
* A call that removes all "." or ".." from a path, without also removing all forward slashes.
*/
class DotRemovingReplaceCall extends StringReplaceCall {
class DotRemovingReplaceCall extends DataFlow::CallNode {
DataFlow::Node input;
DataFlow::Node output;
DotRemovingReplaceCall() {
input = this.getReceiver() and
this.getCalleeName() = "replace" and
input = getReceiver() and
output = this and
this.isGlobal() and
exists(RegExpLiteral literal, RegExpTerm term |
this.getRegExp().asExpr() = literal and
getArgument(0).getALocalSource().asExpr() = literal and
literal.isGlobal() and
literal.getRoot() = term and
not term.getAMatchedString() = "/"
|
@@ -622,8 +624,6 @@ module TaintedPath {
(
this = fileSystemAccess.getAPathArgument() and
not exists(fileSystemAccess.getRootPathArgument())
or
this = fileSystemAccess.getRootPathArgument()
) and
not this = any(ResolvingPathCall call).getInput()
}
@@ -664,74 +664,6 @@ module TaintedPath {
AngularJSTemplateUrlSink() { this = any(AngularJS::CustomDirective d).getMember("templateUrl") }
}
/**
* The path argument of a [send](https://www.npmjs.com/package/send) call, viewed as a sink.
*/
class SendPathSink extends Sink, DataFlow::ValueNode {
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
}
/**
* A path argument given to a `Page` in puppeteer, specifying where a pdf/screenshot should be saved.
*/
private class PuppeteerPath extends TaintedPath::Sink {
PuppeteerPath() {
this =
Puppeteer::page()
.getMember(["pdf", "screenshot"])
.getParameter(0)
.getMember("path")
.getARhs()
}
}
/**
* An argument given to the `prettier` library specifying the location of a config file.
*/
private class PrettierFileSink extends TaintedPath::Sink {
PrettierFileSink() {
this =
API::moduleImport("prettier")
.getMember(["resolveConfig", "resolveConfigFile", "getFileInfo"])
.getACall()
.getArgument(0)
or
this =
API::moduleImport("prettier")
.getMember("resolveConfig")
.getACall()
.getParameter(1)
.getMember("config")
.getARhs()
}
}
/**
* The `cwd` option for the `read-pkg` library.
*/
private class ReadPkgCwdSink extends TaintedPath::Sink {
ReadPkgCwdSink() {
this =
API::moduleImport("read-pkg")
.getMember(["readPackageAsync", "readPackageSync"])
.getParameter(0)
.getMember("cwd")
.getARhs()
}
}
/**
* The `cwd` option to a shell execution.
*/
private class ShellCwdSink extends TaintedPath::Sink {
ShellCwdSink() {
exists(SystemCommandExecution sys, API::Node opts |
opts.getARhs() = sys.getOptionsArg() and // assuming that an API::Node exists here.
this = opts.getMember("cwd").getARhs()
)
}
}
/**
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
*/