@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.
Files changed (157) 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/membrane/source-membrane.js +4 -18
  6. package/lib/assess/policy/propagators.json +11 -21
  7. package/lib/assess/policy/rules.json +5 -0
  8. package/lib/assess/policy/signatures.json +15 -0
  9. package/lib/assess/propagators/dustjs/escape-html.js +22 -0
  10. package/lib/assess/propagators/dustjs/escape-js.js +22 -0
  11. package/lib/assess/propagators/encode-uri/encode-uri-component.js +22 -0
  12. package/lib/assess/propagators/encode-uri/encode-uri.js +22 -0
  13. package/lib/assess/propagators/index.js +0 -2
  14. package/lib/assess/propagators/joi/values.js +26 -11
  15. package/lib/assess/propagators/mustache/escape.js +22 -0
  16. package/lib/assess/propagators/path/common.js +155 -46
  17. package/lib/assess/propagators/path/join.js +5 -1
  18. package/lib/assess/propagators/path/normalize.js +1 -2
  19. package/lib/assess/propagators/path/resolve.js +11 -2
  20. package/lib/assess/propagators/template-escape.js +84 -0
  21. package/lib/assess/propagators/templates.js +2 -3
  22. package/lib/assess/sinks/dustjs-linkedin-xss.js +131 -0
  23. package/lib/core/arch-components/dynamodb.js +1 -2
  24. package/lib/core/arch-components/dynamodbv3.js +44 -0
  25. package/lib/core/arch-components/index.js +1 -0
  26. package/lib/core/arch-components/rethinkdb.js +53 -0
  27. package/lib/core/async-storage/hooks/bluebird.js +20 -0
  28. package/lib/core/config/options.js +2 -1
  29. package/lib/core/stacktrace.js +3 -4
  30. package/lib/feature-set.js +2 -1
  31. package/lib/hooks/frameworks/base.js +8 -2
  32. package/lib/hooks/frameworks/http.js +23 -16
  33. package/lib/hooks/frameworks/http2.js +73 -0
  34. package/lib/hooks/frameworks/index.js +8 -3
  35. package/lib/hooks/http.js +112 -128
  36. package/lib/hooks/patcher.js +69 -48
  37. package/lib/hooks/require.js +16 -22
  38. package/lib/instrumentation.js +0 -3
  39. package/lib/protect/rules/cmd-injection-command-backdoors/backdoor-detector.js +3 -3
  40. package/lib/protect/rules/signatures/reflected-xss/helpers/function-call.js +1 -1
  41. package/lib/protect/rules/xss/helpers/function-call.js +1 -1
  42. package/lib/util/clean-stack.js +1 -1
  43. package/lib/util/clean-string/brackets.js +3 -3
  44. package/lib/util/ip-analyzer.js +1 -1
  45. package/lib/util/some.js +27 -0
  46. package/lib/util/source-map.js +1 -1
  47. package/lib/util/xml-analyzer/external-entity-finder.js +1 -1
  48. package/package.json +14 -16
  49. package/lib/hooks/frameworks/https.js +0 -42
  50. package/node_modules/bindings/LICENSE.md +0 -22
  51. package/node_modules/bindings/README.md +0 -98
  52. package/node_modules/bindings/bindings.js +0 -221
  53. package/node_modules/bindings/package.json +0 -32
  54. package/node_modules/file-uri-to-path/.npmignore +0 -1
  55. package/node_modules/file-uri-to-path/.travis.yml +0 -30
  56. package/node_modules/file-uri-to-path/History.md +0 -21
  57. package/node_modules/file-uri-to-path/LICENSE +0 -20
  58. package/node_modules/file-uri-to-path/README.md +0 -74
  59. package/node_modules/file-uri-to-path/index.d.ts +0 -2
  60. package/node_modules/file-uri-to-path/index.js +0 -66
  61. package/node_modules/file-uri-to-path/package.json +0 -36
  62. package/node_modules/file-uri-to-path/test/test.js +0 -24
  63. package/node_modules/file-uri-to-path/test/tests.json +0 -13
  64. package/node_modules/glossy/LICENSE +0 -19
  65. package/node_modules/glossy/README.md +0 -129
  66. package/node_modules/glossy/index.js +0 -12
  67. package/node_modules/glossy/lib/glossy/parse.js +0 -520
  68. package/node_modules/glossy/lib/glossy/produce.js +0 -459
  69. package/node_modules/glossy/package.json +0 -47
  70. package/node_modules/glossy/test/decide.js +0 -7
  71. package/node_modules/glossy/test/decode_pri.js +0 -24
  72. package/node_modules/glossy/test/parse_3164.js +0 -104
  73. package/node_modules/glossy/test/parse_5424.js +0 -106
  74. package/node_modules/glossy/test/parse_5848.js +0 -40
  75. package/node_modules/glossy/test/parse_8601.js +0 -14
  76. package/node_modules/glossy/test/parse_rfc3339.js +0 -9
  77. package/node_modules/glossy/test/produce.js +0 -162
  78. package/node_modules/glossy/test/runner.js +0 -40
  79. package/node_modules/glossy/test/structure_data.js +0 -24
  80. package/node_modules/nan/CHANGELOG.md +0 -537
  81. package/node_modules/nan/LICENSE.md +0 -13
  82. package/node_modules/nan/README.md +0 -455
  83. package/node_modules/nan/doc/asyncworker.md +0 -146
  84. package/node_modules/nan/doc/buffers.md +0 -54
  85. package/node_modules/nan/doc/callback.md +0 -76
  86. package/node_modules/nan/doc/converters.md +0 -41
  87. package/node_modules/nan/doc/errors.md +0 -226
  88. package/node_modules/nan/doc/json.md +0 -62
  89. package/node_modules/nan/doc/maybe_types.md +0 -583
  90. package/node_modules/nan/doc/methods.md +0 -664
  91. package/node_modules/nan/doc/new.md +0 -147
  92. package/node_modules/nan/doc/node_misc.md +0 -123
  93. package/node_modules/nan/doc/object_wrappers.md +0 -263
  94. package/node_modules/nan/doc/persistent.md +0 -296
  95. package/node_modules/nan/doc/scopes.md +0 -73
  96. package/node_modules/nan/doc/script.md +0 -38
  97. package/node_modules/nan/doc/string_bytes.md +0 -62
  98. package/node_modules/nan/doc/v8_internals.md +0 -199
  99. package/node_modules/nan/doc/v8_misc.md +0 -85
  100. package/node_modules/nan/include_dirs.js +0 -1
  101. package/node_modules/nan/nan.h +0 -2898
  102. package/node_modules/nan/nan_callbacks.h +0 -88
  103. package/node_modules/nan/nan_callbacks_12_inl.h +0 -514
  104. package/node_modules/nan/nan_callbacks_pre_12_inl.h +0 -520
  105. package/node_modules/nan/nan_converters.h +0 -72
  106. package/node_modules/nan/nan_converters_43_inl.h +0 -68
  107. package/node_modules/nan/nan_converters_pre_43_inl.h +0 -42
  108. package/node_modules/nan/nan_define_own_property_helper.h +0 -29
  109. package/node_modules/nan/nan_implementation_12_inl.h +0 -430
  110. package/node_modules/nan/nan_implementation_pre_12_inl.h +0 -263
  111. package/node_modules/nan/nan_json.h +0 -166
  112. package/node_modules/nan/nan_maybe_43_inl.h +0 -356
  113. package/node_modules/nan/nan_maybe_pre_43_inl.h +0 -268
  114. package/node_modules/nan/nan_new.h +0 -340
  115. package/node_modules/nan/nan_object_wrap.h +0 -156
  116. package/node_modules/nan/nan_persistent_12_inl.h +0 -132
  117. package/node_modules/nan/nan_persistent_pre_12_inl.h +0 -242
  118. package/node_modules/nan/nan_private.h +0 -73
  119. package/node_modules/nan/nan_string_bytes.h +0 -305
  120. package/node_modules/nan/nan_typedarray_contents.h +0 -96
  121. package/node_modules/nan/nan_weak.h +0 -437
  122. package/node_modules/nan/package.json +0 -41
  123. package/node_modules/nan/tools/1to2.js +0 -412
  124. package/node_modules/nan/tools/README.md +0 -14
  125. package/node_modules/nan/tools/package.json +0 -19
  126. package/node_modules/unix-dgram/LICENSE +0 -13
  127. package/node_modules/unix-dgram/README.md +0 -107
  128. package/node_modules/unix-dgram/binding.gyp +0 -20
  129. package/node_modules/unix-dgram/build/Makefile +0 -324
  130. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram/src/unix_dgram.o.d +0 -58
  131. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram.node.d +0 -1
  132. package/node_modules/unix-dgram/build/Release/.deps/Release/unix_dgram.node.d +0 -1
  133. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram/src/unix_dgram.o +0 -0
  134. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram.node +0 -0
  135. package/node_modules/unix-dgram/build/Release/unix_dgram.node +0 -0
  136. package/node_modules/unix-dgram/build/binding.Makefile +0 -6
  137. package/node_modules/unix-dgram/build/config.gypi +0 -213
  138. package/node_modules/unix-dgram/build/unix_dgram.target.mk +0 -159
  139. package/node_modules/unix-dgram/lib/unix_dgram.js +0 -168
  140. package/node_modules/unix-dgram/package.json +0 -36
  141. package/node_modules/unix-dgram/src/unix_dgram.cc +0 -404
  142. package/node_modules/unix-dgram/src/win_dummy.cc +0 -7
  143. package/node_modules/unix-dgram/test/test-connect-callback.js +0 -68
  144. package/node_modules/unix-dgram/test/test-connect.js +0 -53
  145. package/node_modules/unix-dgram/test/test-dgram-unix.js +0 -58
  146. package/node_modules/unix-dgram/test/test-send-error.js +0 -26
  147. package/node_modules/winston-syslog/.eslintrc +0 -7
  148. package/node_modules/winston-syslog/.travis.yml +0 -14
  149. package/node_modules/winston-syslog/CHANGELOG.md +0 -9
  150. package/node_modules/winston-syslog/LICENSE +0 -20
  151. package/node_modules/winston-syslog/README.md +0 -135
  152. package/node_modules/winston-syslog/lib/utils.js +0 -26
  153. package/node_modules/winston-syslog/lib/winston-syslog.js +0 -385
  154. package/node_modules/winston-syslog/package.json +0 -56
  155. package/node_modules/winston-syslog/test/format-test.js +0 -122
  156. package/node_modules/winston-syslog/test/syslog-test.js +0 -95
  157. 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
- 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) {
@@ -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, options, data, fn, thisTarget) {
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 _.some(args, (arg) => arg && tracker.getData(arg).tracked);
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
- function recordTime(func, funcKey, type) {
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
- // Run the pre event in a no instrumentation scope
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
- Scopes.runInNoInstrumentationScope(
256
- recordTime(
257
- () => runHooks('pre', options, data, hooked, this),
258
- funcKey,
259
- 'pre'
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
- // run original function in scope that was passed in
265
- data.result = Scopes.runIn(
266
- options.scope,
267
- recordTime(() => getResult.call(this, fn, data, target), funcKey, 'orig'),
268
- funcKey
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 the post event in a no instrumentation scope
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
- Scopes.runInNoInstrumentationScope(
275
- recordTime(
276
- () => runHooks('post', options, data, hooked, this),
277
- funcKey,
278
- 'post'
279
- ),
280
- `${funcKey} post`
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 === promisify.custom &&
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 = _.clone(options);
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
@@ -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
@@ -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
@@ -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(/\s*,\s*/) : [];
69
+ return header ? header.split(',').map((x) => x.trim()) : [];
70
70
  };
71
71
 
72
72
  /**
@@ -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;