Skip to content

Commit eab270e

Browse files
committed
Move the definitions of isEffectiveSink and getAReasonSinkExcluded to the base class.
They can now be implemented generically for all sink types.
1 parent fc56c5a commit eab270e

File tree

11 files changed

+44
-269
lines changed

11 files changed

+44
-269
lines changed

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/ATMConfig.qll

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
private import javascript as JS
88
import EndpointTypes
9-
import EndpointCharacteristics
9+
import EndpointCharacteristics as EndpointCharacteristics
1010

1111
/**
1212
* EXPERIMENTAL. This API may change in the future.
@@ -48,7 +48,7 @@ abstract class AtmConfig extends string {
4848
final predicate isKnownSink(JS::DataFlow::Node sink) {
4949
// If the list of characteristics includes positive indicators with maximal confidence for this class, then it's a
5050
// known sink for the class.
51-
exists(EndpointCharacteristic characteristic |
51+
exists(EndpointCharacteristics::EndpointCharacteristic characteristic |
5252
characteristic.getEndpoints(sink) and
5353
characteristic
5454
.getImplications(this.getASinkEndpointType(), true, characteristic.maximalConfidence())
@@ -69,7 +69,26 @@ abstract class AtmConfig extends string {
6969
* Holds if the candidate sink `candidateSink` predicted by the machine learning model should be
7070
* an effective sink, i.e. one considered as a possible sink of flow in the boosted query.
7171
*/
72-
predicate isEffectiveSink(JS::DataFlow::Node candidateSink) { none() }
72+
final predicate isEffectiveSink(JS::DataFlow::Node candidateSink) {
73+
not exists(getAReasonSinkExcluded(candidateSink))
74+
}
75+
76+
final EndpointCharacteristics::EndpointCharacteristic getAReasonSinkExcluded(
77+
JS::DataFlow::Node candidateSink
78+
) {
79+
// An endpoint is an effective sink if it has neither standard endpoint filter characteristics nor endpoint filter
80+
// characteristics that are specific to this sink type.
81+
exists(EndpointCharacteristics::StandardEndpointFilterCharacteristic standardFilter |
82+
standardFilter.getEndpoints(candidateSink) and
83+
result = standardFilter
84+
)
85+
or
86+
exists(EndpointCharacteristics::EndpointFilterCharacteristic specificFilter |
87+
specificFilter.getEndpoints(candidateSink) and
88+
specificFilter.getImplications(getASinkEndpointType(), false, _) and
89+
result = specificFilter
90+
)
91+
}
7392

