@contrast/protect 1.25.1 → 1.27.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.
@@ -17,18 +17,18 @@
17
17
 
18
18
  const { isSecurityException } = require('../security-exception');
19
19
 
20
- module.exports = function(core) {
20
+ module.exports = function (core) {
21
21
  const {
22
22
  logger,
23
23
  protect: { getSourceContext },
24
24
  } = core;
25
25
 
26
- return core.protect.errorHandlers.commonHandler = function(err) {
26
+ return core.protect.errorHandlers.commonHandler = function (err) {
27
27
  if (!isSecurityException(err)) {
28
28
  throw err;
29
29
  }
30
30
 
31
- const sourceContext = getSourceContext('protect-common-error-handler');
31
+ const sourceContext = getSourceContext();
32
32
  if (!sourceContext) {
33
33
  logger.info('SecurityException caught by Contrast but Protect store is unavailable for req handling');
34
34
  return;
@@ -19,7 +19,7 @@ const SecurityException = require('../../security-exception');
19
19
  const { patchType } = require('../constants');
20
20
 
21
21
 
22
- module.exports = function(core) {
22
+ module.exports = function (core) {
23
23
  const {
24
24
  logger,
25
25
  depHooks,
@@ -107,7 +107,7 @@ module.exports = function(core) {
107
107
  const ret = orig();
108
108
  if (ret && ret.catch) {
109
109
  return ret.catch((err) => {
110
- const sourceContext = protect.getSourceContext('express.Layer.handle_error');
110
+ const sourceContext = protect.getSourceContext();
111
111
  const isSecurityException = SecurityException.isSecurityException(err);
112
112
 
113
113
  if (isSecurityException && sourceContext) {
@@ -18,7 +18,7 @@
18
18
  const { isSecurityException } = require('../../security-exception');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- module.exports = function(core) {
21
+ module.exports = function (core) {
22
22
  const {
23
23
  depHooks,
24
24
  patcher,
@@ -53,11 +53,11 @@ module.exports = function(core) {
53
53
  * @param {object} request fastify request
54
54
  * @param {object} reply fastify repoly
55
55
  */
56
- fastifyErrorHandler.handler = function(err, request, reply) {
56
+ fastifyErrorHandler.handler = function (err, request, reply) {
57
57
  const normalHandler = fastifyErrorHandler._userHandler || fastifyErrorHandler.defaultErrorHandler;
58
58
 
59
59
  if (isSecurityException(err)) {
60
- const sourceContext = protect.getSourceContext('fastify.errorHandler');
60
+ const sourceContext = protect.getSourceContext();
61
61
 
62
62
  if (!sourceContext) {
63
63
  normalHandler.call(this, err, request, reply);
@@ -73,7 +73,7 @@ module.exports = function(core) {
73
73
  /**
74
74
  * Instruments fastify in order to add our custom error handler.
75
75
  */
76
- fastifyErrorHandler.install = function() {
76
+ fastifyErrorHandler.install = function () {
77
77
  depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => patcher.patch(fastify, {
78
78
  name: 'fastify',
79
79
  patchType,
@@ -35,7 +35,7 @@ module.exports = function (core) {
35
35
  patchType,
36
36
  post(data) {
37
37
  const [err] = data.args;
38
- const sourceContext = protect.getSourceContext('Hapi.boom.boomify');
38
+ const sourceContext = protect.getSourceContext();
39
39
  const isSecurityException = SecurityException.isSecurityException(err);
40
40
 
41
41
  if (isSecurityException && sourceContext && err.output.statusCode !== 403) {
@@ -19,7 +19,7 @@ const SecurityException = require('../../security-exception');
19
19
  const { patchType } = require('../constants');
20
20
 
21
21
 
22
- module.exports = function(core) {
22
+ module.exports = function (core) {
23
23
  const {
24
24
  logger,
25
25
  depHooks,
@@ -41,7 +41,7 @@ module.exports = function(core) {
41
41
  patchType,
42
42
  around(orig, data) {
43
43
  const [err] = data.args;
44
- const sourceContext = protect.getSourceContext('Koa.Application.handleRequest');
44
+ const sourceContext = protect.getSourceContext();
45
45
  const isSecurityException = SecurityException.isSecurityException(err);
46
46
 
47
47
  if (isSecurityException && sourceContext) {
@@ -15,18 +15,12 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- module.exports = function (core) {
19
- const { scopes: { sources }, logger } = core;
18
+ module.exports = function init(core) {
19
+ const { scopes: { sources } } = core;
20
20
 
21
- function getSourceContext(callPoint) {
21
+ function getSourceContext() {
22
22
  const sourceContext = sources.getStore()?.protect;
23
-
24
- if (!sourceContext) {
25
- logger.debug('source context not available in %s', callPoint);
26
- return null;
27
- }
28
-
29
- return sourceContext.allowed ? null : sourceContext;
23
+ return sourceContext?.allowed ? null : sourceContext;
30
24
  }
31
25
 
32
26
  core.protect.getSourceContext = getSourceContext;
package/lib/index.js CHANGED
@@ -19,8 +19,6 @@ const agentLib = require('@contrast/agent-lib');
19
19
  const { callChildComponentMethodsSync } = require('@contrast/common');
20
20
 
21
21
  module.exports = function(core) {
22
- if (!core.config.protect.enable) return;
23
-
24
22
  const protect = core.protect = {
25
23
  agentLib: module.exports.instantiateAgentLib(agentLib),
26
24
  };
@@ -37,6 +35,11 @@ module.exports = function(core) {
37
35
  require('./semantic-analysis')(core);
38
36
 
39
37
  protect.install = function() {
38
+ if (!core.config.getEffectiveValue('protect.enable')) {
39
+ core.logger.debug('protect is disabled, skipping installation');
40
+ return;
41
+ }
42
+
40
43
  core.rewriter.install('protect');
41
44
  callChildComponentMethodsSync(protect, 'install');
42
45
  };
@@ -32,7 +32,7 @@ module.exports = (core) => {
32
32
  name: 'busboy.prototype.emit',
33
33
  patchType,
34
34
  pre({ args }) {
35
- const sourceContext = protect.getSourceContext('busboy');
35
+ const sourceContext = protect.getSourceContext();
36
36
 
37
37
  if (sourceContext) {
38
38
  const [eventName, , , fileName] = args;
@@ -40,7 +40,7 @@ module.exports = (core) => {
40
40
  const [req, , origNext] = data.args;
41
41
 
42
42
  function contrastNext(origErr) {
43
- const sourceContext = protect.getSourceContext('cookie-parser');
43
+ const sourceContext = protect.getSourceContext();
44
44
 
45
45
  let securityException;
46
46
 
@@ -49,7 +49,7 @@ module.exports = (core) => {
49
49
  const [req, , origNext] = data.args;
50
50
 
51
51
  function contrastNext(origErr) {
52
- const sourceContext = protect.getSourceContext('express.query');
52
+ const sourceContext = protect.getSourceContext();
53
53
  let securityException;
54
54
 
55
55
  // It is possible for the query to be already parsed by `qs`
@@ -93,7 +93,7 @@ module.exports = (core) => {
93
93
  // we can exit early if
94
94
  // the layer doesn't match the request or
95
95
  // the layer doesn't recognize any parameters
96
- if (!data.result || !layer.keys || layer.keys.length === 0) {
96
+ if (!data.result || !layer.keys || layer.keys.length === 0 || layer.route.path === '*') {
97
97
  return;
98
98
  }
99
99
 
@@ -40,9 +40,9 @@ module.exports = (core) => {
40
40
  name: 'fastify.build',
41
41
  patchType,
42
42
  post({ result: server }) {
43
- server.addHook('preValidation', function(request, reply, done) {
43
+ server.addHook('preValidation', function (request, reply, done) {
44
44
  let securityException;
45
- const sourceContext = protect.getSourceContext('Fastify.preValidationHook');
45
+ const sourceContext = protect.getSourceContext();
46
46
 
47
47
  if (sourceContext) {
48
48
  try {
@@ -32,7 +32,7 @@ module.exports = (core) => {
32
32
  name: 'Formidable.IncomingForm.prototype.parse',
33
33
  patchType,
34
34
  pre(data) {
35
- const sourceContext = protect.getSourceContext('formidable');
35
+ const sourceContext = protect.getSourceContext();
36
36
 
37
37
  if (sourceContext) {
38
38
  const origCb = data.args[1];
@@ -66,7 +66,7 @@ module.exports = (core) => {
66
66
  });
67
67
 
68
68
  server.ext('onPreHandler', function onPreHandler(req, h) {
69
- const sourceContext = protect.getSourceContext('hapi.onPreHandler');
69
+ const sourceContext = protect.getSourceContext();
70
70
 
71
71
  if (sourceContext) {
72
72
  try {
@@ -107,7 +107,7 @@ module.exports = (core) => {
107
107
  name: 'Pez.Dispenser.prototype.emit',
108
108
  patchType,
109
109
  pre: ({ args }) => {
110
- const sourceContext = protect.getSourceContext('hapi/pez');
110
+ const sourceContext = protect.getSourceContext();
111
111
 
112
112
  if (sourceContext) {
113
113
  const [eventName, part] = args;
@@ -38,7 +38,7 @@ module.exports = (core) => {
38
38
  const [ctx, origNext] = data.args;
39
39
 
40
40
  async function contrastNext(origErr) {
41
- const sourceContext = protect.getSourceContext('koa-body');
41
+ const sourceContext = protect.getSourceContext();
42
42
 
43
43
  if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
44
44
  sourceContext.parsedBody = ctx.request.body;
@@ -38,7 +38,7 @@ module.exports = (core) => {
38
38
  const [ctx, origNext] = data.args;
39
39
 
40
40
  async function contrastNext(origErr) {
41
- const sourceContext = protect.getSourceContext('koa-bodyparser');
41
+ const sourceContext = protect.getSourceContext();
42
42
 
43
43
 
44
44
  if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
@@ -37,7 +37,7 @@ module.exports = (core) => {
37
37
  depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
38
38
  function contrastStartMiddleware(ctx, next) {
39
39
  if (ctx.query && Object.keys(ctx.query).length) {
40
- const sourceContext = protect.getSourceContext('Koa startMiddleware');
40
+ const sourceContext = protect.getSourceContext();
41
41
 
42
42
  if (sourceContext && !('parsedQuery' in sourceContext)) {
43
43
  sourceContext.parsedQuery = ctx.query;
@@ -57,7 +57,7 @@ module.exports = (core) => {
57
57
  // if not already inserted, insert the initial middleware.
58
58
  if (
59
59
  app.middleware &&
60
- (!app.middleware[0] || !app.middleware[0]._isContrastStartMiddleware)
60
+ (!app.middleware[0] || !app.middleware[0]._isContrastStartMiddleware)
61
61
  ) {
62
62
  app.middleware.splice(0, 0, contrastStartMiddleware);
63
63
  }
@@ -100,7 +100,7 @@ module.exports = (core) => {
100
100
  const [ctx, origNext] = data.args;
101
101
 
102
102
  async function contrastNext(origErr) {
103
- const sourceContext = protect.getSourceContext('koa-cookie');
103
+ const sourceContext = protect.getSourceContext();
104
104
 
105
105
  if (sourceContext && ctx.cookie) {
106
106
  sourceContext.parsedCookies = ctx.cookie;
@@ -42,7 +42,7 @@ module.exports = (core) => {
42
42
 
43
43
  // We are getting the sourceContext here because in the time of calling
44
44
  // the contrastNext() method the context is lost
45
- const sourceContext = protect.getSourceContext('multer');
45
+ const sourceContext = protect.getSourceContext();
46
46
 
47
47
  if (!sourceContext) return;
48
48
 
@@ -33,7 +33,7 @@ module.exports = (core) => {
33
33
  patchType,
34
34
  post({ args, result }) {
35
35
  if (result && Object.keys(result).length) {
36
- const sourceContext = protect.getSourceContext('qs');
36
+ const sourceContext = protect.getSourceContext();
37
37
 
38
38
  // We need to run analysis for the `qs` result only when it's used as a query parser.
39
39
  // `qs` is used also for parsing bodies, but these cases we handle individually with
@@ -32,7 +32,7 @@ module.exports = (core) => {
32
32
  patchType,
33
33
  post({ result }) {
34
34
  if (result && Object.keys(result).length) {
35
- const sourceContext = protect.getSourceContext('universal-cookie');
35
+ const sourceContext = protect.getSourceContext();
36
36
 
37
37
  if (sourceContext) {
38
38
  sourceContext.parsedCookies = result;
@@ -130,7 +130,7 @@ module.exports = function(core) {
130
130
  }
131
131
  };
132
132
 
133
- inputTracing.nosqlInjectionMongo = function (sourceContext, sinkContext) {
133
+ inputTracing.nosqlInjectionMongo = function(sourceContext, sinkContext) {
134
134
  const ruleId = NOSQL_INJECTION_MONGO;
135
135
  const nosqlInjectionMongoResults =
136
136
  getResultsByRuleId(ruleId, sourceContext) || [];
@@ -155,7 +155,7 @@ module.exports = function(core) {
155
155
  }
156
156
  });
157
157
 
158
- if (expansionResults) {
158
+ if (expansionResults.length) {
159
159
  let expansionFindings = null;
160
160
  for (const result of expansionResults) {
161
161
  expansionFindings = handleObjectValue(result, sinkContext.value);
@@ -172,14 +172,14 @@ module.exports = function(core) {
172
172
  }
173
173
  }
174
174
 
175
- if (stringInjectionResults) {
175
+ if (stringInjectionResults.length) {
176
176
  let stringFindings = null;
177
177
 
178
178
  for (const result of stringInjectionResults) {
179
179
  if (typeof sinkContext.value === 'object') {
180
180
  traverseKeysAndValues(
181
181
  sinkContext.value,
182
- function (path, type, value, obj) {
182
+ function(_path, type, value, obj) {
183
183
  if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
184
184
 
185
185
  if (!stringFindings && type == 'Key' && value == '$accumulator') {
@@ -221,10 +221,16 @@ module.exports = function(core) {
221
221
  const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
222
222
 
223
223
  const nosqlInjectionResults = sourceContext.resultsMap[ruleId];
224
+ const isAlreadyPresentInNosqlresults = result.idsList &&
225
+ result.idsList.some(
226
+ (el) =>
227
+ el === agentLibIDListTypes.MONGO_SLEEP ||
228
+ el === agentLibIDListTypes.TRUE_CLAUSE_1
229
+ );
224
230
  if (Array.isArray(nosqlInjectionResults)) {
225
- nosqlInjectionResults.push(nosqlInjectionResult);
231
+ !isAlreadyPresentInNosqlresults && nosqlInjectionResults.push(nosqlInjectionResult);
226
232
  } else {
227
- sourceContext.resultsMap[ruleId] = [nosqlInjectionResult];
233
+ !isAlreadyPresentInNosqlresults && (sourceContext.resultsMap[ruleId] = [nosqlInjectionResult]);
228
234
  }
229
235
 
230
236
  handleFindings(
@@ -18,7 +18,7 @@
18
18
  const { isString } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- module.exports = function(core) {
21
+ module.exports = function (core) {
22
22
  const {
23
23
  scopes: { instrumentation },
24
24
  patcher,
@@ -29,7 +29,7 @@ module.exports = function(core) {
29
29
  function pre({ args, name, hooked, orig }) {
30
30
  if (instrumentation.isLocked()) return;
31
31
 
32
- const sourceContext = getSourceContext('child_process');
32
+ const sourceContext = getSourceContext();
33
33
  const value = args[0];
34
34
 
35
35
  if (!sourceContext || !value || !isString(value)) return;
@@ -18,7 +18,7 @@
18
18
  const { isString } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- module.exports = function(core) {
21
+ module.exports = function (core) {
22
22
  const {
23
23
  logger,
24
24
  scopes: { instrumentation },
@@ -39,7 +39,7 @@ module.exports = function(core) {
39
39
  pre: ({ args, hooked, orig }) => {
40
40
  if (instrumentation.isLocked()) return;
41
41
 
42
- const sourceContext = protect.getSourceContext('eval');
42
+ const sourceContext = protect.getSourceContext();
43
43
  const value = args[0];
44
44
 
45
45
  if (!sourceContext || !value || !isString(value)) return;
@@ -34,7 +34,7 @@ module.exports = function init(core) {
34
34
  if (core.scopes.instrumentation.isLocked()) return;
35
35
 
36
36
  // obtain the Protect sourceContext
37
- const sourceContext = core.protect.getSourceContext('fs');
37
+ const sourceContext = core.protect.getSourceContext();
38
38
 
39
39
  if (!sourceContext) return;
40
40
 
@@ -18,7 +18,7 @@
18
18
  const { isString } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- module.exports = function(core) {
21
+ module.exports = function (core) {
22
22
  const {
23
23
  logger,
24
24
  scopes: { instrumentation },
@@ -40,7 +40,7 @@ module.exports = function(core) {
40
40
  pre: ({ args, hooked, orig }) => {
41
41
  if (instrumentation.isLocked()) return;
42
42
 
43
- const sourceContext = protect.getSourceContext('Function');
43
+ const sourceContext = protect.getSourceContext();
44
44
  const fnBody = args[args.length - 1];
45
45
 
46
46
  if (!sourceContext || !fnBody || !isString(fnBody)) return;
@@ -16,7 +16,7 @@
16
16
  'use strict';
17
17
  const { patchType } = require('../constants');
18
18
 
19
- module.exports = function (core) {
19
+ module.exports = function(core) {
20
20
  const {
21
21
  depHooks,
22
22
  patcher,
@@ -32,11 +32,11 @@ module.exports = function (core) {
32
32
  }
33
33
 
34
34
  if (Object.prototype.toString.call(query) === '[object String]') {
35
- return query.toString();
35
+ return query;
36
36
  }
37
37
 
38
38
  if (query['$where']) {
39
- return query['$where'].toString();
39
+ return query['$where'];
40
40
  }
41
41
 
42
42
  return query;
@@ -14,224 +14,219 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
- const moduleName = 'mongodb';
18
- const semver = require('semver');
17
+ const { isNonEmptyObject, isString, traverseValues, traverseKeys } = require('@contrast/common');
19
18
  const { patchType } = require('../constants');
20
19
 
21
- module.exports = function (core) {
20
+ const collectionMethods = [
21
+ 'find',
22
+ 'findOne',
23
+ 'findAndModify',
24
+ 'findOneAndDelete',
25
+ 'findOneAndReplace',
26
+ 'findOneAndUpdate',
27
+ 'remove',
28
+ 'replaceOne',
29
+ 'update',
30
+ 'updateOne',
31
+ 'updateMany',
32
+ 'deleteOne',
33
+ 'deleteMany',
34
+ 'aggregate',
35
+ 'mapReduce',
36
+ 'group'
37
+ ];
38
+ const dbMethods = [
39
+ 'command',
40
+ 'eval',
41
+ 'aggregate'
42
+ ];
43
+ // const methodsWithNestedCalls = ['findOne', 'eval', 'group'];
44
+
45
+ module.exports = function(core) {
22
46
  const {
47
+ logger,
48
+ patcher,
49
+ depHooks,
50
+ scopes: { instrumentation },
23
51
  protect: { getSourceContext, inputTracing },
24
- instrumentation: { instrument }
25
52
  } = core;
26
53
 
27
- function getCursorQueryData(args, version) {
28
- const query = semver.gte(version, '3.3.0')
29
- ? args[0]?.cmd?.query || args[1]?.query
30
- : args[1]?.query;
54
+ const instr = inputTracing.mongodbInstrumentation = {};
31
55
 
32
- if (!query) {
33
- return;
34
- }
56
+ instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(value, _argIdx, sourceContext, metadata) {
57
+ if (!isString(value) && !isNonEmptyObject(value)) return;
35
58
 
36
- if (Object.prototype.toString.call(query) === '[object String]') {
37
- return query.toString();
38
- }
59
+ const { name, hooked, orig } = metadata;
60
+ const sinkContext = {
61
+ name,
62
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
63
+ };
39
64
 
40
- if (query['$where']) {
41
- return query['$where'].toString();
65
+ if (name === 'mongodb.Db.prototype.command' && value?.filter) {
66
+ value = value.filter;
42
67
  }
43
68
 
44
- return query;
45
- }
69
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value });
70
+ };
71
+
72
+ instr.getAggregateVulnerabilityInfo = function getAggregateVulnerabilityInfo(aggregation, _argIdx, sourceContext, metadata) {
73
+ if (!isNonEmptyObject(aggregation)) return;
46
74
 
47
- function getOpQueryData(op) {
48
- if (!op.q) {
75
+ const { name, hooked, orig } = metadata;
76
+ const sinkContext = {
77
+ name,
78
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
79
+ };
80
+
81
+ traverseValues(aggregation, (path, _type, value) => {
82
+ const accumulatorFuncProps = [
83
+ 'init',
84
+ 'merge',
85
+ 'accumulate',
86
+ 'finalize'
87
+ ];
88
+ const lastIdx = path.length - 1;
89
+
90
+ if (
91
+ (path[lastIdx - 1] === '$function' && path[lastIdx] === 'body') ||
92
+ (path[lastIdx - 1] === '$accumulator' && accumulatorFuncProps.includes(path[lastIdx]))
93
+ ) {
94
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value });
95
+ }
96
+ });
97
+ };
98
+
99
+ instr.getMapReduceVulnerabilityInfo = function getMapReduceVulnerabilityInfo(argToCheck, argIdx, sourceContext, metadata) {
100
+ const { name, hooked, orig } = metadata;
101
+ const sinkContext = {
102
+ name,
103
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
104
+ };
105
+
106
+ if (argIdx !== 2 && isString(argToCheck)) {
107
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
49
108
  return;
50
109
  }
51
- if (op.q.$where) {
52
- return op.q.$where;
53
- }
54
- return op.q;
55
- }
56
110
 
57
- function preHook(value, { name, hooked, orig }) {
58
- const sourceContext = getSourceContext(name);
111
+ if (!isNonEmptyObject(argToCheck)) return;
59
112
 
60
- if (!sourceContext || !value) return;
113
+ traverseKeys(argToCheck, (_path, _type, key) => {
114
+ const vulnerableProps = [
115
+ 'query',
116
+ 'finalize',
117
+ ];
118
+ if (vulnerableProps.includes(key)) {
119
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck[key] });
120
+ }
121
+ });
122
+ };
61
123
 
124
+ instr.getGroupVulnerabilityInfo = function getGroupVulnerabilityInfo(argToCheck, argIdx, sourceContext, metadata) {
125
+ const { name, hooked, orig } = metadata;
62
126
  const sinkContext = {
63
127
  name,
64
- value,
65
128
  stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
66
129
  };
67
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
68
- }
69
130
 
70
- function CollectionVal({ args, ...ctx }) {
71
- const value = typeof args[0] == 'function' ? null : args[0];
72
- preHook(value, ctx);
73
- }
131
+ if (argIdx !== 1 && isString(argToCheck)) {
132
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
133
+ return;
134
+ }
74
135
 
75
- function dbCommandVal({ args, ...ctx }) {
76
- const value = args[0]?.filter;
77
- preHook(value, ctx);
78
- }
136
+ if (!isNonEmptyObject(argToCheck)) return;
79
137
 
80
- function v4CursorVal({ args, ...ctx }) {
81
- const value = args[2];
82
- preHook(value, ctx);
83
- }
138
+ inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
139
+ };
84
140
 
85
- function v3CursorVal({ args, ...ctx }, version) {
86
- const value = getCursorQueryData(args, version);
87
- preHook(value, ctx);
88
- }
141
+ function createAroundHook(getInfoMethod, vulnerableArgIdxs) {
142
+ const argsIdxsToCheck = vulnerableArgIdxs || [0];
143
+ return function(next, data) {
144
+ const { args, name, hooked, orig } = data;
145
+ const sourceCtx = getSourceContext(name);
146
+
147
+ if (instrumentation.isLocked() || !sourceCtx) {
148
+ return next();
149
+ }
150
+
151
+ for (const argIdx of argsIdxsToCheck) {
152
+ getInfoMethod(args[argIdx], argIdx, sourceCtx, { name, hooked, orig });
153
+ }
89
154
 
90
- function firstArgVal({ args, ...ctx }) {
91
- const value = args[0];
92
- preHook(value, ctx);
155
+ return next();
156
+ // return methodsWithNestedCalls.includes(method) ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
157
+ };
93
158
  }
94
159
 
95
- function v3TopologyVal({ args, ...ctx }) {
96
- const ops = Array.isArray(args[1]) ? args[1] : [args[1]];
97
- for (const op of ops) {
98
- const value = op && getOpQueryData(op);
99
- if (value) {
100
- preHook(value, ctx);
160
+ instr.install = function() {
161
+ depHooks.resolve({ name: 'mongodb' }, (mongodb, version) => {
162
+ patchCollection(mongodb, version);
163
+ patchDatabase(mongodb, version);
164
+ });
165
+ };
166
+
167
+ function patchCollection(mongodb, version) {
168
+ const proto = mongodb.Collection.prototype;
169
+ for (const method of collectionMethods) {
170
+ const name = `mongodb.Collection.prototype.${method}`;
171
+
172
+ if (!proto[method]) {
173
+ logger.trace({ name, version }, 'method not found - skipping instrumentation');
174
+ continue;
175
+ }
176
+
177
+ if (method === 'aggregate') {
178
+ patcher.patch(proto, method, {
179
+ name,
180
+ patchType,
181
+ around: createAroundHook(instr.getAggregateVulnerabilityInfo)
182
+ });
183
+ } else if (method === 'mapReduce') {
184
+ patcher.patch(proto, method, {
185
+ name,
186
+ patchType,
187
+ around: createAroundHook(instr.getMapReduceVulnerabilityInfo, [0, 1, 2])
188
+ });
189
+ } else if (method === 'group') {
190
+ patcher.patch(proto, method, {
191
+ name,
192
+ patchType,
193
+ around: createAroundHook(instr.getGroupVulnerabilityInfo, [0, 1, 3])
194
+ });
195
+ } else {
196
+ patcher.patch(proto, method, {
197
+ name,
198
+ patchType,
199
+ around: createAroundHook(instr.getQueryVulnerabilityInfo)
200
+ });
101
201
  }
102
202
  }
103
203
  }
104
204
 
105
- function install() {
106
- [
107
- {
108
- moduleName,
109
- version: '>=2.0.0 <3.0.0 || >=4.0.0',
110
- patchObjects: [
111
- {
112
- name: 'Collection.prototype',
113
- methods: [
114
- 'update',
115
- 'updateOne',
116
- 'replaceOne',
117
- 'updateMany',
118
- 'remove',
119
- 'deleteOne',
120
- 'deleteMany',
121
- 'findAndModify',
122
- 'findOneAndDelete',
123
- 'findOneAndReplace',
124
- 'findOneAndUpdate',
125
- 'countDocuments',
126
- 'count',
127
- 'distinct',
128
- 'aggregate'
129
- ],
130
- patchType,
131
- preHookFn: CollectionVal
132
- },
133
- {
134
- name: 'Db.prototype',
135
- methods: ['command'],
136
- patchType,
137
- preHookFn: dbCommandVal
138
- }
139
- ]
140
- },
141
- {
142
- moduleName,
143
- version: '>=4.0.0',
144
- file: 'lib/cursor/find_cursor',
145
- patchObjects: [
146
- {
147
- methods: ['FindCursor'],
148
- patchType,
149
- preHookFn: v4CursorVal
150
- }
151
- ]
152
- },
153
- {
154
- moduleName,
155
- version: '<4.0.0',
156
- patchObjects: [
157
- {
158
- name: 'CoreServer.prototype',
159
- methods: ['cursor'],
160
- patchType,
161
- preHookFn: v3CursorVal
162
- },
163
- {
164
- name: 'Db.prototype',
165
- methods: ['command'],
166
- patchType,
167
- preHookFn: dbCommandVal
168
- },
169
- {
170
- name: 'Db.prototype',
171
- methods: ['eval'],
172
- patchType,
173
- preHookFn: firstArgVal
174
- },
175
- {
176
- name: 'Collection.prototype',
177
- methods: ['find'],
178
- patchType,
179
- preHookFn: firstArgVal
180
- },
181
- ]
182
- },
183
- {
184
- moduleName,
185
- file: 'lib/topologies/topology_base.js',
186
- version: '<4.0.0 >=3.0.0',
187
- patchObjects: [
188
- {
189
- name: 'TopologyBase.prototype',
190
- methods: ['update', 'remove'],
191
- patchType,
192
- preHookFn: v3TopologyVal
193
- },
194
- {
195
- name: 'TopologyBase.prototype',
196
- methods: ['command'],
197
- patchType,
198
- preHookFn: v3CursorVal
199
- }
200
- ]
201
- },
202
- {
203
- moduleName,
204
- file: 'lib/topologies/native_topology.js',
205
- version: '<4.0.0 >=3.0.0',
206
- patchObjects: [
207
- {
208
- name: 'NativeTopology.prototype',
209
- patchName: 'prototype',
210
- methods: ['update', 'remove'],
211
- patchType,
212
- preHookFn: v3TopologyVal
213
- },
214
- {
215
- name: 'NativeTopology.prototype',
216
- patchName: 'prototype',
217
- methods: ['command'],
218
- patchType,
219
- preHookFn: v3CursorVal
220
- }
221
- ]
222
- },
223
- ].forEach(({ moduleName, file, version, patchObjects }) => {
224
- instrument({
225
- moduleName,
226
- file,
227
- version,
228
- patchObjects
229
- });
230
- });
205
+ function patchDatabase(mongodb, version) {
206
+ const proto = mongodb.Db.prototype;
207
+ for (const method of dbMethods) {
208
+ const name = `mongodb.Db.prototype.${method}`;
209
+
210
+ if (!proto[method]) {
211
+ logger.trace({ name, version }, 'method not found - skipping instrumentation');
212
+ continue;
213
+ }
214
+
215
+ if (method === 'aggregate') {
216
+ patcher.patch(proto, method, {
217
+ name,
218
+ patchType,
219
+ around: createAroundHook(instr.getAggregateVulnerabilityInfo)
220
+ });
221
+ } else {
222
+ patcher.patch(proto, method, {
223
+ name,
224
+ patchType,
225
+ around: createAroundHook(instr.getQueryVulnerabilityInfo)
226
+ });
227
+ }
228
+ }
231
229
  }
232
- const mongodbInstr = (core.protect.inputTracing.mongodbInstrumentation = {
233
- install,
234
- });
235
230
 
236
- return mongodbInstr;
231
+ return instr;
237
232
  };
package/lib/policy.js CHANGED
@@ -346,6 +346,8 @@ module.exports = function (core) {
346
346
  }
347
347
 
348
348
  messages.on(SERVER_SETTINGS_UPDATE, (msg) => {
349
+ if (!config.getEffectiveValue('protect.enable')) return;
350
+
349
351
  updateExclusions(msg);
350
352
  updateGlobalPolicy(msg);
351
353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.25.1",
3
+ "version": "1.27.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^7.0.1",
21
21
  "@contrast/common": "1.15.1",
22
- "@contrast/core": "1.23.1",
23
- "@contrast/esm-hooks": "1.19.1",
22
+ "@contrast/core": "1.25.0",
23
+ "@contrast/esm-hooks": "1.21.0",
24
24
  "@contrast/scopes": "1.4.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"