@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.
Files changed (156) hide show
  1. package/bin/VERSION +1 -1
  2. package/bin/linux/contrast-service +0 -0
  3. package/bin/mac/contrast-service +0 -0
  4. package/bin/windows/contrast-service.exe +0 -0
  5. package/lib/assess/hapi/route-coverage.js +3 -3
  6. package/lib/assess/membrane/index.js +2 -8
  7. package/lib/assess/membrane/source-membrane.js +3 -4
  8. package/lib/assess/models/base-event.js +2 -2
  9. package/lib/assess/models/call-context.js +0 -3
  10. package/lib/assess/policy/propagators.json +20 -0
  11. package/lib/assess/policy/signatures.json +103 -0
  12. package/lib/assess/propagators/path/common.js +165 -36
  13. package/lib/assess/propagators/path/join.js +5 -1
  14. package/lib/assess/propagators/path/normalize.js +5 -1
  15. package/lib/assess/propagators/path/resolve.js +11 -2
  16. package/lib/assess/response-scanning/autocomplete-missing.js +0 -2
  17. package/lib/assess/response-scanning/parameter-pollution.js +0 -2
  18. package/lib/core/arch-components/dynamodb.js +1 -2
  19. package/lib/core/arch-components/dynamodbv3.js +44 -0
  20. package/lib/core/arch-components/index.js +1 -0
  21. package/lib/core/async-storage/hooks/bluebird.js +20 -0
  22. package/lib/core/config/options.js +3 -2
  23. package/lib/core/express/utils.js +1 -1
  24. package/lib/core/logger/debug-logger.js +15 -17
  25. package/lib/core/stacktrace.js +3 -4
  26. package/lib/feature-set.js +2 -1
  27. package/lib/hooks/encoding.js +1 -1
  28. package/lib/hooks/frameworks/base.js +8 -2
  29. package/lib/hooks/frameworks/http.js +23 -16
  30. package/lib/hooks/frameworks/http2.js +73 -0
  31. package/lib/hooks/frameworks/index.js +8 -3
  32. package/lib/hooks/http.js +112 -128
  33. package/lib/hooks/patcher.js +10 -12
  34. package/lib/hooks/require.js +16 -22
  35. package/lib/instrumentation.js +0 -3
  36. package/lib/protect/analysis/aho-corasick.js +13 -30
  37. package/lib/protect/rules/cmd-injection-command-backdoors/backdoor-detector.js +3 -3
  38. package/lib/protect/rules/signatures/reflected-xss/helpers/function-call.js +1 -1
  39. package/lib/protect/rules/xss/helpers/function-call.js +1 -1
  40. package/lib/util/clean-stack.js +1 -1
  41. package/lib/util/clean-string/brackets.js +3 -3
  42. package/lib/util/clean-string/concatenations.js +1 -1
  43. package/lib/util/clean-string/util.js +1 -2
  44. package/lib/util/ip-analyzer.js +1 -1
  45. package/lib/util/some.js +27 -0
  46. package/lib/util/xml-analyzer/external-entity-finder.js +1 -1
  47. package/package.json +14 -15
  48. package/lib/hooks/frameworks/https.js +0 -42
  49. package/node_modules/bindings/LICENSE.md +0 -22
  50. package/node_modules/bindings/README.md +0 -98
  51. package/node_modules/bindings/bindings.js +0 -221
  52. package/node_modules/bindings/package.json +0 -32
  53. package/node_modules/file-uri-to-path/.npmignore +0 -1
  54. package/node_modules/file-uri-to-path/.travis.yml +0 -30
  55. package/node_modules/file-uri-to-path/History.md +0 -21
  56. package/node_modules/file-uri-to-path/LICENSE +0 -20
  57. package/node_modules/file-uri-to-path/README.md +0 -74
  58. package/node_modules/file-uri-to-path/index.d.ts +0 -2
  59. package/node_modules/file-uri-to-path/index.js +0 -66
  60. package/node_modules/file-uri-to-path/package.json +0 -36
  61. package/node_modules/file-uri-to-path/test/test.js +0 -24
  62. package/node_modules/file-uri-to-path/test/tests.json +0 -13
  63. package/node_modules/glossy/LICENSE +0 -19
  64. package/node_modules/glossy/README.md +0 -129
  65. package/node_modules/glossy/index.js +0 -12
  66. package/node_modules/glossy/lib/glossy/parse.js +0 -520
  67. package/node_modules/glossy/lib/glossy/produce.js +0 -459
  68. package/node_modules/glossy/package.json +0 -47
  69. package/node_modules/glossy/test/decide.js +0 -7
  70. package/node_modules/glossy/test/decode_pri.js +0 -24
  71. package/node_modules/glossy/test/parse_3164.js +0 -104
  72. package/node_modules/glossy/test/parse_5424.js +0 -106
  73. package/node_modules/glossy/test/parse_5848.js +0 -40
  74. package/node_modules/glossy/test/parse_8601.js +0 -14
  75. package/node_modules/glossy/test/parse_rfc3339.js +0 -9
  76. package/node_modules/glossy/test/produce.js +0 -162
  77. package/node_modules/glossy/test/runner.js +0 -40
  78. package/node_modules/glossy/test/structure_data.js +0 -24
  79. package/node_modules/nan/CHANGELOG.md +0 -537
  80. package/node_modules/nan/LICENSE.md +0 -13
  81. package/node_modules/nan/README.md +0 -455
  82. package/node_modules/nan/doc/asyncworker.md +0 -146
  83. package/node_modules/nan/doc/buffers.md +0 -54
  84. package/node_modules/nan/doc/callback.md +0 -76
  85. package/node_modules/nan/doc/converters.md +0 -41
  86. package/node_modules/nan/doc/errors.md +0 -226
  87. package/node_modules/nan/doc/json.md +0 -62
  88. package/node_modules/nan/doc/maybe_types.md +0 -583
  89. package/node_modules/nan/doc/methods.md +0 -664
  90. package/node_modules/nan/doc/new.md +0 -147
  91. package/node_modules/nan/doc/node_misc.md +0 -123
  92. package/node_modules/nan/doc/object_wrappers.md +0 -263
  93. package/node_modules/nan/doc/persistent.md +0 -296
  94. package/node_modules/nan/doc/scopes.md +0 -73
  95. package/node_modules/nan/doc/script.md +0 -38
  96. package/node_modules/nan/doc/string_bytes.md +0 -62
  97. package/node_modules/nan/doc/v8_internals.md +0 -199
  98. package/node_modules/nan/doc/v8_misc.md +0 -85
  99. package/node_modules/nan/include_dirs.js +0 -1
  100. package/node_modules/nan/nan.h +0 -2898
  101. package/node_modules/nan/nan_callbacks.h +0 -88
  102. package/node_modules/nan/nan_callbacks_12_inl.h +0 -514
  103. package/node_modules/nan/nan_callbacks_pre_12_inl.h +0 -520
  104. package/node_modules/nan/nan_converters.h +0 -72
  105. package/node_modules/nan/nan_converters_43_inl.h +0 -68
  106. package/node_modules/nan/nan_converters_pre_43_inl.h +0 -42
  107. package/node_modules/nan/nan_define_own_property_helper.h +0 -29
  108. package/node_modules/nan/nan_implementation_12_inl.h +0 -430
  109. package/node_modules/nan/nan_implementation_pre_12_inl.h +0 -263
  110. package/node_modules/nan/nan_json.h +0 -166
  111. package/node_modules/nan/nan_maybe_43_inl.h +0 -356
  112. package/node_modules/nan/nan_maybe_pre_43_inl.h +0 -268
  113. package/node_modules/nan/nan_new.h +0 -340
  114. package/node_modules/nan/nan_object_wrap.h +0 -156
  115. package/node_modules/nan/nan_persistent_12_inl.h +0 -132
  116. package/node_modules/nan/nan_persistent_pre_12_inl.h +0 -242
  117. package/node_modules/nan/nan_private.h +0 -73
  118. package/node_modules/nan/nan_string_bytes.h +0 -305
  119. package/node_modules/nan/nan_typedarray_contents.h +0 -96
  120. package/node_modules/nan/nan_weak.h +0 -437
  121. package/node_modules/nan/package.json +0 -41
  122. package/node_modules/nan/tools/1to2.js +0 -412
  123. package/node_modules/nan/tools/README.md +0 -14
  124. package/node_modules/nan/tools/package.json +0 -19
  125. package/node_modules/unix-dgram/LICENSE +0 -13
  126. package/node_modules/unix-dgram/README.md +0 -107
  127. package/node_modules/unix-dgram/binding.gyp +0 -20
  128. package/node_modules/unix-dgram/build/Makefile +0 -324
  129. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram/src/unix_dgram.o.d +0 -58
  130. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram.node.d +0 -1
  131. package/node_modules/unix-dgram/build/Release/.deps/Release/unix_dgram.node.d +0 -1
  132. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram/src/unix_dgram.o +0 -0
  133. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram.node +0 -0
  134. package/node_modules/unix-dgram/build/Release/unix_dgram.node +0 -0
  135. package/node_modules/unix-dgram/build/binding.Makefile +0 -6
  136. package/node_modules/unix-dgram/build/config.gypi +0 -213
  137. package/node_modules/unix-dgram/build/unix_dgram.target.mk +0 -159
  138. package/node_modules/unix-dgram/lib/unix_dgram.js +0 -168
  139. package/node_modules/unix-dgram/package.json +0 -36
  140. package/node_modules/unix-dgram/src/unix_dgram.cc +0 -404
  141. package/node_modules/unix-dgram/src/win_dummy.cc +0 -7
  142. package/node_modules/unix-dgram/test/test-connect-callback.js +0 -68
  143. package/node_modules/unix-dgram/test/test-connect.js +0 -53
  144. package/node_modules/unix-dgram/test/test-dgram-unix.js +0 -58
  145. package/node_modules/unix-dgram/test/test-send-error.js +0 -26
  146. package/node_modules/winston-syslog/.eslintrc +0 -7
  147. package/node_modules/winston-syslog/.travis.yml +0 -14
  148. package/node_modules/winston-syslog/CHANGELOG.md +0 -9
  149. package/node_modules/winston-syslog/LICENSE +0 -20
  150. package/node_modules/winston-syslog/README.md +0 -135
  151. package/node_modules/winston-syslog/lib/utils.js +0 -26
  152. package/node_modules/winston-syslog/lib/winston-syslog.js +0 -385
  153. package/node_modules/winston-syslog/package.json +0 -56
  154. package/node_modules/winston-syslog/test/format-test.js +0 -122
  155. package/node_modules/winston-syslog/test/syslog-test.js +0 -95
  156. 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 (require('./http'))(agent);
