@contrast/agent 4.12.2 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bootstrap.js +2 -3
- package/esm.mjs +9 -35
- package/lib/assess/membrane/debraner.js +0 -2
- package/lib/assess/membrane/index.js +1 -3
- package/lib/assess/models/tag-range/util.js +1 -2
- package/lib/assess/policy/propagators.json +13 -4
- package/lib/assess/policy/rules.json +42 -0
- package/lib/assess/policy/signatures.json +18 -0
- package/lib/assess/policy/util.js +3 -2
- package/lib/assess/propagators/JSON/stringify.js +6 -11
- package/lib/assess/propagators/ajv/conditionals.js +0 -3
- package/lib/assess/propagators/ajv/json-schema-type-evaluators.js +5 -4
- package/lib/assess/propagators/ajv/refs.js +1 -2
- package/lib/assess/propagators/ajv/schema-context.js +2 -3
- package/lib/assess/propagators/joi/any.js +1 -1
- package/lib/assess/propagators/joi/object.js +1 -1
- package/lib/assess/propagators/joi/string-base.js +16 -3
- package/lib/assess/propagators/mongoose/map.js +1 -1
- package/lib/assess/propagators/mongoose/mixed.js +1 -1
- package/lib/assess/propagators/mongoose/string.js +1 -1
- package/lib/assess/propagators/path/common.js +38 -29
- package/lib/assess/propagators/path/resolve.js +1 -0
- package/lib/assess/propagators/sequelize/utils.js +1 -2
- package/lib/assess/propagators/v8/init-hooks.js +0 -1
- package/lib/assess/sinks/dynamo.js +65 -30
- package/lib/assess/static/hardcoded.js +3 -3
- package/lib/assess/static/read-findings-from-cache.js +40 -0
- package/lib/assess/technologies/index.js +12 -13
- package/lib/cli-rewriter/index.js +65 -6
- package/lib/core/async-storage/hooks/mysql.js +57 -6
- package/lib/core/config/options.js +12 -6
- package/lib/core/config/util.js +15 -33
- package/lib/core/exclusions/input.js +6 -1
- package/lib/core/express/index.js +2 -4
- package/lib/core/logger/debug-logger.js +2 -2
- package/lib/core/stacktrace.js +2 -1
- package/lib/hooks/http.js +81 -81
- package/lib/hooks/require.js +1 -0
- package/lib/instrumentation.js +17 -0
- package/lib/protect/analysis/aho-corasick.js +1 -1
- package/lib/protect/errors/handler-async-errors.js +66 -0
- package/lib/protect/input-analysis.js +7 -13
- package/lib/protect/listeners.js +27 -23
- package/lib/protect/rules/base-scanner/index.js +2 -2
- package/lib/protect/rules/bot-blocker/bot-blocker-rule.js +4 -2
- package/lib/protect/rules/cmd-injection/cmdinjection-rule.js +57 -2
- package/lib/protect/rules/cmd-injection-semantic-chained-commands/cmd-injection-semantic-chained-commands-rule.js +31 -2
- package/lib/protect/rules/cmd-injection-semantic-dangerous-paths/cmd-injection-semantic-dangerous-paths-rule.js +32 -2
- package/lib/protect/rules/index.js +42 -21
- package/lib/protect/rules/ip-denylist/ip-denylist-rule.js +2 -2
- package/lib/protect/rules/nosqli/nosql-injection-rule.js +104 -39
- package/lib/protect/rules/path-traversal/path-traversal-rule.js +3 -0
- package/lib/protect/rules/rule-factory.js +6 -7
- package/lib/protect/rules/signatures/signature.js +3 -0
- package/lib/protect/rules/sqli/sql-injection-rule.js +98 -5
- package/lib/protect/rules/sqli/sql-scanner/labels.json +0 -3
- package/lib/protect/rules/xss/reflected-xss-rule.js +3 -3
- package/lib/protect/sample-aggregator.js +65 -57
- package/lib/protect/service.js +709 -104
- package/lib/reporter/models/app-activity/sample.js +6 -0
- package/lib/reporter/speedracer/unknown-connection-state.js +20 -32
- package/lib/reporter/translations/to-protobuf/settings/assess-features.js +4 -6
- package/lib/reporter/ts-reporter.js +1 -1
- package/lib/util/get-file-type.js +43 -0
- package/package.json +11 -11
- package/perf-logs.js +2 -5
|
@@ -27,7 +27,6 @@ const DEFAULT_SETTINGS = { serverFeatures: { defend: { enabled: false } } };
|
|
|
27
27
|
* implemented, we add its constructor path to provide support.
|
|
28
28
|
*/
|
|
29
29
|
const ctors = {
|
|
30
|
-
/* eslint-disable prettier/prettier */
|
|
31
30
|
// protect rules
|
|
32
31
|
[RULES.CMD_INJECTION]: require('./cmd-injection/cmdinjection-rule'),
|
|
33
32
|
[RULES.CMD_INJECTION_COMMAND_BACKDOORS]: require('./cmd-injection-command-backdoors/cmd-injection-command-backdoors-rule'),
|
|
@@ -46,15 +45,15 @@ const ctors = {
|
|
|
46
45
|
[RULES.BOT_BLOCKER]: require('./bot-blocker/bot-blocker-rule'),
|
|
47
46
|
[RULES.IP_DENYLIST]: require('./ip-denylist/ip-denylist-rule'),
|
|
48
47
|
[RULES.VIRTUAL_PATCH]: require('./virtual-patch/virtual-patch-rule'),
|
|
49
|
-
/* eslint-enable */
|
|
50
48
|
};
|
|
51
49
|
|
|
52
50
|
class ProtectRuleFactory {
|
|
53
|
-
constructor({ featureSet = DEFAULT_SETTINGS, enabled }) {
|
|
51
|
+
constructor({ featureSet = DEFAULT_SETTINGS, enabled, agent }) {
|
|
54
52
|
this.featureSet = featureSet;
|
|
55
53
|
this.settings = {
|
|
56
54
|
exceptions: []
|
|
57
55
|
};
|
|
56
|
+
this.agent = agent;
|
|
58
57
|
this.policies = {};
|
|
59
58
|
this.signatures = new SignatureKit();
|
|
60
59
|
|
|
@@ -180,7 +179,7 @@ class ProtectRuleFactory {
|
|
|
180
179
|
*/
|
|
181
180
|
IpDenylist() {
|
|
182
181
|
const policy = this.policies[RULES.IP_DENYLIST];
|
|
183
|
-
return new ctors[RULES.IP_DENYLIST](policy);
|
|
182
|
+
return new ctors[RULES.IP_DENYLIST](policy, this.agent);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
/**
|
|
@@ -189,7 +188,7 @@ class ProtectRuleFactory {
|
|
|
189
188
|
*/
|
|
190
189
|
BotBlocker() {
|
|
191
190
|
const policy = this.policies[RULES.BOT_BLOCKER];
|
|
192
|
-
return new ctors[RULES.BOT_BLOCKER](policy);
|
|
191
|
+
return new ctors[RULES.BOT_BLOCKER](policy, this.agent);
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
/**
|
|
@@ -198,7 +197,7 @@ class ProtectRuleFactory {
|
|
|
198
197
|
*/
|
|
199
198
|
VirtualPatchRules() {
|
|
200
199
|
return this.policies[RULES.VIRTUAL_PATCH].map(
|
|
201
|
-
(policy) => new ctors[RULES.VIRTUAL_PATCH](policy)
|
|
200
|
+
(policy) => new ctors[RULES.VIRTUAL_PATCH](policy, this.agent)
|
|
202
201
|
);
|
|
203
202
|
}
|
|
204
203
|
|
|
@@ -224,7 +223,7 @@ class ProtectRuleFactory {
|
|
|
224
223
|
policy.enable_rep = enableRep;
|
|
225
224
|
|
|
226
225
|
if (ProtectRuleFactory.shouldBuildRule(policy)) {
|
|
227
|
-
const rule = new ctors[id](policy);
|
|
226
|
+
const rule = new ctors[id](policy, this.agent);
|
|
228
227
|
rule.signature = this.signatures.get(id);
|
|
229
228
|
memo.push(rule);
|
|
230
229
|
}
|
|
@@ -162,6 +162,9 @@ const createPatterns = (patternDefs) =>
|
|
|
162
162
|
class Signature {
|
|
163
163
|
constructor(definition) {
|
|
164
164
|
this.name = definition.name;
|
|
165
|
+
// the following is undefined if the rule is not an input to the
|
|
166
|
+
// agent-lib score functions.
|
|
167
|
+
this.agentLibBit = definition.agentLibBit;
|
|
165
168
|
this.keywordSearchers = normalizeScores(
|
|
166
169
|
createKeywords(definition.keywordsList)
|
|
167
170
|
);
|
|
@@ -15,7 +15,6 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
const _ = require('lodash');
|
|
18
|
-
|
|
19
18
|
const logger = require('../../../core/logger')('contrast:rules:protect');
|
|
20
19
|
const { IMPORTANCE, INPUT_TYPES, SINK_TYPES } = require('../common');
|
|
21
20
|
const isGenericComplicated = require('./generic-complicated');
|
|
@@ -38,8 +37,8 @@ const ScannerKit = new Map([
|
|
|
38
37
|
]);
|
|
39
38
|
|
|
40
39
|
class SQLInjectionRule extends require('../') {
|
|
41
|
-
constructor(policy) {
|
|
42
|
-
super(policy);
|
|
40
|
+
constructor(policy, agent) {
|
|
41
|
+
super(policy, agent);
|
|
43
42
|
|
|
44
43
|
this._scanners = new Map();
|
|
45
44
|
|
|
@@ -57,15 +56,29 @@ class SQLInjectionRule extends require('../') {
|
|
|
57
56
|
INPUT_TYPES.QUERYSTRING,
|
|
58
57
|
INPUT_TYPES.XML_VALUE,
|
|
59
58
|
INPUT_TYPES.URI,
|
|
60
|
-
INPUT_TYPES.URL_PARAMETER
|
|
59
|
+
INPUT_TYPES.URL_PARAMETER,
|
|
61
60
|
];
|
|
62
61
|
this.applicableSinks = [SINK_TYPES.SQL_QUERY];
|
|
62
|
+
|
|
63
|
+
// if not using agentLib this constructor is done.
|
|
64
|
+
if (!agent.agentLib) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.dbFlavors = {};
|
|
69
|
+
for (const flavor in agent.agentLib.DbType) {
|
|
70
|
+
this.dbFlavors[flavor.toLowerCase()] = this.agent.agentLib.DbType[flavor];
|
|
71
|
+
}
|
|
72
|
+
this.dbFlavorKeys = Object.keys(this.dbFlavors);
|
|
73
|
+
|
|
74
|
+
this.usesLibInputAnalysis = true;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
/**
|
|
66
78
|
* Evaluates the sink data by scanning the code string for injections
|
|
67
79
|
* by collected sample inputs.
|
|
68
80
|
* @param {SinkEvent} event Emitted by database drivers/wrappers/ORMs
|
|
81
|
+
* @param {Set(Sample)} applicableSamples Samples matching sql-injection criteria
|
|
69
82
|
*/
|
|
70
83
|
evaluateAtSink({ event, applicableSamples }) {
|
|
71
84
|
if (_.isEmpty(applicableSamples) || !event.data) {
|
|
@@ -88,9 +101,65 @@ class SQLInjectionRule extends require('../') {
|
|
|
88
101
|
}
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Evaluates the sink data by scanning the code string for injections
|
|
106
|
+
* by collected sample inputs. Rather than using the node evaluators,
|
|
107
|
+
* this takes advantage of the agent-lib.
|
|
108
|
+
* @param {SinkEvent} event Emitted by database drivers/wrappers/ORMs
|
|
109
|
+
* @param {Sample[]} applicableSamples samples that apply to SQL injection.
|
|
110
|
+
*/
|
|
111
|
+
// eslint-disable-next-line complexity
|
|
112
|
+
evaluateAtSinkForLib({ event, applicableSamples }) {
|
|
113
|
+
if (applicableSamples.size == 0 || !event.data) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const tag = this.dbFlavorKeys.find((name) => event.tags.has(name));
|
|
118
|
+
const dialect = this.dbFlavors[tag] || this.dbFlavors.mysql;
|
|
119
|
+
|
|
120
|
+
for (const sample of applicableSamples) {
|
|
121
|
+
let evalResult = null;
|
|
122
|
+
try {
|
|
123
|
+
const input = sample.input.value;
|
|
124
|
+
const sinkData = event.data;
|
|
125
|
+
const inputIndex = sinkData.indexOf(input);
|
|
126
|
+
|
|
127
|
+
if (inputIndex !== -1) {
|
|
128
|
+
evalResult = this.agent.agentLib.checkSqlInjectionSink(
|
|
129
|
+
inputIndex,
|
|
130
|
+
input.length,
|
|
131
|
+
dialect,
|
|
132
|
+
sinkData
|
|
133
|
+
);
|
|
134
|
+
if (evalResult) {
|
|
135
|
+
// capture the query for reporting purposes.
|
|
136
|
+
evalResult.query = sinkData;
|
|
137
|
+
} else if (inputIndex === 0 && input.length === sinkData.length) {
|
|
138
|
+
evalResult = {
|
|
139
|
+
startIndex: 0,
|
|
140
|
+
endIndex: input.length - 1,
|
|
141
|
+
overrunIndex: 0,
|
|
142
|
+
boundaryIndex: 0,
|
|
143
|
+
sinkData,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
logger.info(`Failed to evaluate command-injection sink: ${e}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (evalResult) {
|
|
152
|
+
this.appendAttackDetails(sample, evalResult);
|
|
153
|
+
sample.captureAppContext(event);
|
|
154
|
+
logger.warn(`EFFECTIVE - rule: ${this.id}, mode: ${this.mode}`);
|
|
155
|
+
this.blockRequest(sample);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
91
160
|
/**
|
|
92
161
|
* In addition to using the traditional signature matching, this rule will
|
|
93
|
-
* also check
|
|
162
|
+
* also check whether the input has other characteristics that warrant bumping
|
|
94
163
|
* an evaluation's importance from NONE -> WORTH_WATCHING.
|
|
95
164
|
* @returns {}
|
|
96
165
|
*/
|
|
@@ -141,6 +210,30 @@ class SQLInjectionRule extends require('../') {
|
|
|
141
210
|
};
|
|
142
211
|
}
|
|
143
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Builds details for Sql Injection Attack from agent-lib evaluation
|
|
215
|
+
* results.
|
|
216
|
+
* @param {Sample} sample The Sample for the attack
|
|
217
|
+
* @param {Object} findings The results of the sink analysis
|
|
218
|
+
* @returns {Object} The details
|
|
219
|
+
*/
|
|
220
|
+
buildDetailsForLib(sample, finding) {
|
|
221
|
+
if (!finding) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { query } = finding;
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
start: finding.startIndex,
|
|
229
|
+
end: finding.endIndex,
|
|
230
|
+
input: sample.input.toSerializable(),
|
|
231
|
+
boundaryOverrunIndex: finding.overrunIndex,
|
|
232
|
+
inputBoundaryIndex: finding.boundaryIndex,
|
|
233
|
+
query
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
144
237
|
/**
|
|
145
238
|
* Returns the appropriate scanner for the SQL dialect.
|
|
146
239
|
* @param {String} id The id of the scanner
|
|
@@ -14,8 +14,6 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const _ = require('lodash');
|
|
18
|
-
|
|
19
17
|
const logger = require('../../../core/logger')('contrast:rules:protect');
|
|
20
18
|
const { INPUT_TYPES, SINK_TYPES } = require('../common');
|
|
21
19
|
|
|
@@ -40,6 +38,8 @@ class ReflectedXssRule extends require('../') {
|
|
|
40
38
|
INPUT_TYPES.URL_PARAMETER
|
|
41
39
|
];
|
|
42
40
|
this.applicableSinks = [SINK_TYPES.RESPONSE_BODY];
|
|
41
|
+
|
|
42
|
+
this.usesLibInputAnalysis = true;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Evaluates a SinkEvent contingent on <code>applicableSinks</code>.
|
|
@@ -48,7 +48,7 @@ class ReflectedXssRule extends require('../') {
|
|
|
48
48
|
* @param {Set<Sample>} params.applicableSamples all (definite and ww) samples for xss
|
|
49
49
|
*/
|
|
50
50
|
evaluateAtSink({ event, applicableSamples }) {
|
|
51
|
-
if (!applicableSamples.size
|
|
51
|
+
if (!applicableSamples.size) {
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -20,7 +20,7 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
20
20
|
|
|
21
21
|
const _ = require('lodash');
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const { IMPORTANCE } = require('../constants');
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Compares two findings and returns the one which is
|
|
@@ -38,31 +38,26 @@ const rankEffectiveness = (a, b) => {
|
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
return
|
|
41
|
+
// effective is a boolean, so there is no "higher" effectiveness.
|
|
42
|
+
return a.sample.effective ? a : b;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Compares two findings and returns the one which
|
|
47
|
-
*
|
|
48
|
-
* assessment score is the same, <code>null</code> is
|
|
49
|
-
* returned, signifying that a ranking order cannot be
|
|
50
|
-
* determined for this criteria.
|
|
46
|
+
* Compares two findings and returns the one which has the highest
|
|
47
|
+
* score. If their scores are the same, return the first finding.
|
|
51
48
|
* @param {object} a First finding
|
|
52
49
|
* @param {object} b Second finding
|
|
53
|
-
* @returns {object
|
|
50
|
+
* @returns {object}
|
|
54
51
|
*/
|
|
55
|
-
const
|
|
56
|
-
const p1 = _.get(a, POINTS_PATH);
|
|
57
|
-
const p2 = _.get(b, POINTS_PATH);
|
|
52
|
+
const POINTS_PATH = 'sample.assessment.results.points';
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
54
|
+
function rankPoints(a, b) {
|
|
55
|
+
const pA = _.get(a, POINTS_PATH);
|
|
56
|
+
const pB = _.get(b, POINTS_PATH);
|
|
63
57
|
|
|
64
|
-
return
|
|
65
|
-
|
|
58
|
+
// return a if a is greater than or equal to b
|
|
59
|
+
return pA < pB ? b : a;
|
|
60
|
+
}
|
|
66
61
|
|
|
67
62
|
/**
|
|
68
63
|
* Ranks two findings against effectiveness and score.
|
|
@@ -72,64 +67,77 @@ const rankPoints = (a, b) => {
|
|
|
72
67
|
* @param {object} b Second finding
|
|
73
68
|
* @returns {object} The highest-ranked finding
|
|
74
69
|
*/
|
|
75
|
-
const rankFindings = (a, b) => rankEffectiveness(a, b) || rankPoints(a, b)
|
|
70
|
+
const rankFindings = (a, b) => rankEffectiveness(a, b) || rankPoints(a, b);
|
|
76
71
|
|
|
77
72
|
/**
|
|
78
|
-
* Takes a collection of findings and organizes them into
|
|
79
|
-
*
|
|
80
|
-
*
|
|
73
|
+
* Takes a collection of findings and organizes them into a map having keys
|
|
74
|
+
* being unique input types and paths, and values being unique findings pertaining
|
|
75
|
+
* to each key.
|
|
81
76
|
* @param {object} memo The map being created
|
|
82
77
|
* @param {object} finding Current finding in iteratee
|
|
83
78
|
* @returns {object} Map of ranked findings per type/path
|
|
84
79
|
*/
|
|
85
|
-
|
|
80
|
+
function rankIntoMap(memo, finding) {
|
|
86
81
|
const type = _.get(finding, 'sample.input.type', '_');
|
|
87
82
|
const path = _.get(finding, 'sample.input.documentPath', '_');
|
|
88
83
|
const name = _.get(finding, 'sample.input.name', '_');
|
|
89
84
|
|
|
90
|
-
|
|
85
|
+
// this string is the key to the map
|
|
91
86
|
const memoPath = `${type}.${path}.${name}`;
|
|
92
|
-
const existing = memo[memoPath];
|
|
93
87
|
|
|
94
|
-
memo[memoPath]
|
|
88
|
+
const existing = memo[memoPath];
|
|
89
|
+
// if it exists, rank it against the new finding
|
|
90
|
+
memo[memoPath] = existing ? rankFindings(existing, finding) : finding;
|
|
95
91
|
|
|
92
|
+
// memo returned despite having side effects because this function is
|
|
93
|
+
// called using reduce.
|
|
96
94
|
return memo;
|
|
97
|
-
}
|
|
95
|
+
}
|
|
98
96
|
|
|
99
97
|
/**
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
*
|
|
104
|
-
* collection without low-scoring samples. Low-scoring
|
|
105
|
-
* samples are not confirmed attacks. BUT, we DO want to
|
|
106
|
-
* report on findings if they were effective, always.
|
|
107
|
-
* @param {object[]} findings
|
|
108
|
-
* @returns {object[]}
|
|
109
|
-
*/
|
|
110
|
-
const filterLowScoring = (findings = []) =>
|
|
111
|
-
_.filter(
|
|
112
|
-
findings,
|
|
113
|
-
(finding) =>
|
|
114
|
-
// Virtual Patch findings do not have inputs but always create a WW sample.
|
|
115
|
-
// Make sure they were effective.
|
|
116
|
-
(!finding.sample.input && finding.sample.effective) ||
|
|
117
|
-
/* Don't include low-scoring ineffective attacks. */
|
|
118
|
-
!(!finding.sample.effective && !finding.sample.confirmedAttack)
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Accepts a collection of findings and returns a new
|
|
123
|
-
* collection whose samples are unique with respect to
|
|
124
|
-
* input type and path of the finding per input type. Any
|
|
125
|
-
* duplicate findings for the same type and path will be
|
|
126
|
-
* filtered to only include the one with the highest
|
|
127
|
-
* points/effectiveness ranking.
|
|
98
|
+
* Accepts a collection of findings and returns a new collection whose samples
|
|
99
|
+
* are unique with respect to input type and path of the finding per input type.
|
|
100
|
+
* Any duplicate findings for the same type and path will be filtered to only
|
|
101
|
+
* include the one with the highest points/effectiveness ranking.
|
|
128
102
|
* @param {object[]} findings The findings to aggregate.
|
|
103
|
+
* @param {function} wwFilter filter to check if a sample should be saved.
|
|
129
104
|
* @returns {object[]}
|
|
105
|
+
*
|
|
106
|
+
* effective, but not blocked, becomes PROBED.
|
|
107
|
+
*
|
|
130
108
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
109
|
+
function aggregate(findings, wwFilter) {
|
|
110
|
+
// separate the findings into effective and ineffective
|
|
111
|
+
// but marked as worth-watching by agent-lib. if not in
|
|
112
|
+
// one of the two lists, the finding is not relevant.
|
|
113
|
+
const effective = [];
|
|
114
|
+
const wwFindings = [];
|
|
115
|
+
// and the rest were not agent-lib worth-watching or effective
|
|
116
|
+
for (const finding of findings) {
|
|
117
|
+
const { sample } = finding;
|
|
118
|
+
if (sample.confirmedAttack || sample.effective) {
|
|
119
|
+
effective.push(finding);
|
|
120
|
+
} else if (finding.rule.agentLibBit && sample.assessment.results.importance === IMPORTANCE.WORTH_WATCHING) {
|
|
121
|
+
//console.log(finding.rule.id, s.results)
|
|
122
|
+
wwFindings.push(finding);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// do we need to run any inputs through the full scoring (not the abbreviated
|
|
127
|
+
// worth-watching scoring)?
|
|
128
|
+
if (wwFindings.length) {
|
|
129
|
+
for (const finding of wwFindings) {
|
|
130
|
+
if (wwFilter(finding)) {
|
|
131
|
+
finding.sample.assessment.results.importance = IMPORTANCE.DEFINITE;
|
|
132
|
+
// put this in the "effective list", even though it wasn't effective, so it
|
|
133
|
+
// will be reported as PROBED.
|
|
134
|
+
effective.push(finding);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return _.values(effective.reduce(rankIntoMap, {}));
|
|
140
|
+
}
|
|
133
141
|
|
|
134
142
|
module.exports = {
|
|
135
143
|
aggregate
|