@contrast/agent 4.5.0 → 4.5.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/source-membrane.js +3 -4
- package/lib/core/async-storage/hooks/bluebird.js +20 -0
- package/lib/core/stacktrace.js +1 -3
- package/lib/feature-set.js +2 -1
- package/lib/hooks/patcher.js +13 -23
- package/lib/protect/rules/cmd-injection-command-backdoors/backdoor-detector.js +3 -3
- package/lib/protect/rules/signatures/reflected-xss/helpers/function-call.js +1 -1
- package/lib/protect/rules/xss/helpers/function-call.js +1 -1
- package/lib/util/clean-stack.js +1 -1
- package/lib/util/clean-string/brackets.js +3 -3
- package/lib/util/ip-analyzer.js +1 -1
- package/lib/util/xml-analyzer/external-entity-finder.js +1 -1
- package/node_modules/unix-dgram/build/Makefile +1 -1
- package/node_modules/unix-dgram/build/config.gypi +1 -1
- package/package.json +1 -1
package/bin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.27.3
|
|
Binary file
|
package/bin/mac/contrast-service
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -294,10 +294,9 @@ module.exports = class SourceMembrane extends Membrane {
|
|
|
294
294
|
if (!(metadata.sourceType && metadata.path)) {
|
|
295
295
|
return false;
|
|
296
296
|
}
|
|
297
|
-
const koaQueryString = metadata.path.
|
|
298
|
-
if (koaQueryString) {
|
|
299
|
-
|
|
300
|
-
metadata.path = koaQueryString[1] || metadata.path;
|
|
297
|
+
const koaQueryString = metadata.path.split('=');
|
|
298
|
+
if (koaQueryString[1]) {
|
|
299
|
+
metadata.path = koaQueryString[0];
|
|
301
300
|
}
|
|
302
301
|
return true;
|
|
303
302
|
}
|
|
@@ -23,6 +23,26 @@ module.exports = function() {
|
|
|
23
23
|
moduleHook.resolve({ name: 'bluebird' }, (bluebird) => {
|
|
24
24
|
module.exports.patchConfig(bluebird);
|
|
25
25
|
module.exports.patchAddCallbacks(bluebird);
|
|
26
|
+
module.exports.patchGetNewLibraryCopy(bluebird);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ensures that new library copies are also instrumented.
|
|
32
|
+
* @param {function} bluebird the library export
|
|
33
|
+
*/
|
|
34
|
+
module.exports.patchGetNewLibraryCopy = function(bluebird) {
|
|
35
|
+
if (typeof bluebird.getNewLibraryCopy !== 'function') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
patcher.patch(bluebird, 'getNewLibraryCopy', {
|
|
40
|
+
alwaysRun: true,
|
|
41
|
+
name: 'bluebird.getNewLibraryCopy',
|
|
42
|
+
patchType: PATCH_TYPES.ASYNC_CONTEXT,
|
|
43
|
+
post(data) {
|
|
44
|
+
module.exports.patchAddCallbacks(data.result);
|
|
45
|
+
}
|
|
26
46
|
});
|
|
27
47
|
};
|
|
28
48
|
|
package/lib/core/stacktrace.js
CHANGED
|
@@ -21,7 +21,6 @@ const _isAgentPath = require('../util/is-agent-path');
|
|
|
21
21
|
|
|
22
22
|
const STACK_TRACE_LIMIT = 25;
|
|
23
23
|
const EVAL_ORIGIN_REGEX = /\((.*?):(\d+):\d+\)/;
|
|
24
|
-
const EJS_EVAL_ORIGIN_REGEX = /(.*\.ejs$)/;
|
|
25
24
|
const EVENTS_FILE = semver.gte(process.version, '16.0.0')
|
|
26
25
|
? 'node:events'
|
|
27
26
|
: 'events.js';
|
|
@@ -146,8 +145,7 @@ class Factory {
|
|
|
146
145
|
if (callsite.isEval()) {
|
|
147
146
|
evalOrigin = Factory.formatFileName(callsite.getEvalOrigin());
|
|
148
147
|
[, file, lineNumber, columnNumber] =
|
|
149
|
-
evalOrigin.match(EVAL_ORIGIN_REGEX) ||
|
|
150
|
-
evalOrigin.match(EJS_EVAL_ORIGIN_REGEX);
|
|
148
|
+
evalOrigin.match(EVAL_ORIGIN_REGEX) || evalOrigin.endsWith('.ejs');
|
|
151
149
|
}
|
|
152
150
|
|
|
153
151
|
file = file || callsite.getFileName();
|
package/lib/feature-set.js
CHANGED
package/lib/hooks/patcher.js
CHANGED
|
@@ -22,19 +22,20 @@ Copyright: 2021 Contrast Security, Inc
|
|
|
22
22
|
// the arguments keyword instead of providing a `...args` rest param.
|
|
23
23
|
/* eslint-disable prefer-rest-params */
|
|
24
24
|
|
|
25
|
-
const { promisify } = require('util');
|
|
26
25
|
const logger = require('../core/logger')('contrast:hooks');
|
|
27
26
|
const agent = require('../agent');
|
|
28
|
-
const perfLogger = require('../core/logger/perf-logger')(agent);
|
|
29
|
-
const _ = require('lodash');
|
|
30
27
|
const perfLoggingEnabled =
|
|
31
28
|
agent.config && agent.config.agent.node.req_perf_logging;
|
|
29
|
+
const perfLogger = perfLoggingEnabled
|
|
30
|
+
? require('../core/logger/perf-logger')(agent)
|
|
31
|
+
: undefined;
|
|
32
32
|
const tracker = require('../tracker.js');
|
|
33
33
|
const { AsyncStorage } = require('../core/async-storage');
|
|
34
34
|
const {
|
|
35
35
|
Scopes,
|
|
36
36
|
SCOPE_NAMES: { NO_PROPAGATION }
|
|
37
37
|
} = require('../core/async-storage/scopes');
|
|
38
|
+
const promisifyCustom = Symbol.for('nodejs.util.promisify.custom');
|
|
38
39
|
|
|
39
40
|
// When a pre-hook returns the result of this function, the value passed will
|
|
40
41
|
// be used in place of the original function's return value, and the original
|
|
@@ -43,12 +44,8 @@ function preempt(value) {
|
|
|
43
44
|
return new PatcherPreempt(value);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const { toString } = {};
|
|
47
|
-
|
|
48
47
|
function isFunction(fn) {
|
|
49
|
-
return
|
|
50
|
-
toString.call(this, fn) === '[object Function]' || fn instanceof Function
|
|
51
|
-
);
|
|
48
|
+
return fn instanceof Function || typeof fn === 'function';
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
function PatcherPreempt(v) {
|
|
@@ -112,15 +109,6 @@ function runHooks(type, options, data, fn, thisTarget) {
|
|
|
112
109
|
});
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
/**
|
|
116
|
-
* Checks if any of the provided arguments are tracked.
|
|
117
|
-
* @param {any[]} args
|
|
118
|
-
* @return {boolean}
|
|
119
|
-
*/
|
|
120
|
-
function argsTracked(args) {
|
|
121
|
-
return _.some(args, (arg) => arg && tracker.getData(arg).tracked);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
112
|
/**
|
|
125
113
|
* Whether to skip the running of instrumentation in registered pre/post hooks.
|
|
126
114
|
* When this returns `false` (don't skip), the pre/post hooks will execute in
|
|
@@ -232,8 +220,11 @@ function hookFunction(fn, options) {
|
|
|
232
220
|
}
|
|
233
221
|
|
|
234
222
|
// short-circuit optimization for add
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
}
|
|
237
228
|
}
|
|
238
229
|
|
|
239
230
|
const data = {
|
|
@@ -292,7 +283,7 @@ function hookFunction(fn, options) {
|
|
|
292
283
|
...Object.getOwnPropertyNames(descriptors)
|
|
293
284
|
]) {
|
|
294
285
|
if (
|
|
295
|
-
key ===
|
|
286
|
+
key === promisifyCustom &&
|
|
296
287
|
typeof descriptors[key].value === 'function'
|
|
297
288
|
) {
|
|
298
289
|
// We must assume that custom promisified function values are true aliases
|
|
@@ -330,8 +321,7 @@ function hookableMethods(obj, props, callback) {
|
|
|
330
321
|
}
|
|
331
322
|
|
|
332
323
|
// Execute callback for every method in the props array.
|
|
333
|
-
let i = props.length;
|
|
334
|
-
while (i--) {
|
|
324
|
+
for (let i = props.length; i >= 0; i--) {
|
|
335
325
|
// If the property isn't a function...
|
|
336
326
|
if (
|
|
337
327
|
!isFunction(obj[props[i]]) ||
|
|
@@ -482,7 +472,7 @@ function patch(obj, props, options) {
|
|
|
482
472
|
}
|
|
483
473
|
|
|
484
474
|
const hookedMethods = hookableMethods(obj, props).map(function(prop) {
|
|
485
|
-
const opts =
|
|
475
|
+
const opts = Object.assign({}, options);
|
|
486
476
|
|
|
487
477
|
// staticName means do not append methods to final name
|
|
488
478
|
// only used for Function(aka global.ContrastFunction)
|
|
@@ -14,7 +14,7 @@ Copyright: 2021 Contrast Security, Inc
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
const { INPUT_TYPES } = require('../common');
|
|
17
|
-
const
|
|
17
|
+
const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
|
|
18
18
|
const stripWhiteSpace = (str) => str.replace(/\s/g, '');
|
|
19
19
|
const REQUEST_KEYS = {
|
|
20
20
|
queryParams: INPUT_TYPES.QUERYSTRING,
|
|
@@ -96,8 +96,8 @@ module.exports = class BackdoorDetector {
|
|
|
96
96
|
const normalizedParam = stripWhiteSpace(requestValue);
|
|
97
97
|
return (
|
|
98
98
|
normalizedParam === this.normalizedCmd ||
|
|
99
|
-
(
|
|
100
|
-
this.normalizedCmd
|
|
99
|
+
(this.normalizedCmd.endsWith(normalizedParam) &&
|
|
100
|
+
SINK_EXPLOIT_PATTERN_START.test(this.normalizedCmd))
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
};
|
|
@@ -73,7 +73,7 @@ class FunctionCall {
|
|
|
73
73
|
hasMultipleUnquotedBarewords() {
|
|
74
74
|
let rc = false;
|
|
75
75
|
const QUOTES = new RegExp('[\'|"]');
|
|
76
|
-
const MULTI_WORDS = new RegExp('[
|
|
76
|
+
const MULTI_WORDS = new RegExp('[\\w\\s]+');
|
|
77
77
|
if (!this.expression.match(QUOTES)) {
|
|
78
78
|
rc = this.expression.match(MULTI_WORDS);
|
|
79
79
|
}
|
|
@@ -72,7 +72,7 @@ class FunctionCall {
|
|
|
72
72
|
hasMultipleUnquotedBarewords() {
|
|
73
73
|
let rc = false;
|
|
74
74
|
const QUOTES = new RegExp('[\'|"]');
|
|
75
|
-
const MULTI_WORDS = new RegExp('[
|
|
75
|
+
const MULTI_WORDS = new RegExp('[\\w\\s]+');
|
|
76
76
|
if (!this.expression.match(QUOTES)) {
|
|
77
77
|
rc = this.expression.match(MULTI_WORDS);
|
|
78
78
|
}
|
package/lib/util/clean-stack.js
CHANGED
|
@@ -202,7 +202,7 @@ const makeFrame = (callsite) => {
|
|
|
202
202
|
evalOrigin = CleanStack.formatFileName(callsite.getEvalOrigin());
|
|
203
203
|
[, file, lineNumber, columnNumber] = callsite
|
|
204
204
|
.getEvalOrigin()
|
|
205
|
-
.match(/\((
|
|
205
|
+
.match(/\((.{3,4095}?):(\d+):\d+\)/);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
file = file || callsite.getFileName();
|
|
@@ -40,7 +40,7 @@ class Brackets {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Coerces
|
|
43
|
+
* Coerces occurrences of substrings that look like
|
|
44
44
|
* bracket accessors (['abcd'], ["efgh"]) into their
|
|
45
45
|
* dot-accessor equivalents (.abcd, .efgh).
|
|
46
46
|
* @param {} str
|
|
@@ -58,8 +58,8 @@ function coerceToDotAccessors(str) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const bracketed = str.substring(startIdx, stopIdx + 1);
|
|
61
|
-
const
|
|
62
|
-
const match =
|
|
61
|
+
const pattern = /^\[\s*[`'"]([a-zA-Z_]+[a-zA-Z0-9_]*)[`'"]\s*]$/;
|
|
62
|
+
const match = pattern.exec(bracketed);
|
|
63
63
|
|
|
64
64
|
return !match
|
|
65
65
|
? str
|
package/lib/util/ip-analyzer.js
CHANGED
|
@@ -66,7 +66,7 @@ const getReqAddresses = (req = {}) => {
|
|
|
66
66
|
*/
|
|
67
67
|
const getForwardedFor = (req = {}) => {
|
|
68
68
|
const header = req.headers && req.headers['x-forwarded-for'];
|
|
69
|
-
return header ? header.split(
|
|
69
|
+
return header ? header.split(',').map((x) => x.trim()) : [];
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -38,7 +38,7 @@ const UP_DIR_LINUX = '../';
|
|
|
38
38
|
const UP_DIR_WIN = '..\\';
|
|
39
39
|
const ENTITY_TYPES = { SYSTEM: 'SYSTEM', PUBLIC: 'PUBLIC' };
|
|
40
40
|
|
|
41
|
-
const EXTERNAL_RX = /<!ENTITY\s+[a-zA-Z0-f]+\s+(?:SYSTEM|PUBLIC)\s+(
|
|
41
|
+
const EXTERNAL_RX = /<!ENTITY\s+[a-zA-Z0-f]+\s+(?:SYSTEM|PUBLIC)\s+"(.*?)"\s*>/g;
|
|
42
42
|
// <!ENTITY name SYSTEM "URI">
|
|
43
43
|
const SYSTEM_ID_REGEX = /<!ENTITY\s+([a-zA-Z0-9]+)\s+SYSTEM\s+"(.*?)"\s*>/;
|
|
44
44
|
// <!ENTITY name PUBLIC "public_ID" "URI">
|
|
@@ -309,7 +309,7 @@ endif
|
|
|
309
309
|
|
|
310
310
|
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
|
311
311
|
cmd_regen_makefile = cd $(srcdir); /opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/home/runner/.cache/node-gyp/12.22.7" "-Dnode_gyp_dir=/opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/home/runner/.cache/node-gyp/12.22.7/<(target_arch)/node.lib" "-Dmodule_root_dir=/home/runner/work/node-agent/node-agent/target/node_modules/unix-dgram" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/home/runner/work/node-agent/node-agent/target/node_modules/unix-dgram/build/config.gypi -I/opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/home/runner/.cache/node-gyp/12.22.7/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
|
312
|
-
Makefile: $(srcdir)/build/config.gypi $(srcdir)/../../../../../../../../opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
|
|
312
|
+
Makefile: $(srcdir)/../../../../../../.cache/node-gyp/12.22.7/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
|
|
313
313
|
$(call do_cmd,regen_makefile)
|
|
314
314
|
|
|
315
315
|
# "all" is a concatenation of the "all" targets from all the included
|