@contrast/agent 4.5.1 → 4.7.1

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 (209) 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/lib/assess/membrane/deserialization-membrane.js +4 -5
  6. package/lib/assess/membrane/source-membrane.js +16 -33
  7. package/lib/assess/models/call-context.js +1 -1
  8. package/lib/assess/policy/propagators.json +19 -21
  9. package/lib/assess/policy/rules.json +7 -2
  10. package/lib/assess/policy/signatures.json +42 -0
  11. package/lib/assess/policy/util.js +2 -1
  12. package/lib/assess/propagators/JSON/parse.js +1 -1
  13. package/lib/assess/propagators/JSON/stringify.js +3 -3
  14. package/lib/assess/propagators/array-prototype-join.js +7 -8
  15. package/lib/assess/propagators/common.js +7 -5
  16. package/lib/assess/propagators/dustjs/escape-html.js +22 -0
  17. package/lib/assess/propagators/dustjs/escape-js.js +22 -0
  18. package/lib/assess/propagators/encode-uri/encode-uri-component.js +22 -0
  19. package/lib/assess/propagators/encode-uri/encode-uri.js +22 -0
  20. package/lib/assess/propagators/handlebars-escape-expresssion.js +1 -1
  21. package/lib/assess/propagators/index.js +0 -2
  22. package/lib/assess/propagators/joi/boolean.js +1 -1
  23. package/lib/assess/propagators/joi/expression.js +1 -1
  24. package/lib/assess/propagators/joi/number.js +1 -1
  25. package/lib/assess/propagators/joi/string-base.js +1 -1
  26. package/lib/assess/propagators/joi/string-schema.js +12 -13
  27. package/lib/assess/propagators/joi/values.js +37 -22
  28. package/lib/assess/propagators/manager.js +12 -10
  29. package/lib/assess/propagators/mongoose/helpers.js +20 -0
  30. package/lib/assess/propagators/mongoose/index.js +18 -0
  31. package/lib/assess/propagators/mongoose/map.js +74 -0
  32. package/lib/assess/propagators/mongoose/string.js +104 -0
  33. package/lib/assess/propagators/mustache/escape.js +22 -0
  34. package/lib/assess/propagators/number.js +54 -0
  35. package/lib/assess/propagators/object.js +6 -7
  36. package/lib/assess/propagators/path/basename.js +14 -13
  37. package/lib/assess/propagators/path/common.js +156 -47
  38. package/lib/assess/propagators/path/dirname.js +14 -13
  39. package/lib/assess/propagators/path/extname.js +14 -13
  40. package/lib/assess/propagators/path/join.js +5 -1
  41. package/lib/assess/propagators/path/normalize.js +1 -2
  42. package/lib/assess/propagators/path/parse.js +1 -1
  43. package/lib/assess/propagators/path/relative.js +7 -5
  44. package/lib/assess/propagators/path/resolve.js +11 -2
  45. package/lib/assess/propagators/querystring/escape.js +20 -18
  46. package/lib/assess/propagators/querystring/parse.js +7 -5
  47. package/lib/assess/propagators/querystring/stringify.js +25 -24
  48. package/lib/assess/propagators/querystring/unescape.js +20 -18
  49. package/lib/assess/propagators/sequelize/sql-string-escape.js +1 -1
  50. package/lib/assess/propagators/sequelize/sql-string-format-named-parameters.js +1 -1
  51. package/lib/assess/propagators/sequelize/sql-string-format.js +3 -3
  52. package/lib/assess/propagators/sequelize/utils.js +2 -2
  53. package/lib/assess/propagators/string-prototype-replace.js +30 -28
  54. package/lib/assess/propagators/string-prototype-split.js +36 -36
  55. package/lib/assess/propagators/string-prototype-trim.js +15 -17
  56. package/lib/assess/propagators/string.js +12 -16
  57. package/lib/assess/propagators/template-escape.js +87 -0
  58. package/lib/assess/propagators/templates.js +10 -11
  59. package/lib/assess/propagators/url/url-prototype-parse.js +5 -6
  60. package/lib/assess/propagators/url/url-url.js +51 -43
  61. package/lib/assess/propagators/util/format.js +1 -1
  62. package/lib/assess/propagators/v8/init-hooks.js +3 -3
  63. package/lib/assess/propagators/validator/init-hooks.js +22 -22
  64. package/lib/assess/sinks/common.js +10 -5
  65. package/lib/assess/sinks/dustjs-linkedin-xss.js +131 -0
  66. package/lib/assess/sinks/libxmljs-xxe.js +1 -1
  67. package/lib/assess/sinks/mongodb.js +2 -1
  68. package/lib/assess/sinks/ssrf-url.js +1 -1
  69. package/lib/constants.js +4 -1
  70. package/lib/core/arch-components/dynamodb.js +1 -2
  71. package/lib/core/arch-components/dynamodbv3.js +44 -0
  72. package/lib/core/arch-components/index.js +1 -0
  73. package/lib/core/arch-components/rethinkdb.js +53 -0
  74. package/lib/core/config/options.js +3 -2
  75. package/lib/core/rewrite/injections.js +8 -0
  76. package/lib/core/stacktrace.js +2 -1
  77. package/lib/feature-set.js +1 -1
  78. package/lib/hooks/frameworks/base.js +8 -2
  79. package/lib/hooks/frameworks/http.js +23 -16
  80. package/lib/hooks/frameworks/http2.js +73 -0
  81. package/lib/hooks/frameworks/index.js +8 -3
  82. package/lib/hooks/http.js +112 -128
  83. package/lib/hooks/object-to-primitive.js +6 -7
  84. package/lib/hooks/patcher.js +75 -44
  85. package/lib/hooks/require.js +16 -22
  86. package/lib/instrumentation.js +0 -3
  87. package/lib/protect/rules/nosqli/nosql-injection-rule.js +228 -0
  88. package/lib/protect/rules/rule-factory.js +2 -2
  89. package/lib/protect/service.js +23 -11
  90. package/lib/protect/sinks/mongodb.js +56 -55
  91. package/lib/reporter/translations/to-protobuf/dtm/index.js +1 -1
  92. package/lib/reporter/translations/to-protobuf/dtm/ip-denylist-details.js +1 -1
  93. package/lib/reporter/translations/to-protobuf/dtm/rasp-rule-sample.js +1 -1
  94. package/lib/reporter/translations/to-protobuf/settings/defend-features.js +8 -6
  95. package/lib/reporter/translations/to-protobuf/settings/exclusions.js +5 -4
  96. package/lib/tracker.js +13 -65
  97. package/lib/util/some.js +27 -0
  98. package/lib/util/source-map.js +1 -1
  99. package/package.json +15 -16
  100. package/lib/hooks/frameworks/https.js +0 -42
  101. package/lib/protect/rules/nosqli/no-sql-injection-rule.js +0 -109
  102. package/node_modules/bindings/LICENSE.md +0 -22
  103. package/node_modules/bindings/README.md +0 -98
  104. package/node_modules/bindings/bindings.js +0 -221
  105. package/node_modules/bindings/package.json +0 -32
  106. package/node_modules/file-uri-to-path/.npmignore +0 -1
  107. package/node_modules/file-uri-to-path/.travis.yml +0 -30
  108. package/node_modules/file-uri-to-path/History.md +0 -21
  109. package/node_modules/file-uri-to-path/LICENSE +0 -20
  110. package/node_modules/file-uri-to-path/README.md +0 -74
  111. package/node_modules/file-uri-to-path/index.d.ts +0 -2
  112. package/node_modules/file-uri-to-path/index.js +0 -66
  113. package/node_modules/file-uri-to-path/package.json +0 -36
  114. package/node_modules/file-uri-to-path/test/test.js +0 -24
  115. package/node_modules/file-uri-to-path/test/tests.json +0 -13
  116. package/node_modules/glossy/LICENSE +0 -19
  117. package/node_modules/glossy/README.md +0 -129
  118. package/node_modules/glossy/index.js +0 -12
  119. package/node_modules/glossy/lib/glossy/parse.js +0 -520
  120. package/node_modules/glossy/lib/glossy/produce.js +0 -459
  121. package/node_modules/glossy/package.json +0 -47
  122. package/node_modules/glossy/test/decide.js +0 -7
  123. package/node_modules/glossy/test/decode_pri.js +0 -24
  124. package/node_modules/glossy/test/parse_3164.js +0 -104
  125. package/node_modules/glossy/test/parse_5424.js +0 -106
  126. package/node_modules/glossy/test/parse_5848.js +0 -40
  127. package/node_modules/glossy/test/parse_8601.js +0 -14
  128. package/node_modules/glossy/test/parse_rfc3339.js +0 -9
  129. package/node_modules/glossy/test/produce.js +0 -162
  130. package/node_modules/glossy/test/runner.js +0 -40
  131. package/node_modules/glossy/test/structure_data.js +0 -24
  132. package/node_modules/nan/CHANGELOG.md +0 -537
  133. package/node_modules/nan/LICENSE.md +0 -13
  134. package/node_modules/nan/README.md +0 -455
  135. package/node_modules/nan/doc/asyncworker.md +0 -146
  136. package/node_modules/nan/doc/buffers.md +0 -54
  137. package/node_modules/nan/doc/callback.md +0 -76
  138. package/node_modules/nan/doc/converters.md +0 -41
  139. package/node_modules/nan/doc/errors.md +0 -226
  140. package/node_modules/nan/doc/json.md +0 -62
  141. package/node_modules/nan/doc/maybe_types.md +0 -583
  142. package/node_modules/nan/doc/methods.md +0 -664
  143. package/node_modules/nan/doc/new.md +0 -147
  144. package/node_modules/nan/doc/node_misc.md +0 -123
  145. package/node_modules/nan/doc/object_wrappers.md +0 -263
  146. package/node_modules/nan/doc/persistent.md +0 -296
  147. package/node_modules/nan/doc/scopes.md +0 -73
  148. package/node_modules/nan/doc/script.md +0 -38
  149. package/node_modules/nan/doc/string_bytes.md +0 -62
  150. package/node_modules/nan/doc/v8_internals.md +0 -199
  151. package/node_modules/nan/doc/v8_misc.md +0 -85
  152. package/node_modules/nan/include_dirs.js +0 -1
  153. package/node_modules/nan/nan.h +0 -2898
  154. package/node_modules/nan/nan_callbacks.h +0 -88
  155. package/node_modules/nan/nan_callbacks_12_inl.h +0 -514
  156. package/node_modules/nan/nan_callbacks_pre_12_inl.h +0 -520
  157. package/node_modules/nan/nan_converters.h +0 -72
  158. package/node_modules/nan/nan_converters_43_inl.h +0 -68
  159. package/node_modules/nan/nan_converters_pre_43_inl.h +0 -42
  160. package/node_modules/nan/nan_define_own_property_helper.h +0 -29
  161. package/node_modules/nan/nan_implementation_12_inl.h +0 -430
  162. package/node_modules/nan/nan_implementation_pre_12_inl.h +0 -263
  163. package/node_modules/nan/nan_json.h +0 -166
  164. package/node_modules/nan/nan_maybe_43_inl.h +0 -356
  165. package/node_modules/nan/nan_maybe_pre_43_inl.h +0 -268
  166. package/node_modules/nan/nan_new.h +0 -340
  167. package/node_modules/nan/nan_object_wrap.h +0 -156
  168. package/node_modules/nan/nan_persistent_12_inl.h +0 -132
  169. package/node_modules/nan/nan_persistent_pre_12_inl.h +0 -242
  170. package/node_modules/nan/nan_private.h +0 -73
  171. package/node_modules/nan/nan_string_bytes.h +0 -305
  172. package/node_modules/nan/nan_typedarray_contents.h +0 -96
  173. package/node_modules/nan/nan_weak.h +0 -437
  174. package/node_modules/nan/package.json +0 -41
  175. package/node_modules/nan/tools/1to2.js +0 -412
  176. package/node_modules/nan/tools/README.md +0 -14
  177. package/node_modules/nan/tools/package.json +0 -19
  178. package/node_modules/unix-dgram/LICENSE +0 -13
  179. package/node_modules/unix-dgram/README.md +0 -107
  180. package/node_modules/unix-dgram/binding.gyp +0 -20
  181. package/node_modules/unix-dgram/build/Makefile +0 -324
  182. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram/src/unix_dgram.o.d +0 -58
  183. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram.node.d +0 -1
  184. package/node_modules/unix-dgram/build/Release/.deps/Release/unix_dgram.node.d +0 -1
  185. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram/src/unix_dgram.o +0 -0
  186. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram.node +0 -0
  187. package/node_modules/unix-dgram/build/Release/unix_dgram.node +0 -0
  188. package/node_modules/unix-dgram/build/binding.Makefile +0 -6
  189. package/node_modules/unix-dgram/build/config.gypi +0 -213
  190. package/node_modules/unix-dgram/build/unix_dgram.target.mk +0 -159
  191. package/node_modules/unix-dgram/lib/unix_dgram.js +0 -168
  192. package/node_modules/unix-dgram/package.json +0 -36
  193. package/node_modules/unix-dgram/src/unix_dgram.cc +0 -404
  194. package/node_modules/unix-dgram/src/win_dummy.cc +0 -7
  195. package/node_modules/unix-dgram/test/test-connect-callback.js +0 -68
  196. package/node_modules/unix-dgram/test/test-connect.js +0 -53
  197. package/node_modules/unix-dgram/test/test-dgram-unix.js +0 -58
  198. package/node_modules/unix-dgram/test/test-send-error.js +0 -26
  199. package/node_modules/winston-syslog/.eslintrc +0 -7
  200. package/node_modules/winston-syslog/.travis.yml +0 -14
  201. package/node_modules/winston-syslog/CHANGELOG.md +0 -9
  202. package/node_modules/winston-syslog/LICENSE +0 -20
  203. package/node_modules/winston-syslog/README.md +0 -135
  204. package/node_modules/winston-syslog/lib/utils.js +0 -26
  205. package/node_modules/winston-syslog/lib/winston-syslog.js +0 -385
  206. package/node_modules/winston-syslog/package.json +0 -56
  207. package/node_modules/winston-syslog/test/format-test.js +0 -122
  208. package/node_modules/winston-syslog/test/syslog-test.js +0 -95
  209. package/node_modules/winston-syslog/test/unix-connect-test.js +0 -133
