@contrast/agent 4.5.0 → 4.7.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.
- 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 +4 -18
- package/lib/assess/policy/propagators.json +11 -21
- package/lib/assess/policy/rules.json +5 -0
- package/lib/assess/policy/signatures.json +15 -0
- 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/index.js +0 -2
- package/lib/assess/propagators/joi/values.js +26 -11
- package/lib/assess/propagators/mustache/escape.js +22 -0
- package/lib/assess/propagators/path/common.js +155 -46
- package/lib/assess/propagators/path/join.js +5 -1
- package/lib/assess/propagators/path/normalize.js +1 -2
- package/lib/assess/propagators/path/resolve.js +11 -2
- package/lib/assess/propagators/template-escape.js +84 -0
- package/lib/assess/propagators/templates.js +2 -3
- package/lib/assess/sinks/dustjs-linkedin-xss.js +131 -0
- 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/async-storage/hooks/bluebird.js +20 -0
- package/lib/core/config/options.js +2 -1
- package/lib/core/stacktrace.js +3 -4
- package/lib/feature-set.js +2 -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/patcher.js +69 -48
- package/lib/hooks/require.js +16 -22
- package/lib/instrumentation.js +0 -3
- 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/some.js +27 -0
- package/lib/util/source-map.js +1 -1
- package/lib/util/xml-analyzer/external-entity-finder.js +1 -1
- package/package.json +14 -16
- package/lib/hooks/frameworks/https.js +0 -42
- 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/http.js
CHANGED
|
@@ -35,39 +35,45 @@ const logger = require('../core/logger')('contrast:hooks');
|
|
|
35
35
|
const patcher = require('./patcher');
|
|
36
36
|
const { PATCH_TYPES } = require('../constants');
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
logger.info('applying non-policy hook: http');
|
|
38
|
+
const flatten = (ary) => Array.prototype.join.apply(ary, [',']);
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Logs stack for every res.[end, writeHead, write] call
|
|
42
|
+
*
|
|
43
|
+
* @param {ServerResponse} response
|
|
44
|
+
* @param {string} method method to hook
|
|
45
|
+
*/
|
|
46
|
+
const logHook = (response, method) => {
|
|
47
|
+
patcher.patch(response, method, {
|
|
48
|
+
alwaysRun: true,
|
|
49
|
+
name: 'req-log',
|
|
50
|
+
patchType: PATCH_TYPES.MISC,
|
|
51
|
+
post(data) {
|
|
52
|
+
const { stack } = new Error();
|
|
53
|
+
logger.info('res.%s(%s):%o', method, flatten(data.args), stack);
|
|
54
|
+
}
|
|
46
55
|
});
|
|
47
56
|
};
|
|
48
57
|
|
|
49
|
-
module.exports.getId = getId;
|
|
50
|
-
module.exports.hookServerPrototype = hookServerPrototype;
|
|
51
|
-
|
|
52
58
|
/**
|
|
53
59
|
* Hooks the Server prototype's <code>emit</code> method.
|
|
54
|
-
* @param {Server}
|
|
60
|
+
* @param {Server} server The Server prototype
|
|
55
61
|
*/
|
|
56
|
-
|
|
57
|
-
patcher.patch(
|
|
62
|
+
const hookServer = (server, agent, id) => {
|
|
63
|
+
patcher.patch(server, 'listen', {
|
|
58
64
|
alwaysRun: true,
|
|
59
65
|
name: 'server-listen-log-time',
|
|
60
66
|
patchType: PATCH_TYPES.MISC,
|
|
61
|
-
pre(
|
|
67
|
+
pre() {
|
|
62
68
|
logger.info(`${process.pid}: http listen after ${process.uptime()}`);
|
|
63
69
|
}
|
|
64
70
|
});
|
|
65
71
|
|
|
66
72
|
// Wrap the request emit in a contrast domain to track non-dataflow hooks, and for storage.
|
|
67
|
-
const { emit } =
|
|
68
|
-
|
|
69
|
-
const [event, req, res] = args;
|
|
73
|
+
const { emit } = server;
|
|
74
|
+
server.emit = function newEmit(...args) {
|
|
70
75
|
const self = this;
|
|
76
|
+
const [event, req, res] = args;
|
|
71
77
|
|
|
72
78
|
if (event === 'request') {
|
|
73
79
|
logger.debug('Received request %s, method %s', req.url, req.method);
|
|
@@ -75,35 +81,77 @@ function hookServerPrototype(proto, agent) {
|
|
|
75
81
|
agent,
|
|
76
82
|
req,
|
|
77
83
|
res,
|
|
78
|
-
hookResponse,
|
|
79
|
-
|
|
84
|
+
hookResponse(response, agent) {
|
|
85
|
+
const { writeHead, end } = response;
|
|
86
|
+
// XXX(ehden): we keep the original method available to call when handling
|
|
87
|
+
// blocked requests because of MethodTampering or other rules whose sink
|
|
88
|
+
// type is RESPONSE_STATUS and which can block at perimeter. We call the original
|
|
89
|
+
// when we block to prevent recursing through blocks by setting our own 403.
|
|
90
|
+
response[WRITE_HEAD] = writeHead;
|
|
91
|
+
response[END] = end;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Patch writeHead to emit a sink event before calling writeHead
|
|
95
|
+
*
|
|
96
|
+
*/
|
|
97
|
+
patcher.patch(response, 'writeHead', {
|
|
98
|
+
alwaysRun: true,
|
|
99
|
+
name: 'write-head',
|
|
100
|
+
patchType: PATCH_TYPES.PROTECT_SINK,
|
|
101
|
+
pre(data) {
|
|
102
|
+
const [statusCode, reason] = data.args;
|
|
103
|
+
let [, , obj] = data.args;
|
|
104
|
+
let contentType;
|
|
105
|
+
if (typeof reason !== 'string') {
|
|
106
|
+
obj = reason;
|
|
107
|
+
}
|
|
108
|
+
if (obj && typeof obj === 'object') {
|
|
109
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
110
|
+
if (key.toLowerCase() === 'content-type') {
|
|
111
|
+
contentType = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (contentType) {
|
|
116
|
+
AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, contentType);
|
|
117
|
+
}
|
|
118
|
+
emitSinkEvent(statusCode, SINK_TYPES.RESPONSE_STATUS, id, false);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
patcher.patch(response, 'setHeader', {
|
|
123
|
+
alwaysRun: true,
|
|
124
|
+
name: 'set-header',
|
|
125
|
+
patchType: PATCH_TYPES.ASYNC_CONTEXT,
|
|
126
|
+
pre(data) {
|
|
127
|
+
const [name = '', value] = data.args;
|
|
128
|
+
if (name.toLowerCase() === 'content-type' && value) {
|
|
129
|
+
AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// special HTTP logging for responses.
|
|
135
|
+
if (agent.config.agent.logger.log_outbound_http) {
|
|
136
|
+
['end', 'writeHead', 'write'].forEach((method) => {
|
|
137
|
+
logHook(response, method);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
callback() {
|
|
80
142
|
const ipEvent = createSourceEvent(
|
|
81
143
|
req,
|
|
82
144
|
req,
|
|
83
145
|
res,
|
|
84
|
-
'
|
|
146
|
+
'socket.remoteAddress',
|
|
85
147
|
INPUT_TYPES.IP,
|
|
86
|
-
|
|
148
|
+
id
|
|
87
149
|
);
|
|
88
150
|
agentEmitter.emit('http.requestStart', req, res, ipEvent);
|
|
89
151
|
|
|
90
|
-
emitSourceEvent(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
res,
|
|
94
|
-
'method',
|
|
95
|
-
INPUT_TYPES.METHOD,
|
|
96
|
-
getId(req)
|
|
97
|
-
);
|
|
98
|
-
emitSourceEvent(
|
|
99
|
-
req,
|
|
100
|
-
req,
|
|
101
|
-
res,
|
|
102
|
-
'headers',
|
|
103
|
-
INPUT_TYPES.HEADER,
|
|
104
|
-
getId(req)
|
|
105
|
-
);
|
|
106
|
-
emitSourceEvent(req, req, res, 'url', INPUT_TYPES.URL, getId(req));
|
|
152
|
+
emitSourceEvent(req, req, res, 'method', INPUT_TYPES.METHOD, id);
|
|
153
|
+
emitSourceEvent(req, req, res, 'headers', INPUT_TYPES.HEADER, id);
|
|
154
|
+
emitSourceEvent(req, req, res, 'url', INPUT_TYPES.URL, id);
|
|
107
155
|
|
|
108
156
|
const rv = emit.apply(self, args);
|
|
109
157
|
|
|
@@ -115,101 +163,37 @@ function hookServerPrototype(proto, agent) {
|
|
|
115
163
|
return emit.apply(self, args);
|
|
116
164
|
}
|
|
117
165
|
};
|
|
118
|
-
}
|
|
166
|
+
};
|
|
119
167
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* context.
|
|
123
|
-
* @param {ServerResponse} res
|
|
124
|
-
*/
|
|
125
|
-
function hookResponse(res, agent) {
|
|
126
|
-
const flattenArgs = (args) => Array.prototype.join.apply(args, [',']);
|
|
127
|
-
|
|
128
|
-
/*
|
|
129
|
-
* Logs stack for every res.[end, writeHead, write] call
|
|
130
|
-
*
|
|
131
|
-
* @param {ServerResponse} res
|
|
132
|
-
* @param {string} method method to hook
|
|
133
|
-
*/
|
|
134
|
-
function logHook(res, method) {
|
|
135
|
-
patcher.patch(res, method, {
|
|
136
|
-
alwaysRun: true,
|
|
137
|
-
name: 'req-log',
|
|
138
|
-
patchType: PATCH_TYPES.MISC,
|
|
139
|
-
post(data) {
|
|
140
|
-
const { stack } = new Error();
|
|
141
|
-
logger.info('res.%s(%s):%o', method, flattenArgs(data.args), stack);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const { writeHead, end } = res;
|
|
147
|
-
// XXX(ehden): we keep the original method available to call when handling
|
|
148
|
-
// blocked requests because of MethodTampering or other rules whose sink
|
|
149
|
-
// type is RESPONSE_STATUS and which can block at perimeter. We call the original
|
|
150
|
-
// when we block to prevent recursing through blocks by setting our own 403.
|
|
151
|
-
res[WRITE_HEAD] = writeHead;
|
|
152
|
-
res[END] = end;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Patch writeHead to emit a sink event before calling writeHead
|
|
156
|
-
*
|
|
157
|
-
*/
|
|
158
|
-
patcher.patch(res, 'writeHead', {
|
|
159
|
-
alwaysRun: true,
|
|
160
|
-
name: 'write-head',
|
|
161
|
-
patchType: PATCH_TYPES.PROTECT_SINK,
|
|
162
|
-
pre(data) {
|
|
163
|
-
let contentType;
|
|
164
|
-
if (data.args[1] && typeof data.args[1] === 'object') {
|
|
165
|
-
for (const [key, value] of Object.entries(data.args[1])) {
|
|
166
|
-
if (key.toLowerCase() === 'content-type') {
|
|
167
|
-
contentType = value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
168
|
+
module.exports = (agent) => {
|
|
169
|
+
logger.info('applying non-policy hook: http');
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
const [statusCode] = data.args;
|
|
176
|
-
emitSinkEvent(statusCode, SINK_TYPES.RESPONSE_STATUS, getId(this), false);
|
|
177
|
-
}
|
|
171
|
+
moduleHook.resolve({ name: 'http' }, (http) => {
|
|
172
|
+
hookServer(http.Server.prototype, agent, 'http');
|
|
178
173
|
});
|
|
179
174
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
name: 'set-header',
|
|
183
|
-
patchType: PATCH_TYPES.ASYNC_CONTEXT,
|
|
184
|
-
pre(data) {
|
|
185
|
-
if (
|
|
186
|
-
(data.args[0] || '').toLowerCase() === 'content-type' &&
|
|
187
|
-
data.args[1]
|
|
188
|
-
) {
|
|
189
|
-
AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, data.args[1]);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
175
|
+
moduleHook.resolve({ name: 'https' }, (https) => {
|
|
176
|
+
hookServer(https.Server.prototype, agent, 'https');
|
|
192
177
|
});
|
|
193
178
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
179
|
+
moduleHook.resolve({ name: 'http2' }, (http2) => {
|
|
180
|
+
patcher.patch(http2, 'createServer', {
|
|
181
|
+
name: `create-server-http2-hooks`,
|
|
182
|
+
patchType: PATCH_TYPES.MISC,
|
|
183
|
+
alwaysRun: true,
|
|
184
|
+
post({ result }) {
|
|
185
|
+
hookServer(result, agent);
|
|
186
|
+
}
|
|
198
187
|
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
188
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return req.socket.encrypted ? 'https' : 'http';
|
|
214
|
-
}
|
|
215
|
-
}
|
|
189
|
+
patcher.patch(http2, 'createSecureServer', {
|
|
190
|
+
name: `create-secure-server-http2-hooks`,
|
|
191
|
+
patchType: PATCH_TYPES.MISC,
|
|
192
|
+
alwaysRun: true,
|
|
193
|
+
post({ result }) {
|
|
194
|
+
hookServer(result, agent);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
module.exports.hookServer = hookServer;
|
package/lib/hooks/patcher.js
CHANGED
|
@@ -22,19 +22,21 @@ 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');
|
|
39
|
+
const some = require('../util/some');
|
|
38
40
|
|
|
39
41
|
// When a pre-hook returns the result of this function, the value passed will
|
|
40
42
|
// be used in place of the original function's return value, and the original
|
|
@@ -43,12 +45,8 @@ function preempt(value) {
|
|
|
43
45
|
return new PatcherPreempt(value);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
const { toString } = {};
|
|
47
|
-
|
|
48
48
|
function isFunction(fn) {
|
|
49
|
-
return
|
|
50
|
-
toString.call(this, fn) === '[object Function]' || fn instanceof Function
|
|
51
|
-
);
|
|
49
|
+
return fn instanceof Function || typeof fn === 'function';
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
function PatcherPreempt(v) {
|
|
@@ -99,12 +97,7 @@ Function.prototype._contrast_toString = function() {
|
|
|
99
97
|
return Reflect.apply(functionToString, this, arguments);
|
|
100
98
|
};
|
|
101
99
|
|
|
102
|
-
function runHooks(type,
|
|
103
|
-
const fnHooks = hooks.get(fn);
|
|
104
|
-
if (!fnHooks) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
100
|
+
function runHooks(type, data, thisTarget, fnHooks) {
|
|
108
101
|
fnHooks.forEach((hook, key) => {
|
|
109
102
|
if (hook[type]) {
|
|
110
103
|
hook[type].apply(thisTarget, [data]);
|
|
@@ -118,7 +111,7 @@ function runHooks(type, options, data, fn, thisTarget) {
|
|
|
118
111
|
* @return {boolean}
|
|
119
112
|
*/
|
|
120
113
|
function argsTracked(args) {
|
|
121
|
-
return
|
|
114
|
+
return some(args, (arg) => arg && tracker.getData(arg).tracked);
|
|
122
115
|
}
|
|
123
116
|
|
|
124
117
|
/**
|
|
@@ -187,11 +180,17 @@ function runOriginalFunction(fn, { args, name }, target) {
|
|
|
187
180
|
return fn.apply(this, args);
|
|
188
181
|
}
|
|
189
182
|
|
|
190
|
-
|
|
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) {
|
|
191
193
|
return () => {
|
|
192
|
-
if (!process.hrtime.bigint || !perfLoggingEnabled) {
|
|
193
|
-
return func();
|
|
194
|
-
}
|
|
195
194
|
const start = process.hrtime.bigint();
|
|
196
195
|
const rv = func();
|
|
197
196
|
perfLogger[type](funcKey, Number(process.hrtime.bigint() - start));
|
|
@@ -219,6 +218,7 @@ function hookFunction(fn, options) {
|
|
|
219
218
|
const { funcKey } = options;
|
|
220
219
|
logger.trace(`hook ${funcKey}`);
|
|
221
220
|
|
|
221
|
+
// eslint-disable-next-line complexity
|
|
222
222
|
function hooked(...args) {
|
|
223
223
|
const target = new.target;
|
|
224
224
|
|
|
@@ -249,36 +249,59 @@ function hookFunction(fn, options) {
|
|
|
249
249
|
perfLogger.logEvent(funcKey);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
|
|
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
|
|
253
256
|
// this will prevent other instrumentation from running
|
|
254
257
|
// within the pre function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`${funcKey} pre`
|
|
262
|
-
);
|
|
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
|
+
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
if (hasPreHook) {
|
|
266
|
+
Scopes.runInNoInstrumentationScope(
|
|
267
|
+
runWrapper(() => runHooks('pre', data, this, fnHooks), funcKey, 'pre'),
|
|
268
|
+
`${funcKey} pre`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
270
271
|
|
|
271
|
-
// 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
|
|
272
293
|
// this will prevent other instrumentation from running
|
|
273
294
|
// within the post function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
if (hasPostHook) {
|
|
296
|
+
Scopes.runInNoInstrumentationScope(
|
|
297
|
+
runWrapper(
|
|
298
|
+
() => runHooks('post', data, this, fnHooks),
|
|
299
|
+
funcKey,
|
|
300
|
+
'post'
|
|
301
|
+
),
|
|
302
|
+
`${funcKey} post`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
282
305
|
|
|
283
306
|
return data.result;
|
|
284
307
|
}
|
|
@@ -292,7 +315,7 @@ function hookFunction(fn, options) {
|
|
|
292
315
|
...Object.getOwnPropertyNames(descriptors)
|
|
293
316
|
]) {
|
|
294
317
|
if (
|
|
295
|
-
key ===
|
|
318
|
+
key === promisifyCustom &&
|
|
296
319
|
typeof descriptors[key].value === 'function'
|
|
297
320
|
) {
|
|
298
321
|
// We must assume that custom promisified function values are true aliases
|
|
@@ -438,8 +461,7 @@ function hook(obj, prop, opts) {
|
|
|
438
461
|
}
|
|
439
462
|
} catch (err) {
|
|
440
463
|
logger.info(
|
|
441
|
-
`unable to patch unconfigurable property ${opts.name}:${prop}
|
|
442
|
-
`,
|
|
464
|
+
`unable to patch unconfigurable property ${opts.name}:${prop}`,
|
|
443
465
|
err
|
|
444
466
|
);
|
|
445
467
|
}
|
|
@@ -482,7 +504,7 @@ function patch(obj, props, options) {
|
|
|
482
504
|
}
|
|
483
505
|
|
|
484
506
|
const hookedMethods = hookableMethods(obj, props).map(function(prop) {
|
|
485
|
-
const opts =
|
|
507
|
+
const opts = typeof options === 'object' ? { ...options } : options;
|
|
486
508
|
|
|
487
509
|
// staticName means do not append methods to final name
|
|
488
510
|
// only used for Function(aka global.ContrastFunction)
|
|
@@ -545,7 +567,6 @@ function unwrap(fn) {
|
|
|
545
567
|
module.exports = {
|
|
546
568
|
patch,
|
|
547
569
|
preempt,
|
|
548
|
-
recordTime,
|
|
549
570
|
resetInstrumentation,
|
|
550
571
|
unpatch,
|
|
551
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
|
|
@@ -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
|
/**
|
package/lib/util/some.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
function some(array, predicate) {
|
|
16
|
+
let index = -1;
|
|
17
|
+
const length = array == null ? 0 : array.length;
|
|
18
|
+
|
|
19
|
+
while (++index < length) {
|
|
20
|
+
if (predicate(array[index], index, array)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = some;
|