7493
/**
7594
* EXPERIMENTAL. This API may change in the future.

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/NosqlInjectionATM.qll

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -11,92 +11,13 @@ import AdaptiveThreatModeling
1111
private import CoreKnowledge as CoreKnowledge
1212
private import StandardEndpointFilters as StandardEndpointFilters
1313

14-
module SinkEndpointFilter {
15-
/**
16-
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
17-
*
18-
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
19-
* effective sink.
20-
*/
21-
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
22-
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
23-
or
24-
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
25-
// additional databases accesses that aren't modeled yet
26-
call.(DataFlow::MethodCallNode).getMethodName() =
27-
["create", "createCollection", "createIndexes"] and
28-
result = "matches database access call heuristic"
29-
or
30-
// Remove modeled sinks
31-
CoreKnowledge::isArgumentToKnownLibrarySinkFunction(sinkCandidate) and
32-
result = "modeled sink"
33-
or
34-
// Remove common kinds of unlikely sinks
35-
CoreKnowledge::isKnownStepSrc(sinkCandidate) and
36-
result = "predecessor in a modeled flow step"
37-
or
38-
// Remove modeled database calls. Arguments to modeled calls are very likely to be modeled
39-
// as sinks if they are true positives. Therefore arguments that are not modeled as sinks
40-
// are unlikely to be true positives.
41-
call instanceof DatabaseAccess and
42-
result = "modeled database access"
43-
or
44-
// Remove calls to APIs that aren't relevant to NoSQL injection
45-
call.getReceiver() instanceof Http::RequestNode and
46-
result = "receiver is a HTTP request expression"
47-
or
48-
call.getReceiver() instanceof Http::ResponseNode and
49-
result = "receiver is a HTTP response expression"
50-
)
51-
or
52-
// Require NoSQL injection sink candidates to be (a) direct arguments to external library calls
53-
// or (b) heuristic sinks for NoSQL injection.
54-
//
55-
// ## Direct arguments to external library calls
56-
//
57-
// The `StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter
58-
// allows sink candidates which are within object literals or array literals, for example
59-
// `req.sendFile(_, { path: ENDPOINT })`.
60-
//
61-
// However, the NoSQL injection query deals differently with these types of sinks compared to
62-
// other security queries. Other security queries such as SQL injection tend to treat
63-
// `ENDPOINT` as the ground truth sink, but the NoSQL injection query instead treats
64-
// `{ path: ENDPOINT }` as the ground truth sink and defines an additional flow step to ensure
65-
// data flows from `ENDPOINT` to the ground truth sink `{ path: ENDPOINT }`.
66-
//
67-
// Therefore for the NoSQL injection boosted query, we must ignore sink candidates within object
68-
// literals or array literals, to avoid having multiple alerts for the same security
69-
// vulnerability (one FP where the sink is `ENDPOINT` and one TP where the sink is
70-
// `{ path: ENDPOINT }`). We accomplish this by directly testing that the sink candidate is an
71-
// argument of a likely external library call.
72-
//
73-
// ## Heuristic sinks
74-
//
75-
// We also allow heuristic sinks in addition to direct arguments to external library calls.
76-
// These are copied from the `HeuristicNosqlInjectionSink` class defined within
77-
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
78-
// We can't reuse the class because importing that file would cause us to treat these
79-
// heuristic sinks as known sinks.
80-
not sinkCandidate = StandardEndpointFilters::getALikelyExternalLibraryCall().getAnArgument() and
81-
not (
82-
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(nosql|query)") or
83-
isArgTo(sinkCandidate, "(?i)(query)")
84-
) and
85-
result = "not a direct argument to a likely external library call or a heuristic sink"
86-
}
87-
}
88-
8914
class NosqlInjectionAtmConfig extends AtmConfig {
9015
NosqlInjectionAtmConfig() { this = "NosqlInjectionATMConfig" }
9116

9217
override predicate isKnownSource(DataFlow::Node source) {
9318
source instanceof NosqlInjection::Source or TaintedObject::isSource(source, _)
9419
}
9520

96-
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
97-
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
98-
}
99-
10021
override EndpointType getASinkEndpointType() { result instanceof NosqlInjectionSinkType }
10122
}
10223

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/SqlInjectionATM.qll

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,65 +10,11 @@ import AdaptiveThreatModeling
1010
import CoreKnowledge as CoreKnowledge
1111
import StandardEndpointFilters as StandardEndpointFilters
1212