@@ -36,6 +36,7 @@ const {
36
36
  SCOPE_NAMES: { NO_PROPAGATION }
37
37
  } = require('../core/async-storage/scopes');
38
38
  const promisifyCustom = Symbol.for('nodejs.util.promisify.custom');
39
+ const some = require('../util/some');
39
40
 
40
41
  // When a pre-hook returns the result of this function, the value passed will
41
42
  // be used in place of the original function's return value, and the original
@@ -96,12 +97,7 @@ Function.prototype._contrast_toString = function() {
96
97
  return Reflect.apply(functionToString, this, arguments);
97
98
  };
98
99
 
99
- function runHooks(type, options, data, fn, thisTarget) {
100
- const fnHooks = hooks.get(fn);
101
- if (!fnHooks) {
102
- return;
103
- }
104
-
100
+ function runHooks(type, data, thisTarget, fnHooks) {
105
101
  fnHooks.forEach((hook, key) => {
106
102
  if (hook[type]) {
107
103
  hook[type].apply(thisTarget, [data]);
@@ -109,6 +105,15 @@ function runHooks(type, options, data, fn, thisTarget) {
109
105
  });
110
106
  }
111
107
 
108
+ /**
109
+ * Checks if any of the provided arguments are tracked.
110
+ * @param {any[]} args
111
+ * @return {boolean}
112
+ */
113
+ function argsTracked(args) {
114
+ return some(args, (arg) => arg && tracker.getData(arg));
115
+ }
116
+
112
117
  /**
113
118
  * Whether to skip the running of instrumentation in registered pre/post hooks.
114
119
  * When this returns `false` (don't skip), the pre/post hooks will execute in
@@ -175,11 +180,17 @@ function runOriginalFunction(fn, { args, name }, target) {
175
180
  return fn.apply(this, args);
176
181
  }
177
182
 
178
- function recordTime(func, funcKey, type) {
183
+ const runWrapper =
184
+ !process.hrtime.bigint || !perfLoggingEnabled
185
+ ? runWithoutRecordingTime
186
+ : runAndRecordTime;
187
+
188
+ function runWithoutRecordingTime(func) {
189
+ return () => func();
190
+ }
191
+
192
+ function runAndRecordTime(func, funcKey, type) {
179
193
  return () => {
180
- if (!process.hrtime.bigint || !perfLoggingEnabled) {
181
- return func();
182
- }
183
194
  const start = process.hrtime.bigint();
184
195
  const rv = func();
185
196
  perfLogger[type](funcKey, Number(process.hrtime.bigint() - start));
@@ -207,6 +218,7 @@ function hookFunction(fn, options) {
207
218
  const { funcKey } = options;
208
219
  logger.trace(`hook ${funcKey}`);
209
220
 
221
+ // eslint-disable-next-line complexity
210
222
  function hooked(...args) {
211
223
  const target = new.target;
212
224
 
@@ -220,11 +232,8 @@ function hookFunction(fn, options) {
220
232
  }
221
233
 
222
234
  // short-circuit optimization for add
223
- const [arg1, arg2] = args;
224
- if (fn.name === '__add' && arg1 && arg2) {
225
- if (!tracker.getData(arg1).tracked && !tracker.getData(arg2).tracked) {
226
- return arg1 + arg2;
227
- }
235
+ if (fn.name === '__add' && !argsTracked(args)) {
236
+ return args[0] + args[1];
228
237
  }
229
238
 
230
239
  const data = {
@@ -240,36 +249,59 @@ function hookFunction(fn, options) {
240
249
  perfLogger.logEvent(funcKey);
241
250
  }
242
251
 
243
- // Run the pre event in a no instrumentation scope
252
+ let hasPreHook, hasPostHook;
253
+ const fnHooks = hooks && hooks.get(hooked);
254
+
255
+ // If there's a pre event run it in a no instrumentation scope
244
256
  // this will prevent other instrumentation from running
245
257
  // within the pre function
246
- Scopes.runInNoInstrumentationScope(
247
- recordTime(
248
- () => runHooks('pre', options, data, hooked, this),
249
- funcKey,
250
- 'pre'
251
- ),
252
- `${funcKey} pre`
253
- );
258
+ if (fnHooks) {
259
+ for (const storedHooks of fnHooks.values()) {
260
+ hasPreHook = Boolean(hasPreHook || (storedHooks && storedHooks.pre));
261
+ hasPostHook = Boolean(hasPostHook || (storedHooks && storedHooks.post));
262
+ }
263
+ }
254
264
 
255
- // run original function in scope that was passed in
256
- data.result = Scopes.runIn(
257
- options.scope,
258
- recordTime(() => getResult.call(this, fn, data, target), funcKey, 'orig'),
259
- funcKey
260
- );
265
+ if (hasPreHook) {
266
+ Scopes.runInNoInstrumentationScope(
267
+ runWrapper(() => runHooks('pre', data, this, fnHooks), funcKey, 'pre'),
268
+ `${funcKey} pre`
269
+ );
270
+ }
261
271
 
262
- // Run the post event in a no instrumentation scope
272
+ // Run original function in scope that was passed in
273
+ // or just run the original function when the passed scope is undefined
274
+ if (options.scope) {
275
+ data.result = Scopes.runIn(
276
+ options.scope,
277
+ runWrapper(
278
+ () => getResult.call(this, fn, data, target),
279
+ funcKey,
280
+ 'orig'
281
+ ),
282
+ funcKey
283
+ );
284
+ } else {
285
+ data.result = runWrapper(
286
+ () => getResult.call(this, fn, data, target),
287
+ funcKey,
288
+ 'orig'
289
+ )();
290
+ }
291
+
292
+ // If there's a post event run it in a no instrumentation scope
263
293
  // this will prevent other instrumentation from running
264
294
  // within the post function
265
- Scopes.runInNoInstrumentationScope(
266
- recordTime(
267
- () => runHooks('post', options, data, hooked, this),
268
- funcKey,
269
- 'post'
270
- ),
271
- `${funcKey} post`
272
- );
295
+ if (hasPostHook) {
296
+ Scopes.runInNoInstrumentationScope(
297
+ runWrapper(
298
+ () => runHooks('post', data, this, fnHooks),
299
+ funcKey,
300
+ 'post'
301
+ ),
302
+ `${funcKey} post`
303
+ );
304
+ }
273
305
 
274
306
  return data.result;
275
307
  }
@@ -321,7 +353,8 @@ function hookableMethods(obj, props, callback) {
321
353
  }
322
354
 
323
355
  // Execute callback for every method in the props array.
324
- for (let i = props.length; i >= 0; i--) {
356
+ let i = props.length;
357
+ while (i--) {
325
358
  // If the property isn't a function...
326
359
  if (
327
360
  !isFunction(obj[props[i]]) ||
@@ -428,8 +461,7 @@ function hook(obj, prop, opts) {
428
461
  }
429
462
  } catch (err) {
430
463
  logger.info(
431
- `unable to patch unconfigurable property ${opts.name}:${prop}
432
- `,
464
+ `unable to patch unconfigurable property ${opts.name}:${prop}`,
433
465
  err
434
466
  );
435
467
  }
@@ -472,7 +504,7 @@ function patch(obj, props, options) {
472
504
  }
473
505
 
474
506
  const hookedMethods = hookableMethods(obj, props).map(function(prop) {
475
- const opts = Object.assign({}, options);
507
+ const opts = typeof options === 'object' ? { ...options } : options;
476
508
 
477
509
  // staticName means do not append methods to final name
478
510
  // only used for Function(aka global.ContrastFunction)
@@ -535,7 +567,6 @@ function unwrap(fn) {
535
567
  module.exports = {
536
568
  patch,
537
569
  preempt,
538
- recordTime,
539
570
  resetInstrumentation,
540
571
  unpatch,
541
572
  unwrap
@@ -14,44 +14,38 @@ Copyright: 2021 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
- /**
18
- * @module lib/hooks/moduleHook
19
- * @class ModuleHook
20
- */
21
-
22
17
  const Module = require('module');
23
- const agentEmitter = require('../agent-emitter');
24
18
  const RequireHook = require('@contrast/require-hook');
19
+ const agentEmitter = require('../agent-emitter');
25
20
  const logger = require('../core/logger')('hooks:require');
26
21
 
27
22
  class ModuleHook {
28
- constructor(options) {
23
+ constructor() {
29
24
  this.reqHook = new RequireHook(logger);
30
25
 
31
- // RequireHook takes care of hooking
32
- // but still need to patch require to emit 'require' events
33
- const origModRequire = Module.prototype.require;
34
- Module.prototype.require = function(name) {
35
- agentEmitter.emit('require', name);
36
- return origModRequire.call(this, name);
26
+ // RequireHook takes care of hooking but we still need to patch require to
27
+ // emit 'require' events
28
+ const origModLoad = Module._load;
29
+ Module._load = function __loadAndEmit(...args) {
30
+ const [request] = args;
31
+ agentEmitter.emit('require', request);
32
+ return Reflect.apply(origModLoad, this, args);
37
33
  };
38
34
  }
39
35
 
40
36
  /**
41
- *
42
37
  * Collects instrumentation functions that will run on the specified
43
- * module's file at the time it is loaded via <code>require</code>.
44
- * @param {Object} descriptor describes module you're hook(name, version, file)
45
- * @param {Function} instrumentation The function to run on the module at
46
- * the time it is required in the code
38
+ * module's file at the time it is loaded via `require`.
39
+ * @param {RequireHook.Descriptor} descriptor describes the module to hook
40
+ * @param {Function} handler The function to run on the module at the time it is required
47
41
  */
48
- resolve(descriptor, instrumentation) {
49
- this.reqHook.resolve(descriptor, instrumentation);
42
+ resolve(descriptor, handler) {
43
+ this.reqHook.resolve(descriptor, handler);
50
44
  }
51
45
 
52
46
  /**
53
- * reset the hooked state for a module so that it's handlers re-run (used in unit tests)
54
- *
47
+ * Reset the hooked state for a module so that it's handlers re-run.
48
+ * NOTE: used in unit tests.
55
49
  * @param {string} name the name of the module
56
50
  */
57
51
  reset(name) {
@@ -40,9 +40,6 @@ module.exports = function instrument(agent, reporter) {
40
40
  return;
41
41
  }
42
42
 
43
- const stackFactory = require('./core/stacktrace').singleton;
44
- stackFactory.stackTraceLimit = agent.config.agent.stack_trace_limit;
45
-
46
43
  // The order of these matters, do not re-order
47
44
  collectLibInfo(agent);
48
45
  // (CONTRAST-31526) this must be required first to load global.ContrastMethods
@@ -0,0 +1,228 @@
1
+ /**
2
+ Copyright: 2021 Contrast Security, Inc
3
+ Contact: support@contrastsecurity.com
4
+ License: Commercial
5
+
6
+ NOTICE: This Software and the patented inventions embodied within may only be
7
+ used as part of Contrast Security’s commercial offerings. Even though it is
8
+ made available through public repositories, use of this Software is subject to
9
+ the applicable End User Licensing Agreement found at
10
+ https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ between Contrast Security and the End User. The Software may not be reverse
12
+ engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ way not consistent with the End User License Agreement.
14
+ */
15
+ 'use strict';
16
+ /* eslint-disable complexity */
17
+ const _ = require('lodash');
18
+
19
+ const logger = require('../../../core/logger')('contrast:rules:protect');
20
+ const { INPUT_TYPES, SINK_TYPES } = require('../common');
21
+ const AsyncStorage = require('../../../core/async-storage');
22
+ const constants = require('../../../constants');
23
+ const { traverse } = require('../../../util/traverse');
24
+
25
+ const brackets = /\[(.{1,1024}?)\]/;
26
+ const child = /\[(.{1,1024}?)\]/g;
27
+
28
+ const MONGODB = 'mongodb';
29
+
30
+ const ScannerKit = new Map([
31
+ [MONGODB, () => require('../nosqli/nosql-scanner').create('MongoDB')]
32
+ ]);
33
+ const SubstringFinder = require('../base-scanner/substring-finder');
34
+
35
+ class NoSqlInjectionRule extends require('../') {
36
+ constructor(policy = {}) {
37
+ policy.inputParseDepth = 3;
38
+ super(policy);
39
+
40
+ this._scanners = new Map();
41
+
42
+ this.id = 'nosql-injection';
43
+ this.name = 'NoSQL Injection';
44
+ this.applicableInputs = [
45
+ INPUT_TYPES.BODY,
46
+ INPUT_TYPES.JSON_VALUE,
47
+ INPUT_TYPES.JSON_ARRAYED_VALUE,
48
+ INPUT_TYPES.PARAMETER_NAME,
49
+ INPUT_TYPES.PARAMETER_VALUE,
50
+ INPUT_TYPES.QUERYSTRING,
51
+ INPUT_TYPES.XML_VALUE,
52
+ INPUT_TYPES.URI,
53
+ INPUT_TYPES.URL_PARAMETER
54
+ ];
55
+ this.applicableSinks = [SINK_TYPES.NOSQL_QUERY];
56
+ }
57
+
58
+ evaluateAtSink({ event, applicableSamples }) {
59
+ if (_.isEmpty(applicableSamples) || !event.data) {
60
+ return;
61
+ }
62
+
63
+ if (typeof event.data === 'object') {
64
+ for (const sample of applicableSamples) {
65
+ const requestData = this.getRequestData(sample.input);
66
+ if (!requestData) {
67
+ return;
68
+ }
69
+ let found;
70
+ traverse(event.data, (key, value) => {
71
+ if (found) {
72
+ return;
73
+ }
74
+
75
+ Object.keys(requestData).some((reqKey) => {
76
+ if (value[reqKey] === requestData[reqKey]) {
77
+ found = reqKey;
78
+ return true;
79
+ }
80
+ });
81
+ });
82
+
83
+ if (found) {
84
+ const query = require('util').inspect(event.data, false, null);
85
+
86
+ let injection;
87
+ for (const location of new SubstringFinder(query, found)) {
88
+ if (location) {
89
+ injection = {
90
+ input: found,
91
+ location,
92
+ query
93
+ };
94
+ break;
95
+ }
96
+ }
97
+ if (injection) {
98
+ this.appendAttackDetails(sample, injection);
99
+ sample.captureAppContext(event);
100
+ logger.warn(`EFFECTIVE - rule: ${this.id}, mode: ${this.mode}`);
101
+ this.blockRequest(sample);
102
+ }
103
+ }
104
+ }
105
+ } else if (typeof event.data === 'string' || event.data instanceof String) {
106
+ const scanner = this.getScanner(event.id);
107
+
108
+ for (const sample of applicableSamples) {
109
+ const injection = scanner.findInjection(sample.input.value, event.data);
110
+
111
+ if (injection) {
112
+ this.appendAttackDetails(sample, injection);
113
+ sample.captureAppContext(event);
114
+ logger.warn(`EFFECTIVE - rule: ${this.id}, mode: ${this.mode} `);
115
+ this.blockRequest(sample);
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Given the sample's user input object, will read the value from the request
123
+ * from the async storage context.
124
+ * @param {string} _documentPath
125
+ * @param {string} _documentType
126
+ */
127
+ getRequestData({ _documentPath, _documentType }) {
128
+ const ctx = AsyncStorage.getContext();
129
+
130
+ if (!ctx) {
131
+ logger.info(
132
+ 'unable to perform nosql-expansion sink analysis: async storage context not available'
133
+ );
134
+ return;
135
+ }
136
+
137
+ const pathArray = [];
138
+
139
+ if (!_documentPath) {
140
+ return pathArray;
141
+ }
142
+
143
+ if (
144
+ _documentType === constants.INPUT_TYPES.PARAMETER_NAME &&
145
+ _documentPath.length <= 1024
146
+ ) {
147
+ let segment = brackets.exec(_documentPath);
148
+ const parent = segment
149
+ ? _documentPath.slice(0, segment.index)
150
+ : _documentPath;
151
+
152
+ pathArray.push('query');
153
+
154
+ if (parent) {
155
+ pathArray.push(parent);
156
+ }
157
+ while ((segment = child.exec(_documentPath))) {
158
+ pathArray.push(segment[1]);
159
+ }
160
+ pathArray.pop();
161
+ } else {
162
+ // only qs params and body are "expandable"
163
+ pathArray.push('body', ..._documentPath.split('.'));
164
+ }
165
+
166
+ let data;
167
+ let curr = ctx.req;
168
+
169
+ for (const path of pathArray) {
170
+ if (curr) {
171
+ data = curr[path];
172
+ curr = data;
173
+ }
174
+ }
175
+ return data;
176
+ }
177
+
178
+ getScanner(id) {
179
+ if (!ScannerKit.has(id)) {
180
+ throw new Error(`Unknown NoSQL scanner: ${id}`);
181
+ }
182
+
183
+ if (!this._scanners.has(id)) {
184
+ this._scanners.set(id, ScannerKit.get(id)());
185
+ }
186
+
187
+ return this._scanners.get(id);
188
+ }
189
+
190
+ /**
191
+ * Builds details for NoSQL Injection Attack.
192
+ * @param {UserInput} inputDtm The user input that resulted in attack
193
+ * @param {String} query The query that was analyzed
194
+ * @param {Object} results The repsults of the sql-scanner
195
+ * @returns {Object} The details
196
+ */
197
+ buildDetails(sample, findings) {
198
+ if (!findings) {
199
+ return null;
200
+ }
201
+
202
+ const { boundary, location, query } = findings;
203
+ let inputBoundaryIndex, boundaryOverrunIndex;
204
+ const start = location[0];
205
+ const end = location[1] + 1;
206
+
207
+ if (boundary) {
208
+ inputBoundaryIndex = boundary.previous
209
+ ? boundary.previous.start
210
+ : boundary.start;
211
+ boundaryOverrunIndex = boundary.stop + 1;
212
+ } else {
213
+ inputBoundaryIndex = start;
214
+ boundaryOverrunIndex = end;
215
+ }
216
+
217
+ return {
218
+ start,
219
+ end,
220
+ input: sample.input.toSerializable(),
221
+ boundaryOverrunIndex,
222
+ inputBoundaryIndex,
223
+ query
224
+ };
225
+ }
226
+ }
227
+
228
+ module.exports = NoSqlInjectionRule;
@@ -34,7 +34,7 @@ const ctors = {
34
34
  [RULES.CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS]: require('./cmd-injection-semantic-chained-commands/cmd-injection-semantic-chained-commands-rule'),
35
35
  [RULES.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS]: require('./cmd-injection-semantic-dangerous-paths/cmd-injection-semantic-dangerous-paths-rule.js'),
36
36
  [RULES.METHOD_TAMPERING]: require('./method-tampering/method-tampering-rule'),
37
- [RULES.NOSQL_INJECTION]: require('./nosqli/no-sql-injection-rule'),
37
+ [RULES.NOSQL_INJECTION]: require('./nosqli/nosql-injection-rule'),
38
38
  [RULES.PATH_TRAVERSAL]: require('./path-traversal/path-traversal-rule'),
39
39
  [RULES.REFLECTED_XSS]: require('./xss/reflected-xss-rule'),
40
40
  [RULES.SQL_INJECTION]: require('./sqli/sql-injection-rule'),
@@ -109,7 +109,7 @@ class ProtectRuleFactory {
109
109
  this.signatures = new SignatureKit(definitionList);
110
110
  }
111
111
 
112
- const denylist = _.get(settings, 'defend.ipBlacklistsList');
112
+ const denylist = _.get(settings, 'defend.ipDenylistsList');
113
113
  if (denylist) {
114
114
  logger.info(`IP Denylist updated. Total: ${denylist.length}`);
115
115
  }
@@ -20,7 +20,12 @@ Copyright: 2021 Contrast Security, Inc
20
20
 
21
21
  const _ = require('lodash');
22
22
 
23
- const { IMPORTANCE, SAFE_HEADER_VALUES, INPUT_TYPES } = require('../constants');
23
+ const {
24
+ IMPORTANCE,
25
+ SAFE_HEADER_VALUES,
26
+ INPUT_TYPES,
27
+ RULES
28
+ } = require('../constants');
24
29
  const agentEmitter = require('../agent-emitter');
25
30
  const SampleAggregator = require('./sample-aggregator');
26
31
  const RuleFactory = require('./rules/rule-factory');
@@ -203,15 +208,22 @@ class ProtectService {
203
208
  appContext.request = request;
204
209
  }
205
210
 
206
- for (const {
207
- scoreLevel,
208
- ruleId,
209
- inputType: type,
210
- path,
211
- key: name,
212
- value,
213
- idsList
214
- } of resultsList) {
211
+ for (const result of resultsList) {
212
+ // Coerce custom rule id
213
+ if (result.ruleId === RULES.NOSQL_EXPANSION) {
214
+ result.ruleId = RULES.NOSQL_INJECTION;
215
+ }
216
+
217
+ const {
218
+ scoreLevel,
219
+ ruleId,
220
+ inputType: type,
221
+ path,
222
+ key: name,
223
+ value,
224
+ idsList
225
+ } = result;
226
+
215
227
  if (scoreLevel === IMPORTANCE.NONE) {
216
228
  return;
217
229
  }
@@ -269,7 +281,7 @@ class ProtectService {
269
281
  * Loads IP analyzer for allowist analysis given TS settings.
270
282
  */
271
283
  updateIpAllowlist(settings) {
272
- const list = _.get(settings, 'defend.ipWhitelistsList');
284
+ const list = _.get(settings, 'defend.ipAllowlistsList');
273
285
  if (list && list.length) {
274
286
  this.ipAllowlist = new IpAnalyzer(list);
275
287
  this.ipAllowlist.on('expired', (dtm) => {