@contrast/agent 4.12.0 → 4.13.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.
Files changed (41) hide show
  1. package/bin/VERSION +1 -1
  2. package/bin/linux/contrast-service +0 -0
  3. package/bin/mac/contrast-service +0 -0
  4. package/bin/windows/contrast-service.exe +0 -0
  5. package/esm.mjs +1 -32
  6. package/lib/assess/models/base-event.js +1 -1
  7. package/lib/assess/sinks/dynamo.js +65 -30
  8. package/lib/assess/static/read-findings-from-cache.js +40 -0
  9. package/lib/assess/technologies/index.js +12 -13
  10. package/lib/cli-rewriter/index.js +65 -6
  11. package/lib/contrast.js +1 -2
  12. package/lib/core/config/options.js +6 -0
  13. package/lib/core/config/util.js +15 -33
  14. package/lib/core/exclusions/input.js +6 -1
  15. package/lib/core/express/index.js +2 -4
  16. package/lib/hooks/http.js +81 -81
  17. package/lib/hooks/require.js +1 -0
  18. package/lib/instrumentation.js +17 -0
  19. package/lib/protect/errors/handler-async-errors.js +66 -0
  20. package/lib/protect/input-analysis.js +7 -13
  21. package/lib/protect/listeners.js +27 -23
  22. package/lib/protect/rules/base-scanner/index.js +2 -2
  23. package/lib/protect/rules/bot-blocker/bot-blocker-rule.js +4 -2
  24. package/lib/protect/rules/cmd-injection/cmdinjection-rule.js +57 -2
  25. package/lib/protect/rules/cmd-injection-semantic-chained-commands/cmd-injection-semantic-chained-commands-rule.js +31 -2
  26. package/lib/protect/rules/cmd-injection-semantic-dangerous-paths/cmd-injection-semantic-dangerous-paths-rule.js +32 -2
  27. package/lib/protect/rules/index.js +42 -21
  28. package/lib/protect/rules/ip-denylist/ip-denylist-rule.js +2 -2
  29. package/lib/protect/rules/nosqli/nosql-injection-rule.js +103 -38
  30. package/lib/protect/rules/path-traversal/path-traversal-rule.js +3 -0
  31. package/lib/protect/rules/rule-factory.js +6 -6
  32. package/lib/protect/rules/signatures/signature.js +3 -0
  33. package/lib/protect/rules/sqli/sql-injection-rule.js +98 -5
  34. package/lib/protect/rules/sqli/sql-scanner/labels.json +0 -3
  35. package/lib/protect/rules/xss/reflected-xss-rule.js +3 -3
  36. package/lib/protect/sample-aggregator.js +65 -57
  37. package/lib/protect/service.js +709 -104
  38. package/lib/reporter/models/app-activity/sample.js +6 -0
  39. package/lib/reporter/ts-reporter.js +1 -1
  40. package/lib/util/get-file-type.js +47 -0
  41. package/package.json +5 -3
@@ -28,13 +28,37 @@ const {
28
28
  } = require('./common');
29
29
 