13-
/**
14-
* This module provides logic to filter candidate sinks to those which are likely SQL injection
15-
* sinks.
16-
*/
17-
module SinkEndpointFilter {
18-
private import javascript
19-
private import SQL
20-
21-
/**
22-
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
23-
*
24-
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
25-
* effective sink.
26-
*/
27-
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
28-
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
29-
or
30-
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
31-
// prepared statements for SQL
32-
any(DataFlow::CallNode cn | cn.getCalleeName() = "prepare")
33-
.getAMethodCall("run")
34-
.getAnArgument() = sinkCandidate and
35-
result = "prepared SQL statement"
36-
or
37-
sinkCandidate instanceof DataFlow::ArrayCreationNode and
38-
result = "array creation"
39-
or
40-
// UI is unrelated to SQL
41-
call.getCalleeName().regexpMatch("(?i).*(render|html).*") and
42-
result = "HTML / rendering"
43-
)
44-
or
45-
// Require SQL injection sink candidates to be (a) arguments to external library calls
46-
// (possibly indirectly), or (b) heuristic sinks.
47-
//
48-
// Heuristic sinks are copied from the `HeuristicSqlInjectionSink` class defined within
49-
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
50-
// We can't reuse the class because importing that file would cause us to treat these
51-
// heuristic sinks as known sinks.
52-
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
53-
not (
54-
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(sql|query)") or
55-
isArgTo(sinkCandidate, "(?i)(query)") or
56-
isConcatenatedWithString(sinkCandidate,
57-
"(?s).*(ALTER|COUNT|CREATE|DATABASE|DELETE|DISTINCT|DROP|FROM|GROUP|INSERT|INTO|LIMIT|ORDER|SELECT|TABLE|UPDATE|WHERE).*")
58-
) and
59-
result = "not an argument to a likely external library call or a heuristic sink"
60-
}
61-
}
62-
6313
class SqlInjectionAtmConfig extends AtmConfig {
6414
SqlInjectionAtmConfig() { this = "SqlInjectionATMConfig" }
6515

6616
override predicate isKnownSource(DataFlow::Node source) { source instanceof SqlInjection::Source }
6717

68-
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
69-
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
70-
}
71-
7218
override EndpointType getASinkEndpointType() { result instanceof SqlInjectionSinkType }
7319
}
7420

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@ private import semmle.javascript.heuristics.SyntacticHeuristics
1212
private import CoreKnowledge as CoreKnowledge
1313
import EndpointCharacteristics as EndpointCharacteristics
1414