20
- new (require('./https'))(agent);
21
- new (require('./hapi16'))(agent);
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
- module.exports = function(agent) {
39
- logger.info('applying non-policy hook: http');
38
+ const flatten = (ary) => Array.prototype.join.apply(ary, [',']);
40
39
 
41
- moduleHook.resolve({ name: 'http' }, function(http) {
42
- hookServerPrototype(http.Server.prototype, agent);
43
- });
44
- moduleHook.resolve({ name: 'https' }, function(https) {
45
- hookServerPrototype(https.Server.prototype, agent);
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} proto The Server prototype
60
+ * @param {Server} server The Server prototype
55
61
  */
56
- function hookServerPrototype(proto, agent) {
57
- patcher.patch(proto, 'listen', {
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(data) {
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 } = proto;
68
- proto.emit = function(...args) {
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
- callback: () => {
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
- 'connection.remoteAddress',
146
+ 'socket.remoteAddress',
85
147
  INPUT_TYPES.IP,
86
- getId(req)
148
+ id
87
149
  );
88
150
  agentEmitter.emit('http.requestStart', req, res, ipEvent);
89
151
 
90
- emitSourceEvent(
91
- req,
92
- req,
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
- * Patches ServerResponse instances and runs in AsyncStorage
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
- if (contentType) {
173
- AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, contentType);
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
- patcher.patch(res, 'setHeader', {
181
- alwaysRun: true,
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
- // special HTTP logging for responses.
195
- if (agent.config.agent.logger.log_outbound_http) {
196
- ['end', 'writeHead', 'write'].forEach((method) => {
197
- logHook(res, method);
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
- * Returns http or https depending on information about the
204
- * incomingMessage
205
- *
206
- * @param {IncomingMessage} req
207
- * @return {string} http or https
208
- */
209
- function getId(req) {
210
- if (req == null || req.socket == null) {
211
- return 'http';
212
- } else {
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;
@@ -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 _.some(args, (arg) => arg && tracker.getData(arg).tracked);
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 funciton.
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 === promisify.custom &&
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 = _.clone(options);
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)
@@ -14,44 +14,38 @@ Copyright: 2021 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
- /**
18
- * @module lib/hooks/moduleHook
19
- * @class ModuleHook
20
- */
21
-
22
17
  const Module = require('module');
23
- const agentEmitter = require('../agent-emitter');
24
18
  const RequireHook = require('@contrast/require-hook');
19
+ const agentEmitter = require('../agent-emitter');
25
20
  const logger = require('../core/logger')('hooks:require');
26
21
 
27
22
  class ModuleHook {
28
- constructor(options) {
23
+ constructor() {
29
24
  this.reqHook = new RequireHook(logger);
30
25
 
31
- // RequireHook takes care of hooking
32
- // but still need to patch require to emit 'require' events
33
- const origModRequire = Module.prototype.require;
34
- Module.prototype.require = function(name) {
35
- agentEmitter.emit('require', name);
36
- return origModRequire.call(this, name);
26
+ // RequireHook takes care of hooking but we still need to patch require to
27
+ // emit 'require' events
28
+ const origModLoad = Module._load;
29
+ Module._load = function __loadAndEmit(...args) {
30
+ const [request] = args;
31
+ agentEmitter.emit('require', request);
32
+ return Reflect.apply(origModLoad, this, args);
37
33
  };
38
34
  }
39
35
 
40
36
  /**
41
- *
42
37
  * Collects instrumentation functions that will run on the specified
43
- * module's file at the time it is loaded via <code>require</code>.
44
- * @param {Object} descriptor describes module you're hook(name, version, file)
45
- * @param {Function} instrumentation The function to run on the module at
46
- * the time it is required in the code
38
+ * module's file at the time it is loaded via `require`.
39
+ * @param {RequireHook.Descriptor} descriptor describes the module to hook
40
+ * @param {Function} handler The function to run on the module at the time it is required
47
41
  */
48
- resolve(descriptor, instrumentation) {
49
- this.reqHook.resolve(descriptor, instrumentation);
42
+ resolve(descriptor, handler) {
43
+ this.reqHook.resolve(descriptor, handler);
50
44
  }
51
45
 
52
46
  /**
53
- * reset the hooked state for a module so that it's handlers re-run (used in unit tests)
54
- *
47
+ * Reset the hooked state for a module so that it's handlers re-run.
48
+ * NOTE: used in unit tests.
55
49
  * @param {string} name the name of the module
56
50
  */
57
51
  reset(name) {
@@ -40,9 +40,6 @@ module.exports = function instrument(agent, reporter) {
40
40
  return;
41
41
  }
42
42
 
43
- const stackFactory = require('./core/stacktrace').singleton;
44
- stackFactory.stackTraceLimit = agent.config.agent.stack_trace_limit;
45
-
46
43
  // The order of these matters, do not re-order
47
44
  collectLibInfo(agent);
48
45
  // (CONTRAST-31526) this must be required first to load global.ContrastMethods
@@ -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 (const byte of word) {
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 SINK_EXPLOIT_PATTERN = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)([-/].*)*[-/][a-zA-Z]*c/i;
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
- (SINK_EXPLOIT_PATTERN.test(this.normalizedCmd) &&
100
- this.normalizedCmd.endsWith(normalizedParam))
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('[a-zA-Z]+\\s+[a-zA-Z]+');
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('[a-zA-Z]+\\s+[a-zA-Z]+');
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
  }
@@ -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(/\((.*?):(\d+):\d+\)/);
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 occurrances of substrings that look like
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 patt = /^\[\s*(?:`|'|")([a-zA-Z_]+[a-zA-Z0-9_]*)(?:`|'|")\s*\]$/;
62
- const match = patt.exec(bracketed);
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