30
30
  class Rule {
31
- constructor(policy = { mode: NO_ACTION }) {
31
+ /**
32
+ * Rules that need to make use of the agent lib can also take
33
+ * a second argument: `agent' which is a reference to the agent object.
34
+ */
35
+ constructor(policy, agent) {
36
+ if (!policy) {
37
+ policy = { mode: NO_ACTION };
38
+ }
32
39
  this.policy = policy;
33
40
 
34
41
  this.blockAtEntry = policy.mode === BLOCK_AT_PERIMETER;
35
42
  this.id = policy.id;
36
43
  this.mode = policy.mode;
37
44
  this.name = policy.name;
45
+ this.applicableInputs = [];
46
+ this.applicableSinks = [];
47
+ this.agent = agent;
48
+ const config = (this.agent && this.agent.config) || { agent: { node: {} } };
49
+
50
+ this.agentLibEnabled =
51
+ config.agent.node.native_input_analysis &&
52
+ config.agent.node.speedracer_input_analysis;
53
+
54
+ if (this.agentLibEnabled) {
55
+ this.evaluator = null;
56
+ }
57
+
58
+ // override this value if the rule uses agent library input analysis.
59
+ // all rules that agentLibBit applies to use this field with the single
60
+ // exception of nosqli which uses node input analysis.
61
+ this.usesLibInputAnalysis = false;
38
62
 
39
63
  // Samples --> {}
40
64
  // This lets us manage non-sample state for each rule. This
@@ -66,10 +90,7 @@ class Rule {
66
90
  * @returns {Boolean}
67
91
  */
68
92
  appliesToInputType(type, name) {
69
- return (
70
- _.isEmpty(this.applicableInputs) ||
71
- _.includes(this.applicableInputs, type)
72
- );
93
+ return this.applicableInputs.length === 0 || this.applicableInputs.includes(type);
73
94
  }
74
95
 
75
96
  /**
@@ -79,11 +100,7 @@ class Rule {
79
100
  * @returns {Boolean}
80
101
  */
81
102
  appliesToSink(type) {
82
- if (!this.applicableSinks) {
83
- return false;
84
- }
85
-
86
- return _.includes(this.applicableSinks, type);
103
+ return this.applicableSinks.includes(type);
87
104
  }
88
105
 
89
106
  /**
@@ -105,12 +122,6 @@ class Rule {
105
122
  return this.signature;
106
123
  }
107
124
 
108
- if (!this.evaluator) {
109
- throw new Error(
110
- `Rule ${this.id} has no custom evaluator for signature-matching/classification`
111
- );
112
- }
113
-
114
125
  return this.evaluator;
115
126
  }
116
127
 
@@ -144,6 +155,7 @@ class Rule {
144
155
  const name = sample.input ? sample.input.name : '<anon>';
145
156
  const type = sample.input ? sample.input.type : '<no-type>';
146
157
 
158
+ // TODO: should this be ||?
147
159
  if (!this.blockAtEntry && !sample.effective) {
148
160
  return;
149
161
  }
@@ -172,6 +184,10 @@ class Rule {
172
184
  preFilterUserInput(input, event, samples) {
173
185
  const evaluator = this.getEvaluator();
174
186
 
187
+ if (!evaluator) {
188
+ return;
189
+ }
190
+
175
191
  // This is to support the newer API accepting a UserInput,
176
192
  let evaluation;
177
193
  if (this.signature) {
@@ -188,7 +204,7 @@ class Rule {
188
204
  samples
189
205
  });
190
206
 
191
- if (_.get(sample, 'confirmedAttack') === true) {
207
+ if (sample && sample.confirmedAttack === true && this.mode === BLOCK_AT_PERIMETER) {
192
208
  this.blockRequest(sample);
193
209
  }
194
210
  }
@@ -247,7 +263,7 @@ class Rule {
247
263
 
248
264
  logger.debug(`${classifiedAs}: ${id} - ${type}.${name}.${value}`);
249
265
  if (classifiedAs === IMPORTANCE.NONE) {
250
- return;
266
+ return null;
251
267
  }
252
268
 
253
269
  const sample = samples.addRuleSample({
@@ -277,8 +293,8 @@ class Rule {
277
293
  // for CEF extensions
278
294
  outcome: this.getAttackStatus(sample),
279
295
  pri: this.id,
280
- request: _.get(sample, 'request.uri', '').replace(/ /g, '<space>'),
281
- spt: _.get(sample, 'request.port', ''),
296
+ request: (sample.request.uri || '').replace(/ /g, '<space>'),
297
+ spt: sample.request.port || '',
282
298
  requestMethod: sample.request.method,
283
299
  src: sample.request.ip,
284
300
  value: sample.input.value
@@ -329,7 +345,12 @@ class Rule {
329
345
  */
330
346
  appendAttackDetails(sample, findings) {
331
347
  sample.effective = true;
332
- sample.details = this.buildDetails(sample, findings);
348
+
349
+ if (!(this.usesLibInputAnalysis && this.agentLibEnabled) || !this.buildDetailsForLib) {
350
+ sample.details = this.buildDetails(sample, findings);
351
+ } else {
352
+ sample.details = this.buildDetailsForLib(sample, findings);
353
+ }
333
354
  }
334
355
  }
335
356
 
@@ -26,8 +26,8 @@ const {
26
26
  const Rule = require('../');
27
27
 
28
28
  class IpDenylistRule extends Rule {
29
- constructor(policy) {
30
- super(policy);
29
+ constructor(policy, agent) {
30
+ super(policy, agent);
31
31
  this._analyzer = new IpAnalyzer(policy.data);
32
32
  // TODO: This should go away with CONTRAST-34184
33
33
  this._analyzer.on('expired', (expired) => {
@@ -15,6 +15,7 @@ Copyright: 2022 Contrast Security, Inc
15
15
  'use strict';
16
16
  /* eslint-disable complexity */
17
17
  const _ = require('lodash');
18
+ const util = require('util');
18
19
 
19
20
  const logger = require('../../../core/logger')('contrast:rules:protect');
20
21
  const { INPUT_TYPES, SINK_TYPES } = require('../common');
@@ -32,12 +33,12 @@ const ScannerKit = new Map([
32
33
  [MONGODB, () => require('../nosqli/nosql-scanner').create('MongoDB')],
33
34
  [RETHINKDB, () => require('../nosqli/nosql-scanner').create('RethinkDB')]
34
35
  ]);
35
- const SubstringFinder = require('../base-scanner/substring-finder');
36
36
 
37
37
  class NoSqlInjectionRule extends require('../') {
38
- constructor(policy = {}) {
38
+ // eslint-disable-next-line default-param-last
39
+ constructor(policy = {}, agent) {
39
40
  policy.inputParseDepth = 3;
40
- super(policy);
41
+ super(policy, agent);
41
42
 
42
43
  this._scanners = new Map();
43
44
 
@@ -52,56 +53,64 @@ class NoSqlInjectionRule extends require('../') {
52
53
  INPUT_TYPES.QUERYSTRING,
53
54
  INPUT_TYPES.XML_VALUE,
54
55
  INPUT_TYPES.URI,
55
- INPUT_TYPES.URL_PARAMETER
56
+ INPUT_TYPES.URL_PARAMETER,
57
+ INPUT_TYPES.BODY,
58
+ INPUT_TYPES.MULTIPART_VALUE,
56
59
  ];
60
+
61
+ // POST bodies are handled by the lib.
62
+ // if lib is disabled, node input analysis needs to handle this.
63
+ if (!this.agentLibEnabled) {
64
+ this.applicableInputs.push(INPUT_TYPES.BODY);
65
+ }
66
+
57
67
  this.applicableSinks = [SINK_TYPES.NOSQL_QUERY];
68
+
69
+ // explicitly set to false so that this rule will continue to use node input analysis.
70
+ this.usesLibInputAnalysis = false;
58
71
  }
59
72
 
73
+ /**
74
+ * Evaluates the sink data by scanning the document object for repeated instances
75
+ * of the saved input object/strings. This applies to both string injection and
76
+ * object expansion.
77
+ * @param {SinkEvent} event Emitted by database drivers/wrappers/ORMs
78
+ * @param {Set} applicableSamples set of Samples applicable to rule.
79
+ */
60
80
  evaluateAtSink({ event, applicableSamples }) {
61
- if (_.isEmpty(applicableSamples) || !event.data) {
81
+ if (applicableSamples.size == 0 || !event.data) {
62
82
  return;
63
83
  }
64
84
 
65
- if (typeof event.data === 'object') {
85
+ const { data } = event;
86
+ if (typeof data === 'object') {
66
87
  for (const sample of applicableSamples) {
67
- const requestData = this.getRequestData(sample.input);
68
- if (!requestData) {
88
+ const doc = this.getInput(sample);
89
+ if (!doc) {
69
90
  return;
70
91
  }
71
- let found;
72
- traverse(event.data, (key, value) => {
73
- if (found) {
92
+
93
+ let evalResult = null;
94
+ traverse(data, (key, value) => {
95
+ if (evalResult) {
74
96
  return;
75
97
  }
76
98
 
77
- Object.keys(requestData).some((reqKey) => {
78
- if (value[reqKey] === requestData[reqKey]) {
79
- found = reqKey;
80
- return true;
99
+ // check for _exact_ match under clause for saved user input
100
+ for (const queryClause of Object.keys(doc)) {
101
+ if (key === queryClause) {
102
+ if (_.isEqual(value, doc[queryClause])) {
103
+ evalResult = this.buildFinding(data, queryClause, doc);
104
+ }
81
105
  }
82
- });
106
+ }
83
107
  });
84
108
 
85
- if (found) {
86
- const query = require('util').inspect(event.data, false, null);
87
-
88
- let injection;
89
- for (const location of new SubstringFinder(query, found)) {
90
- if (location) {
91
- injection = {
92
- input: found,
93
- location,
94
- query
95
- };
96
- break;
97
- }
98
- }
99
- if (injection) {
100
- this.appendAttackDetails(sample, injection);
101
- sample.captureAppContext(event);
102
- logger.warn(`EFFECTIVE - rule: ${this.id}, mode: ${this.mode}`);
103
- this.blockRequest(sample);
104
- }
109
+ if (evalResult) {
110
+ this.appendAttackDetails(sample, evalResult);
111
+ sample.captureAppContext(event);
112
+ logger.warn(`EFFECTIVE - rule: ${this.id}, mode: ${this.mode}`);
113
+ this.blockRequest(sample);
105
114
  }
106
115
  }
107
116
  } else if (typeof event.data === 'string' || event.data instanceof String) {
@@ -120,6 +129,47 @@ class NoSqlInjectionRule extends require('../') {
120
129
  }
121
130
  }
122
131
 
132
+ /**
133
+ * Depending on the sample's data, collect the query clause and object
134
+ * for a given input sample. Handles both body inputs and other types.
135
+ * If the library is enabled and the input is a JSON body, it will parse
136
+ * query clauses out and pass them in _inputInfoForSink.
137
+ * @param {Sample} sample applicable sample for nosqli
138
+ * @returns {Object} relevant query clause and doc object to search for.
139
+ */
140
+ getInput(sample) {
141
+ if (!_.isEmpty(sample._inputInfoForSink)) {
142
+ return {
143
+ [sample._inputInfoForSink.queryClause]: sample._inputInfoForSink.docObject
144
+ };
145
+ }
146
+
147
+ const reqData = this.getRequestData(sample.input);
148
+
149
+ return reqData;
150
+ }
151
+
152
+ /**
153
+ * Build the query object based on the inputs passed and stringify them for reporting.
154
+ * @param {String} queryClause the mongo query clause (ex: $ne, $where, etc.)
155
+ * @param {Object} docObject the document object passed to the mongo function.
156
+ */
157
+ buildFinding(queryObject, queryClause, docObject) {
158
+ // this input string being generated is kinda duplicate work from the input stage
159
+ // but it is only really hit when a nosqli is found, so I'm not sure its a big deal.
160
+ const input = util.inspect(docObject, false, null);
161
+ const query = util.inspect(queryObject, false, null);
162
+ // this substring being generated is some duplicate work.
163
+ const start = query.indexOf(input);
164
+ return {
165
+ input,
166
+ // account for query clause, ':' and ' '.
167
+ start: start - queryClause.length - 2,
168
+ end: start + (queryClause.length + 2),
169
+ query
170
+ };
171
+ }
172
+
123
173
  /**
124
174
  * Given the sample's user input object, will read the value from the request
125
175
  * from the async storage context.
@@ -149,7 +199,7 @@ class NoSqlInjectionRule extends require('../') {
149
199
  this.buildPathArray(_documentPath, pathArray);
150
200
  } else {
151
201
  // only qs params and body are "expandable"
152
- pathArray.push('body', ..._documentPath.split('.'));
202
+ pathArray.push('query', ..._documentPath.split('.'));
153
203
  }
154
204
 
155
205
  let data;
@@ -213,9 +263,24 @@ class NoSqlInjectionRule extends require('../') {
213
263
  return null;
214
264
  }
215
265
 
266
+ // if it's not the old node format, then it's the agent-lib format.
267
+ // yeah, i know it's ugly.
268
+ if (!findings.boundary) {
269
+ const { start, end, query } = findings;
270
+ return {
271
+ start,
272
+ end,
273
+ // since there are no actual `token boundary overruns' in nosqli,
274
+ // are these not the exact same as start and end?
275
+ boundaryOverrunIndex: end,
276
+ inputBoundaryIndex: start,
277
+ query
278
+ };
279
+ }
280
+
216
281
  const { boundary, location, query } = findings;
217
282
  let inputBoundaryIndex, boundaryOverrunIndex;
218
- const start = location[0];
283
+ const start = location[0]; // eslint-disable-line
219
284
  const end = location[1] + 1;
220
285
 
221
286
  if (boundary) {
@@ -41,7 +41,10 @@ class PathTraversalRule extends Rule {
41
41
  INPUT_TYPES.URI,
42
42
  INPUT_TYPES.URL_PARAMETER
43
43
  ];
44
+
44
45
  this.applicableSinks = [SINK_TYPES.FILE_PATH];
46
+
47
+ this.usesLibInputAnalysis = true;
45
48
  }
46
49
 
47
50
  /**
@@ -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'),
@@ -50,11 +49,12 @@ const ctors = {
50
49
  };
51
50
 
52
51
  class ProtectRuleFactory {
53
- constructor({ featureSet = DEFAULT_SETTINGS, enabled }) {
52
+ constructor({ featureSet = DEFAULT_SETTINGS, enabled, agent }) {
54
53
  this.featureSet = featureSet;
55
54
  this.settings = {
56
55
  exceptions: []
57
56
  };
57
+ this.agent = agent;
58
58
  this.policies = {};
59
59
  this.signatures = new SignatureKit();
60
60
 
@@ -180,7 +180,7 @@ class ProtectRuleFactory {
180
180
  */
181
181
  IpDenylist() {
182
182
  const policy = this.policies[RULES.IP_DENYLIST];
183
- return new ctors[RULES.IP_DENYLIST](policy);
183
+ return new ctors[RULES.IP_DENYLIST](policy, this.agent);
184
184
  }
185
185
 
186
186
  /**
@@ -189,7 +189,7 @@ class ProtectRuleFactory {
189
189
  */
190
190
  BotBlocker() {
191
191
  const policy = this.policies[RULES.BOT_BLOCKER];
192
- return new ctors[RULES.BOT_BLOCKER](policy);
192
+ return new ctors[RULES.BOT_BLOCKER](policy, this.agent);
193
193
  }
194
194
 
195
195
  /**
@@ -198,7 +198,7 @@ class ProtectRuleFactory {
198
198
  */
199
199
  VirtualPatchRules() {
200
200
  return this.policies[RULES.VIRTUAL_PATCH].map(
201
- (policy) => new ctors[RULES.VIRTUAL_PATCH](policy)
201
+ (policy) => new ctors[RULES.VIRTUAL_PATCH](policy, this.agent)
202
202
  );
203
203
  }
204
204
 
@@ -224,7 +224,7 @@ class ProtectRuleFactory {
224
224
  policy.enable_rep = enableRep;
225
225
 
226
226
  if (ProtectRuleFactory.shouldBuildRule(policy)) {
227
- const rule = new ctors[id](policy);
227
+ const rule = new ctors[id](policy, this.agent);
228
228
  rule.signature = this.signatures.get(id);
229
229
  memo.push(rule);
230
230
  }
@@ -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 wether the input has other characteristics that warrant bumping
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
@@ -115,9 +115,6 @@
115
115
  ")" : "operator",
116
116
  "," : "operator",
117
117
  "*" : "operator",
118
- "=" : "operator",
119
- "^" : "operator",
120
- "!" : "operator",
121
118
 
122
119
  ";": "terminator"
123
120
  },
@@ -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 || !_.isString(event.data)) {
51
+ if (!applicableSamples.size) {
52
52
  return;
53
53
  }
54
54