@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.
- package/bin/VERSION +1 -1
- package/bin/linux/contrast-service +0 -0
- package/bin/mac/contrast-service +0 -0
- package/bin/windows/contrast-service.exe +0 -0
- package/lib/assess/membrane/deserialization-membrane.js +4 -5
- package/lib/assess/membrane/source-membrane.js +16 -33
- package/lib/assess/models/call-context.js +1 -1
- package/lib/assess/policy/propagators.json +19 -21
- package/lib/assess/policy/rules.json +7 -2
- package/lib/assess/policy/signatures.json +42 -0
- package/lib/assess/policy/util.js +2 -1
- package/lib/assess/propagators/JSON/parse.js +1 -1
- package/lib/assess/propagators/JSON/stringify.js +3 -3
- package/lib/assess/propagators/array-prototype-join.js +7 -8
- package/lib/assess/propagators/common.js +7 -5
- package/lib/assess/propagators/dustjs/escape-html.js +22 -0
- package/lib/assess/propagators/dustjs/escape-js.js +22 -0
- package/lib/assess/propagators/encode-uri/encode-uri-component.js +22 -0
- package/lib/assess/propagators/encode-uri/encode-uri.js +22 -0
- package/lib/assess/propagators/handlebars-escape-expresssion.js +1 -1
- package/lib/assess/propagators/index.js +0 -2
- package/lib/assess/propagators/joi/boolean.js +1 -1
- package/lib/assess/propagators/joi/expression.js +1 -1
- package/lib/assess/propagators/joi/number.js +1 -1
- package/lib/assess/propagators/joi/string-base.js +1 -1
- package/lib/assess/propagators/joi/string-schema.js +12 -13
- package/lib/assess/propagators/joi/values.js +37 -22
- package/lib/assess/propagators/manager.js +12 -10
- package/lib/assess/propagators/mongoose/helpers.js +20 -0
- package/lib/assess/propagators/mongoose/index.js +18 -0
- package/lib/assess/propagators/mongoose/map.js +74 -0
- package/lib/assess/propagators/mongoose/string.js +104 -0
- package/lib/assess/propagators/mustache/escape.js +22 -0
- package/lib/assess/propagators/number.js +54 -0
- package/lib/assess/propagators/object.js +6 -7
- package/lib/assess/propagators/path/basename.js +14 -13
- package/lib/assess/propagators/path/common.js +156 -47
- package/lib/assess/propagators/path/dirname.js +14 -13
- package/lib/assess/propagators/path/extname.js +14 -13
- package/lib/assess/propagators/path/join.js +5 -1
- package/lib/assess/propagators/path/normalize.js +1 -2
- package/lib/assess/propagators/path/parse.js +1 -1
- package/lib/assess/propagators/path/relative.js +7 -5
- package/lib/assess/propagators/path/resolve.js +11 -2
- package/lib/assess/propagators/querystring/escape.js +20 -18
- package/lib/assess/propagators/querystring/parse.js +7 -5
- package/lib/assess/propagators/querystring/stringify.js +25 -24
- package/lib/assess/propagators/querystring/unescape.js +20 -18
- package/lib/assess/propagators/sequelize/sql-string-escape.js +1 -1
- package/lib/assess/propagators/sequelize/sql-string-format-named-parameters.js +1 -1
- package/lib/assess/propagators/sequelize/sql-string-format.js +3 -3
- package/lib/assess/propagators/sequelize/utils.js +2 -2
- package/lib/assess/propagators/string-prototype-replace.js +30 -28
- package/lib/assess/propagators/string-prototype-split.js +36 -36
- package/lib/assess/propagators/string-prototype-trim.js +15 -17
- package/lib/assess/propagators/string.js +12 -16
- package/lib/assess/propagators/template-escape.js +87 -0
- package/lib/assess/propagators/templates.js +10 -11
- package/lib/assess/propagators/url/url-prototype-parse.js +5 -6
- package/lib/assess/propagators/url/url-url.js +51 -43
- package/lib/assess/propagators/util/format.js +1 -1
- package/lib/assess/propagators/v8/init-hooks.js +3 -3
- package/lib/assess/propagators/validator/init-hooks.js +22 -22
- package/lib/assess/sinks/common.js +10 -5
- package/lib/assess/sinks/dustjs-linkedin-xss.js +131 -0
- package/lib/assess/sinks/libxmljs-xxe.js +1 -1
- package/lib/assess/sinks/mongodb.js +2 -1
- package/lib/assess/sinks/ssrf-url.js +1 -1
- package/lib/constants.js +4 -1
- package/lib/core/arch-components/dynamodb.js +1 -2
- package/lib/core/arch-components/dynamodbv3.js +44 -0
- package/lib/core/arch-components/index.js +1 -0
- package/lib/core/arch-components/rethinkdb.js +53 -0
- package/lib/core/config/options.js +3 -2
- package/lib/core/rewrite/injections.js +8 -0
- package/lib/core/stacktrace.js +2 -1
- package/lib/feature-set.js +1 -1
- package/lib/hooks/frameworks/base.js +8 -2
- package/lib/hooks/frameworks/http.js +23 -16
- package/lib/hooks/frameworks/http2.js +73 -0
- package/lib/hooks/frameworks/index.js +8 -3
- package/lib/hooks/http.js +112 -128
- package/lib/hooks/object-to-primitive.js +6 -7
- package/lib/hooks/patcher.js +75 -44
- package/lib/hooks/require.js +16 -22
- package/lib/instrumentation.js +0 -3
- package/lib/protect/rules/nosqli/nosql-injection-rule.js +228 -0
- package/lib/protect/rules/rule-factory.js +2 -2
- package/lib/protect/service.js +23 -11
- package/lib/protect/sinks/mongodb.js +56 -55
- package/lib/reporter/translations/to-protobuf/dtm/index.js +1 -1
- package/lib/reporter/translations/to-protobuf/dtm/ip-denylist-details.js +1 -1
- package/lib/reporter/translations/to-protobuf/dtm/rasp-rule-sample.js +1 -1
- package/lib/reporter/translations/to-protobuf/settings/defend-features.js +8 -6
- package/lib/reporter/translations/to-protobuf/settings/exclusions.js +5 -4
- package/lib/tracker.js +13 -65
- package/lib/util/some.js +27 -0
- package/lib/util/source-map.js +1 -1
- package/package.json +15 -16
- package/lib/hooks/frameworks/https.js +0 -42
- package/lib/protect/rules/nosqli/no-sql-injection-rule.js +0 -109
- package/node_modules/bindings/LICENSE.md +0 -22
- package/node_modules/bindings/README.md +0 -98
- package/node_modules/bindings/bindings.js +0 -221
- package/node_modules/bindings/package.json +0 -32
- package/node_modules/file-uri-to-path/.npmignore +0 -1
- package/node_modules/file-uri-to-path/.travis.yml +0 -30
- package/node_modules/file-uri-to-path/History.md +0 -21
- package/node_modules/file-uri-to-path/LICENSE +0 -20
- package/node_modules/file-uri-to-path/README.md +0 -74
- package/node_modules/file-uri-to-path/index.d.ts +0 -2
- package/node_modules/file-uri-to-path/index.js +0 -66
- package/node_modules/file-uri-to-path/package.json +0 -36
- package/node_modules/file-uri-to-path/test/test.js +0 -24
- package/node_modules/file-uri-to-path/test/tests.json +0 -13
- package/node_modules/glossy/LICENSE +0 -19
- package/node_modules/glossy/README.md +0 -129
- package/node_modules/glossy/index.js +0 -12
- package/node_modules/glossy/lib/glossy/parse.js +0 -520
- package/node_modules/glossy/lib/glossy/produce.js +0 -459
- package/node_modules/glossy/package.json +0 -47
- package/node_modules/glossy/test/decide.js +0 -7
- package/node_modules/glossy/test/decode_pri.js +0 -24
- package/node_modules/glossy/test/parse_3164.js +0 -104
- package/node_modules/glossy/test/parse_5424.js +0 -106
- package/node_modules/glossy/test/parse_5848.js +0 -40
- package/node_modules/glossy/test/parse_8601.js +0 -14
- package/node_modules/glossy/test/parse_rfc3339.js +0 -9
- package/node_modules/glossy/test/produce.js +0 -162
- package/node_modules/glossy/test/runner.js +0 -40
- package/node_modules/glossy/test/structure_data.js +0 -24
- package/node_modules/nan/CHANGELOG.md +0 -537
- package/node_modules/nan/LICENSE.md +0 -13
- package/node_modules/nan/README.md +0 -455
- package/node_modules/nan/doc/asyncworker.md +0 -146
- package/node_modules/nan/doc/buffers.md +0 -54
- package/node_modules/nan/doc/callback.md +0 -76
- package/node_modules/nan/doc/converters.md +0 -41
- package/node_modules/nan/doc/errors.md +0 -226
- package/node_modules/nan/doc/json.md +0 -62
- package/node_modules/nan/doc/maybe_types.md +0 -583
- package/node_modules/nan/doc/methods.md +0 -664
- package/node_modules/nan/doc/new.md +0 -147
- package/node_modules/nan/doc/node_misc.md +0 -123
- package/node_modules/nan/doc/object_wrappers.md +0 -263
- package/node_modules/nan/doc/persistent.md +0 -296
- package/node_modules/nan/doc/scopes.md +0 -73
- package/node_modules/nan/doc/script.md +0 -38
- package/node_modules/nan/doc/string_bytes.md +0 -62
- package/node_modules/nan/doc/v8_internals.md +0 -199
- package/node_modules/nan/doc/v8_misc.md +0 -85
- package/node_modules/nan/include_dirs.js +0 -1
- package/node_modules/nan/nan.h +0 -2898
- package/node_modules/nan/nan_callbacks.h +0 -88
- package/node_modules/nan/nan_callbacks_12_inl.h +0 -514
- package/node_modules/nan/nan_callbacks_pre_12_inl.h +0 -520
- package/node_modules/nan/nan_converters.h +0 -72
- package/node_modules/nan/nan_converters_43_inl.h +0 -68
- package/node_modules/nan/nan_converters_pre_43_inl.h +0 -42
- package/node_modules/nan/nan_define_own_property_helper.h +0 -29
- package/node_modules/nan/nan_implementation_12_inl.h +0 -430
- package/node_modules/nan/nan_implementation_pre_12_inl.h +0 -263
- package/node_modules/nan/nan_json.h +0 -166
- package/node_modules/nan/nan_maybe_43_inl.h +0 -356
- package/node_modules/nan/nan_maybe_pre_43_inl.h +0 -268
- package/node_modules/nan/nan_new.h +0 -340
- package/node_modules/nan/nan_object_wrap.h +0 -156
- package/node_modules/nan/nan_persistent_12_inl.h +0 -132
- package/node_modules/nan/nan_persistent_pre_12_inl.h +0 -242
- package/node_modules/nan/nan_private.h +0 -73
- package/node_modules/nan/nan_string_bytes.h +0 -305
- package/node_modules/nan/nan_typedarray_contents.h +0 -96
- package/node_modules/nan/nan_weak.h +0 -437
- package/node_modules/nan/package.json +0 -41
- package/node_modules/nan/tools/1to2.js +0 -412
- package/node_modules/nan/tools/README.md +0 -14
- package/node_modules/nan/tools/package.json +0 -19
- package/node_modules/unix-dgram/LICENSE +0 -13
- package/node_modules/unix-dgram/README.md +0 -107
- package/node_modules/unix-dgram/binding.gyp +0 -20
- package/node_modules/unix-dgram/build/Makefile +0 -324
- package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram/src/unix_dgram.o.d +0 -58
- package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram.node.d +0 -1
- package/node_modules/unix-dgram/build/Release/.deps/Release/unix_dgram.node.d +0 -1
- package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram/src/unix_dgram.o +0 -0
- package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram.node +0 -0
- package/node_modules/unix-dgram/build/Release/unix_dgram.node +0 -0
- package/node_modules/unix-dgram/build/binding.Makefile +0 -6
- package/node_modules/unix-dgram/build/config.gypi +0 -213
- package/node_modules/unix-dgram/build/unix_dgram.target.mk +0 -159
- package/node_modules/unix-dgram/lib/unix_dgram.js +0 -168
- package/node_modules/unix-dgram/package.json +0 -36
- package/node_modules/unix-dgram/src/unix_dgram.cc +0 -404
- package/node_modules/unix-dgram/src/win_dummy.cc +0 -7
- package/node_modules/unix-dgram/test/test-connect-callback.js +0 -68
- package/node_modules/unix-dgram/test/test-connect.js +0 -53
- package/node_modules/unix-dgram/test/test-dgram-unix.js +0 -58
- package/node_modules/unix-dgram/test/test-send-error.js +0 -26
- package/node_modules/winston-syslog/.eslintrc +0 -7
- package/node_modules/winston-syslog/.travis.yml +0 -14
- package/node_modules/winston-syslog/CHANGELOG.md +0 -9
- package/node_modules/winston-syslog/LICENSE +0 -20
- package/node_modules/winston-syslog/README.md +0 -135
- package/node_modules/winston-syslog/lib/utils.js +0 -26
- package/node_modules/winston-syslog/lib/winston-syslog.js +0 -385
- package/node_modules/winston-syslog/package.json +0 -56
- package/node_modules/winston-syslog/test/format-test.js +0 -122
- package/node_modules/winston-syslog/test/syslog-test.js +0 -95
- package/node_modules/winston-syslog/test/unix-connect-test.js +0 -133
package/lib/hooks/patcher.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
package/lib/hooks/require.js
CHANGED
|
@@ -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(
|
|
23
|
+
constructor() {
|
|
29
24
|
this.reqHook = new RequireHook(logger);
|
|
30
25
|
|
|
31
|
-
// RequireHook takes care of hooking
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
Module.
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {Function}
|
|
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,
|
|
49
|
-
this.reqHook.resolve(descriptor,
|
|
42
|
+
resolve(descriptor, handler) {
|
|
43
|
+
this.reqHook.resolve(descriptor, handler);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
/**
|
|
53
|
-
*
|
|
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) {
|
package/lib/instrumentation.js
CHANGED
|
@@ -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/
|
|
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.
|
|
112
|
+
const denylist = _.get(settings, 'defend.ipDenylistsList');
|
|
113
113
|
if (denylist) {
|
|
114
114
|
logger.info(`IP Denylist updated. Total: ${denylist.length}`);
|
|
115
115
|
}
|
package/lib/protect/service.js
CHANGED
|
@@ -20,7 +20,12 @@ Copyright: 2021 Contrast Security, Inc
|
|
|
20
20
|
|
|
21
21
|
const _ = require('lodash');
|
|
22
22
|
|
|
23
|
-
const {
|
|
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
|
-
|
|
208
|
-
ruleId
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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.
|
|
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) => {
|