15-
/** Provides a set of reasons why a given data flow node should be excluded as a sink candidate. */
16-
string getAReasonSinkExcluded(DataFlow::Node n) {
17-
exists(EndpointCharacteristics::StandardEndpointFilterCharacteristic characteristic |
18-
characteristic.getEndpoints(n) and
19-
result = characteristic
20-
)
21-
}
22-
2315
/**
2416
* Holds if the node `n` is an argument to a function that has a manual model.
2517
*/
@@ -77,7 +69,7 @@ private DataFlow::SourceNode getACallback(DataFlow::ParameterNode p, DataFlow::T
7769
* Get calls for which we do not have the callee (i.e. the definition of the called function). This
7870
* acts as a heuristic for identifying calls to external library functions.
7971
*/
80-
private DataFlow::CallNode getACallWithoutCallee() {
72+
DataFlow::CallNode getACallWithoutCallee() {
8173
forall(Function callee | callee = result.getACallee() | callee.getTopLevel().isExterns()) and
8274
not exists(DataFlow::ParameterNode param, DataFlow::FunctionNode callback |
8375
param.flowsTo(result.getCalleeNode()) and

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/TaintedPathATM.qll

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,11 @@ import AdaptiveThreatModeling
1010
import CoreKnowledge as CoreKnowledge
1111
import StandardEndpointFilters as StandardEndpointFilters
1212

13-
/**
14-
* This module provides logic to filter candidate sinks to those which are likely path injection
15-
* sinks.
16-
*/
17-
module SinkEndpointFilter {
18-
private import javascript
19-
private import TaintedPath
20-
21-
/**
22-
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
23-
*
24-
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
25-
* effective sink.
26-
*/
27-
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
28-
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
29-
or
30-
// Require path injection sink candidates to be (a) arguments to external library calls
31-
// (possibly indirectly), or (b) heuristic sinks.
32-
//
33-
// Heuristic sinks are mostly copied from the `HeuristicTaintedPathSink` class defined within
34-
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
35-
// We can't reuse the class because importing that file would cause us to treat these
36-
// heuristic sinks as known sinks.
37-
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
38-
not (
39-
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(file|folder|dir|absolute)")
40-
or
41-
isArgTo(sinkCandidate, "(?i)(get|read)file")
42-
or
43-
exists(string pathPattern |
44-
// paths with at least two parts, and either a trailing or leading slash
45-
pathPattern = "(?i)([a-z0-9_.-]+/){2,}" or
46-
pathPattern = "(?i)(/[a-z0-9_.-]+){2,}"
47-
|
48-
isConcatenatedWithString(sinkCandidate, pathPattern)
49-
)
50-
or
51-
isConcatenatedWithStrings(".*/", sinkCandidate, "/.*")
52-
or
53-
// In addition to the names from `HeuristicTaintedPathSink` in the
54-
// `isAssignedToOrConcatenatedWith` predicate call above, we also allow the noisier "path"
55-
// name.
56-
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)path")
57-
) and
58-
result = "not a direct argument to a likely external library call or a heuristic sink"
59-
}
60-
}
61-
6213
class TaintedPathAtmConfig extends AtmConfig {
6314
TaintedPathAtmConfig() { this = "TaintedPathATMConfig" }
6415

6516
override predicate isKnownSource(DataFlow::Node source) { source instanceof TaintedPath::Source }
6617

67-
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
68-
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
69-
}
70-
7118
override EndpointType getASinkEndpointType() { result instanceof TaintedPathSinkType }
7219
}
7320

javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/XssATM.qll

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,65 +10,11 @@ import AdaptiveThreatModeling
1010
import CoreKnowledge as CoreKnowledge
1111
import StandardEndpointFilters as StandardEndpointFilters
1212

13-
/**
14-
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
15-
*/
16-
module SinkEndpointFilter {
17-
private import javascript
18-
private import DomBasedXss
19-
20-
/**
21-
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
22-
*
23-
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
24-
* effective sink.
25-
*/
26-
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
27-
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
28-
or
29-
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
30-
call.getCalleeName() = "setState"
31-
) and
32-
result = "setState calls ought to be safe in react applications"
33-
or
34-
// Require XSS sink candidates to be (a) arguments to external library calls (possibly
35-
// indirectly), or (b) heuristic sinks.
36-
//
37-
// Heuristic sinks are copied from the `HeuristicDomBasedXssSink` class defined within
38-
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
39-
// We can't reuse the class because importing that file would cause us to treat these
40-
// heuristic sinks as known sinks.
41-
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
42-
not (
43-
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(html|innerhtml)")
44-
or
45-
isArgTo(sinkCandidate, "(?i)(html|render)")
46-
or
47-
sinkCandidate instanceof StringOps::HtmlConcatenationLeaf
48-
or
49-
isConcatenatedWithStrings("(?is).*<[a-z ]+.*", sinkCandidate, "(?s).*>.*")
50-
or
51-
// In addition to the heuristic sinks from `HeuristicDomBasedXssSink`, explicitly allow
52-
// property writes like `elem.innerHTML = <TAINT>` that may not be picked up as HTML
53-
// concatenation leaves.
54-
exists(DataFlow::PropWrite pw |
55-
pw.getPropertyName().regexpMatch("(?i).*html*") and
56-
pw.getRhs() = sinkCandidate
57-
)
58-
) and
59-
result = "not a direct argument to a likely external library call or a heuristic sink"
60-
}
61-
}
62-
6313
class DomBasedXssAtmConfig extends AtmConfig {
6414
DomBasedXssAtmConfig() { this = "DomBasedXssATMConfig" }
6515

6616
override predicate isKnownSource(DataFlow::Node source) { source instanceof DomBasedXss::Source }
6717

68-
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
69-
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
70-
}
71-
7218
override EndpointType getASinkEndpointType() { result instanceof XssSinkType }
7319
}
7420

javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/DebugResultInclusion.ql

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@
1212
import javascript
1313
import experimental.adaptivethreatmodeling.ATMConfig
1414
import extraction.ExtractEndpointDataTraining
15+
private import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
16+
private import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
17+
private import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
18+
private import experimental.adaptivethreatmodeling.XssATM as XssAtm
1519

1620
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
1721
query instanceof NosqlInjectionQuery and
18-
result = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
22+
result = any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
1923
or
2024
query instanceof SqlInjectionQuery and
21-
result = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
25+
result = any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
2226
or
2327
query instanceof TaintedPathQuery and
24-
result = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
28+
result = any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
2529
or
2630
query instanceof XssQuery and
27-
result = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
31+
result = any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
2832
}
2933

3034
pragma[inline]

0 commit comments

Comments
 (0)