@contrast/agent 4.4.1 → 4.6.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/hapi/route-coverage.js +3 -3
- package/lib/assess/membrane/index.js +2 -8
- package/lib/assess/membrane/source-membrane.js +3 -4
- package/lib/assess/models/base-event.js +2 -2
- package/lib/assess/models/call-context.js +0 -3
- package/lib/assess/policy/propagators.json +20 -0
- package/lib/assess/policy/signatures.json +103 -0
- package/lib/assess/propagators/path/common.js +165 -36
- package/lib/assess/propagators/path/join.js +5 -1
- package/lib/assess/propagators/path/normalize.js +5 -1
- package/lib/assess/propagators/path/resolve.js +11 -2
- package/lib/assess/response-scanning/autocomplete-missing.js +0 -2
- package/lib/assess/response-scanning/parameter-pollution.js +0 -2
- 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/async-storage/hooks/bluebird.js +20 -0
- package/lib/core/config/options.js +3 -2
- package/lib/core/express/utils.js +1 -1
- package/lib/core/logger/debug-logger.js +15 -17
- package/lib/core/stacktrace.js +3 -4
- package/lib/feature-set.js +2 -1
- package/lib/hooks/encoding.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/patcher.js +10 -12
- package/lib/hooks/require.js +16 -22
- package/lib/instrumentation.js +0 -3
- package/lib/protect/analysis/aho-corasick.js +13 -30
- 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/clean-string/concatenations.js +1 -1
- package/lib/util/clean-string/util.js +1 -2
- package/lib/util/ip-analyzer.js +1 -1
- package/lib/util/some.js +27 -0
- package/lib/util/xml-analyzer/external-entity-finder.js +1 -1
- package/package.json +14 -15
- 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
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
17
|
+
const { PATCH_TYPES } = require('../../constants');
|
|
18
|
+
const patcher = require('../patcher');
|
|
19
|
+
const HttpFramework = require('./http');
|
|
20
|
+
|
|
21
|
+
/** @typedef {import('../../agent').ContrastAgent} Agent */
|
|
22
|
+
|
|
23
|
+
class Http2Framework extends HttpFramework {
|
|
24
|
+
/**
|
|
25
|
+
* @param {Agent} agent
|
|
26
|
+
*/
|
|
27
|
+
constructor(agent) {
|
|
28
|
+
super(agent, 'http2');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('http2')} http2
|
|
33
|
+
* @returns {import('http2')}
|
|
34
|
+
*/
|
|
35
|
+
onRequire(http2) {
|
|
36
|
+
patcher.patch(http2, 'createServer', {
|
|
37
|
+
name: `${this.id}.createServer`,
|
|
38
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
39
|
+
alwaysRun: true,
|
|
40
|
+
post: ({ args, result }) => this.handleServerCreate(args, result)
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
patcher.patch(http2, 'createSecureServer', {
|
|
44
|
+
name: `${this.id}.createSecureServer`,
|
|
45
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
46
|
+
alwaysRun: true,
|
|
47
|
+
post: ({ args, result }) => this.handleServerCreate(args, result)
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return http2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Emits a create event for the new Server instance.
|
|
55
|
+
* @param {any[]} args The arguments passed to the Server constructor
|
|
56
|
+
* @param {import('net').Server} server The http Server instance
|
|
57
|
+
*/
|
|
58
|
+
handleServerCreate(args, server) {
|
|
59
|
+
super.handleServerCreate(args, server);
|
|
60
|
+
|
|
61
|
+
// Since the `Http2Server` class is not exported, we can patch it here after
|
|
62
|
+
// creation.
|
|
63
|
+
// NOTE: could we just patch `net.Server` here and in `http`?
|
|
64
|
+
patcher.patch(server, 'listen', {
|
|
65
|
+
name: `${this.id}.Http2Server.listen`,
|
|
66
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
67
|
+
alwaysRun: true,
|
|
68
|
+
pre: ({ args, obj }) => this.handleServerListen(args, obj)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = Http2Framework;
|
|
@@ -14,9 +14,14 @@ Copyright: 2021 Contrast Security, Inc
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
+
const Http = require('./http');
|
|
18
|
+
const Http2 = require('./http2');
|
|
19
|
+
const Hapi16 = require('./hapi16');
|
|
20
|
+
|
|
17
21
|
module.exports = function(agent) {
|
|
18
22
|
// load all the hooks
|
|
19
|
-
new (
|
|
20
|
-
new (
|
|
21
|
-
new (
|
|
23
|
+
new Http(agent);
|
|
24
|
+
new Http(agent, 'https');
|
|
25
|
+
new Http2(agent);
|
|
26
|
+
new Hapi16(agent);
|
|
22
27
|
};
|
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) {
|
|
@@ -118,7 +116,7 @@ function runHooks(type, options, data, fn, thisTarget) {
|
|
|
118
116
|
* @return {boolean}
|
|
119
117
|
*/
|
|
120
118
|
function argsTracked(args) {
|
|
121
|
-
return
|
|
119
|
+
return some(args, (arg) => arg && tracker.getData(arg).tracked);
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
/**
|
|
@@ -283,7 +281,7 @@ function hookFunction(fn, options) {
|
|
|
283
281
|
return data.result;
|
|
284
282
|
}
|
|
285
283
|
|
|
286
|
-
// copy over all properties if there are properties on the original
|
|
284
|
+
// copy over all properties if there are properties on the original function.
|
|
287
285
|
// an example of this is the version of buffer you get from require('buffer')
|
|
288
286
|
// see NODE-335 for further background.
|
|
289
287
|
const descriptors = Object.getOwnPropertyDescriptors(fn);
|
|
@@ -292,7 +290,7 @@ function hookFunction(fn, options) {
|
|
|
292
290
|
...Object.getOwnPropertyNames(descriptors)
|
|
293
291
|
]) {
|
|
294
292
|
if (
|
|
295
|
-
key ===
|
|
293
|
+
key === promisifyCustom &&
|
|
296
294
|
typeof descriptors[key].value === 'function'
|
|
297
295
|
) {
|
|
298
296
|
// We must assume that custom promisified function values are true aliases
|
|
@@ -482,7 +480,7 @@ function patch(obj, props, options) {
|
|
|
482
480
|
}
|
|
483
481
|
|
|
484
482
|
const hookedMethods = hookableMethods(obj, props).map(function(prop) {
|
|
485
|
-
const opts =
|
|
483
|
+
const opts = typeof options === 'object' ? { ...options } : options;
|
|
486
484
|
|
|
487
485
|
// staticName means do not append methods to final name
|
|
488
486
|
// only used for Function(aka global.ContrastFunction)
|
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
|
|
@@ -17,9 +17,16 @@ Copyright: 2021 Contrast Security, Inc
|
|
|
17
17
|
// https://www.geeksforgeeks.org/aho-corasick-algorithm-pattern-searching/
|
|
18
18
|
|
|
19
19
|
const a = 'a'.charCodeAt(0);
|
|
20
|
-
const z = 'z'.charCodeAt(0);
|
|
21
20
|
const A = 'A'.charCodeAt(0);
|
|
22
21
|
const Z = 'Z'.charCodeAt(0);
|
|
22
|
+
const l2u = a - A;
|
|
23
|
+
|
|
24
|
+
// initialize the lower -> upper and upper -> lower translation array.
|
|
25
|
+
const TRANSLATION = [];
|
|
26
|
+
for (let byte = A; byte <= Z; byte++) {
|
|
27
|
+
// translate the uppercase character to lowercase
|
|
28
|
+
TRANSLATION[byte] = byte + l2u;
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
class AhoCorasick {
|
|
25
32
|
constructor(words) {
|
|
@@ -28,16 +35,6 @@ class AhoCorasick {
|
|
|
28
35
|
this.maxStates = 0;
|
|
29
36
|
for (const word of words) {
|
|
30
37
|
this.maxStates += word.length;
|
|
31
|
-
for (const char of word) {
|
|
32
|
-
// allow for any character to be upper or lower case. this could be
|
|
33
|
-
// restricted to certain words if desired, by changing the signature
|
|
34
|
-
// to {caseInsensitive, caseSensitive}.
|
|
35
|
-
if (char >= 'a' && char <= 'z') {
|
|
36
|
-
this.maxStates += 1;
|
|
37
|
-
} else if (char >= 'A' && char <= 'Z') {
|
|
38
|
-
this.maxStates += 1;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
// can optimize this by trading off computation for space.
|
|
@@ -83,33 +80,17 @@ class AhoCorasick {
|
|
|
83
80
|
let state = 0;
|
|
84
81
|
|
|
85
82
|
// create transitions for all character of the current word
|
|
86
|
-
for (
|
|
83
|
+
for (let byte of word) {
|
|
87
84
|
if (byte & 0x80) {
|
|
88
85
|
throw new Error('pattern character codes cannot exceed 127');
|
|
86
|
+
} else if (TRANSLATION[byte]) {
|
|
87
|
+
byte = TRANSLATION[byte];
|
|
89
88
|
}
|
|
90
89
|
if (this.goto[state][byte] === undefined) {
|
|
91
90
|
this.goto[state][byte] = stateCount;
|
|
92
91
|
stateCount += 1;
|
|
93
92
|
}
|
|
94
|
-
const previousState = state;
|
|
95
93
|
state = this.goto[state][byte];
|
|
96
|
-
|
|
97
|
-
// now make it case insensitive by mapping the alternate case to the
|
|
98
|
-
// same state as the original case.
|
|
99
|
-
let extra;
|
|
100
|
-
if (byte >= a && byte <= z) {
|
|
101
|
-
extra = byte - (a - A);
|
|
102
|
-
} else if (byte >= A && byte <= Z) {
|
|
103
|
-
extra = byte + (a - A);
|
|
104
|
-
} else {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (this.goto[previousState][extra] === undefined) {
|
|
109
|
-
// transition to the state that the other case character transitioned
|
|
110
|
-
// to.
|
|
111
|
-
this.goto[previousState][extra] = stateCount - 1;
|
|
112
|
-
}
|
|
113
94
|
}
|
|
114
95
|
|
|
115
96
|
// add current word to terminal list
|
|
@@ -177,6 +158,8 @@ class AhoCorasick {
|
|
|
177
158
|
// passes in. and they can be offset by the lowest charcode as well.
|
|
178
159
|
if (byte >= this.maxChars) {
|
|
179
160
|
byte = 0;
|
|
161
|
+
} else if (TRANSLATION[byte]) {
|
|
162
|
+
byte = TRANSLATION[byte];
|
|
180
163
|
}
|
|
181
164
|
let next = this.state;
|
|
182
165
|
while (this.goto[next][byte] === undefined) {
|
|
@@ -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
|