@contrast/protect 1.10.0 → 1.12.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.
@@ -29,6 +29,26 @@ module.exports = function(core) {
29
29
 
30
30
  const express4ErrorHandler = protect.errorHandlers.express4ErrorHandler = {};
31
31
 
32
+ function aroundFn(name) {
33
+ return function around(orig, data) {
34
+ const [err] = data.args;
35
+ const sourceContext = protect.getSourceContext(name);
36
+ const isSecurityException = SecurityException.isSecurityException(err);
37
+
38
+ if (isSecurityException && sourceContext) {
39
+ const blockInfo = sourceContext.securityException;
40
+
41
+ sourceContext.block(...blockInfo);
42
+ return;
43
+ }
44
+
45
+ if (!sourceContext && isSecurityException) {
46
+ logger.info('source context not found; unable to handle response');
47
+ }
48
+ return orig();
49
+ };
50
+ }
51
+
32
52
  express4ErrorHandler.install = function () {
33
53
  depHooks.resolve({ name: 'finalhandler' }, (finalhandler) =>
34
54
  patcher.patch(finalhandler, {
@@ -38,23 +58,7 @@ module.exports = function(core) {
38
58
  data.result = patcher.patch(data.result, {
39
59
  name: 'finalHandler.returnedFunction',
40
60
  patchType,
41
- around(orig, data) {
42
- const [err] = data.args;
43
- const sourceContext = protect.getSourceContext('finalHandler');
44
- const isSecurityException = SecurityException.isSecurityException(err);
45
-
46
- if (isSecurityException && sourceContext) {
47
- const blockInfo = sourceContext.securityException;
48
-
49
- sourceContext.block(...blockInfo);
50
- return;
51
- }
52
-
53
- if (!sourceContext && isSecurityException) {
54
- logger.info('source context not found; unable to handle response');
55
- }
56
- return orig();
57
- },
61
+ around: aroundFn('finalHandler')
58
62
  });
59
63
  },
60
64
  })
@@ -64,23 +68,7 @@ module.exports = function(core) {
64
68
  patcher.patch(Layer.prototype, 'handle_error', {
65
69
  name: 'Layer.prototype.handle_error',
66
70
  patchType,
67
- around(orig, data) {
68
- const [err] = data.args;
69
- const sourceContext = protect.getSourceContext('express.Layer.handle_error');
70
- const isSecurityException = SecurityException.isSecurityException(err);
71
-
72
- if (isSecurityException && sourceContext) {
73
- const blockInfo = sourceContext.securityException;
74
-
75
- sourceContext.block(...blockInfo);
76
- return;
77
- }
78
-
79
- if (!sourceContext && isSecurityException) {
80
- logger.info('source context not found; unable to handle response');
81
- }
82
- return orig();
83
- }
71
+ around: aroundFn('express.Layer.handle_error')
84
72
  });
85
73
 
86
74
  // This should be revisited after the research ticket NODE-2556
@@ -39,7 +39,7 @@ module.exports = function(core) {
39
39
  hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
40
40
  const ruleId = 'untrusted-deserialization';
41
41
  const mode = sourceContext.policy[ruleId];
42
- const { name, value, stacktraceData } = sinkContext;
42
+ const { name, value, stacktraceOpts } = sinkContext;
43
43
 
44
44
  if (mode === 'off') return;
45
45
 
@@ -49,7 +49,7 @@ module.exports = function(core) {
49
49
  const blocked = BLOCKING_MODES.includes(mode);
50
50
  const results = getResults(sourceContext, ruleId);
51
51
 
52
- captureStacktrace(sinkContext, stacktraceData);
52
+ captureStacktrace(sinkContext, stacktraceOpts);
53
53
  results.push({
54
54
  value: sinkContext.value,
55
55
  blocked,
@@ -45,7 +45,7 @@ module.exports = function(core) {
45
45
  const sinkContext = {
46
46
  name: `${name}.${method}`,
47
47
  value,
48
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
48
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
49
49
  };
50
50
  hardening.handleUntrustedDeserialization(sourceContext, sinkContext);
51
51
  },
package/lib/index.d.ts CHANGED
@@ -26,28 +26,6 @@ type Http = typeof http;
26
26
  type Https = typeof https;
27
27
 
28
28
  export type Block = (mode: string, ruleId: string) => void;
29
- export class HttpInstrumentation {
30
- messages: Messages;
31
- scope: Sources;
32
- config: Config;
33
- logger: Logger;
34
- depHooks: RequireHook;
35
- protect: ProtectMessage;
36
- makeSourceContext: Protect['makeSourceContext'];
37
- maxBodySize: number;
38
- installed: boolean;
39
-
40
- constructor(core: any);
41
-
42
- install(): void;
43
- uninstall(): void; //NYI
44
- hookHttp(): void;
45
- hookHttps(): void;
46
- hookServer(xport: Http | Https): void;
47
- initiateRequestHandling(fnContext: { instance: any, method: any, args: any }): void; //TODO
48
- removeCookies(headers: string[]): string[];
49
- }
50
-
51
29
  export interface ProtectRequestStore {
52
30
  reqData: ReqData;
53
31
  block: Block;
@@ -95,7 +73,7 @@ export interface Protect {
95
73
  expressInstrumentation: { install: () => void },
96
74
  fastifyInstrumentation: { install: () => void },
97
75
  koaInstrumentation: { install: () => void },
98
- httpInstrumentation: HttpInstrumentation,
76
+ httpInstrumentation: { install: () => void },
99
77
  install: () => void,
100
78
  },
101
79
  inputTracing: {
@@ -15,203 +15,57 @@
15
15
 
16
16
  'use strict';
17
17
 
18
+ const { patchType } = require('../constants');
18
19
  const { Event } = require('@contrast/common');
19
20
 
20
- // Instruments http `Server` and `IncomingMessage` instances to support input
21
- // analysis in framework-agnostic manner.
22
-
23
21
  module.exports = function(core) {
24
- const { protect: { inputAnalysis } } = core;
25
- inputAnalysis.httpInstrumentation = new HttpInstrumentation(core);
26
- return inputAnalysis.httpInstrumentation;
27
- };
28
- class HttpInstrumentation {
29
- constructor(core) {
30
- this.messages = core.messages;
31
- this.scope = core.scopes.sources;
32
- this.config = core.config;
33
- this.logger = core.logger.child('contrast:protect:input-analysis');
34
- this.depHooks = core.depHooks;
35
- this.protect = core.protect;
36
- this.patcher = core.patcher;
37
- this.makeSourceContext = this.protect.makeSourceContext;
38
- this.maxBodySize = 16 * 1024 * 1024;
39
- this.installed = false;
40
- }
41
-
42
- /**
43
- * After checking whether the sensor is enabled, will set up `require` hooks
44
- * for instrumenting both `http` and `https` modules when they load.
45
- */
46
- install() {
47
- if (this.installed) {
48
- return;
49
- }
50
-
51
- this.installed = true;
52
- this.hookHttp();
53
- this.hookHttps();
54
- this.hookHttp2();
55
- }
56
-
57
- uninstall() {
58
- return null; //NYI
59
- }
60
-
61
- /**
62
- * Sets hooks to instrument `http.Server.prototype`.
63
- */
64
- hookHttp() {
65
- this.logger.debug('hooking library: http');
66
- this.depHooks.resolve({ name: 'http' }, (http) => this.hookServerEmit.call(this, http, 'httpServer'));
67
- }
68
-
69
- /**
70
- * Sets hooks to instrument `https.Server.prototype`.
71
- */
72
- hookHttps() {
73
- this.logger.debug('hooking library: https');
74
- this.depHooks.resolve({ name: 'https' }, (https) => this.hookServerEmit.call(this, https, 'httpsServer'));
75
- }
76
-
77
- /**
78
- * Sets hooks to instrument `http2 Servers`.
79
- */
80
- hookHttp2() {
81
- this.logger.debug('hooking library: http2');
82
- // http2 library does not expose its Server class, so we need to hook the createServer function
83
- this.depHooks.resolve({ name: 'http2' }, (http2) => this.hookCreateServer.call(this, http2, 'http2Server'));
84
- this.depHooks.resolve({ name: 'http2' }, (http2) => this.hookCreateServer.call(this, http2, 'http2SecureServer', 'createSecureServer'));
85
-
86
- this.logger.debug('hooking library: spdy');
87
- this.depHooks.resolve({ name: 'spdy' }, (spdy) => this.hookServerEmit.call(this, spdy, 'spdyServer'));
88
- }
89
-
90
- /**
91
- * Instruments the `Server` prototype from `http(s)` or spdy's http2 Server. This patches `emit` and
92
- * invokes the protect service to do analysis when appropriate.
93
- */
94
- hookServerEmit(serverSource, sourceName) {
95
- serverSource.Server.prototype = this.patcher.patch(serverSource.Server.prototype, 'emit', {
96
- name: `${sourceName}.Server.prototype.emit`,
97
- patchType: 'initiate-handling',
98
- around: this.emitAroundHook.bind(this)
99
- });
100
- }
101
-
102
- /**
103
- * Instruments the `Http2Server` prototype which results from the http2.createServer/createSecureServer() call.
104
- * This also patches `emit` and
105
- * invokes the protect service to do analysis when appropriate.
106
- */
107
- hookCreateServer(serverSource, sourceName, constructorName = 'createServer') {
108
- const self = this;
109
-
110
- return this.patcher.patch(serverSource, constructorName, {
111
- name: sourceName,
112
- patchType: 'initiate-handling',
113
- post(data) {
114
-
115
- const { result: server } = data;
116
- const serverPrototype = server ? Object.getPrototypeOf(server) : null;
117
-
118
- if (!serverPrototype) {
119
- self.logger.error('Unable to patch server prototype, continue without instrumentation');
120
- return;
121
- }
122
-
123
- self.patcher.patch(serverPrototype, 'emit', {
124
- name: `${sourceName}.Server.prototype.emit`,
125
- patchType: 'req-async-storage',
126
- around: self.emitAroundHook.bind(self)
127
- });
22
+ const {
23
+ logger,
24
+ messages,
25
+ scopes: { sources },
26
+ instrumentation: { instrument },
27
+ protect: { inputAnalysis },
28
+ } = core;
29
+
30
+ function removeCookies(headers) {
31
+ for (let i = 0; i < headers.length; i += 2) {
32
+ if (headers[i] === 'cookies') {
33
+ headers = headers.slice();
34
+ headers.splice(i, 2);
128
35
  }
129
- });
36
+ }
37
+ return headers;
130
38
  }
131
39
 
132
- /**
133
- * The around hook for `emit` that
134
- * invokes the protect service to do analysis when appropriate.
135
- */
136
- emitAroundHook(next, data) {
137
- const [type] = data.args;
40
+ function around(next, data) {
41
+ let store, block;
42
+ const { args: [type, req, res] } = data;
138
43
 
139
44
  if (type !== 'request') {
140
45
  return next();
141
46
  }
142
47
 
143
- const context = { instance: data.obj, method: next, args: data.args };
144
- this.initiateRequestHandling(context);
145
- return !!data.obj._events[type];
146
- }
147
-
148
- /**
149
- * Creates the sourceContext for the request and invokes the handler for
150
- * inputs that are present in the 'incomingMessage' object at the time of
151
- * the 'connect' event.
152
- *
153
- * @param {Object} context Function invocation context
154
- */
155
- initiateRequestHandling(fnContext) {
156
- const {
157
- instance,
158
- method,
159
- args,
160
- args: [, req, res]
161
- } = fnContext;
162
-
163
- let store;
164
- let block;
165
-
166
48
  try {
167
- const { messages, protect: { inputAnalysis } } = this; // the functions that do input analysis
168
-
169
- // this must be invoked by the patching code using scope.sources.run({}, ...)
170
- // so that an async context is present.
171
- store = this.scope.getStore();
172
- // nothing can be done if async context is not available.
173
-
49
+ store = sources.getStore();
174
50
  if (!store) {
175
- this.logger.debug('cannot acquire store for initiateRequestHandling()');
176
- setImmediate(() => method.call(instance, ...args));
177
- return;
178
- }
179
- store.protect = this.makeSourceContext(req, res);
180
-
181
- if (store.protect.allowed) {
182
- setImmediate(() => method.call(instance, ...args));
51
+ logger.debug('cannot acquire store for around()');
183
52
  return;
184
53
  }
185
54
 
186
- const { reqData } = store.protect;
55
+ store.protect = core.protect.makeSourceContext(req, res);
56
+ const {
57
+ reqData: { headers, uriPath, method }
58
+ } = store.protect;
187
59
 
188
60
  res.on('finish', () => {
189
- this.protect.inputAnalysis.handleRequestEnd(store.protect);
61
+ inputAnalysis.handleRequestEnd(store.protect);
190
62
  messages.emit(Event.PROTECT, store);
191
63
  });
192
64
 
193
- // don't put inputs in the store; they are a param to each handler. findings
194
- // associated with inputs do go into the store. why not put the inputs
195
- // into the store? after all, the inputs come from the store. mostly because
196
- // they can really add up to a lot of data that isn't going to be used.
197
- //
198
- // how to replace result in resultsList, e.g., queries find something
199
- // but then framework emits parsed queries? does this only matter for
200
- // no-sql? index-lookup or hash?
201
- //
202
- // create inputs for this handler. we defer cookies until the framework
203
- // parses them because there is no way to be certain of their formatting
204
- // and encoding.
205
- //
206
- // the primary reason for this is to avoid passing the incomingMessage,
207
- // req, to all the handlers allowing direct access to it and tightly
208
- // coupling all handlers to an extensive collection of data.
209
65
  const connectInputs = {
210
- headers: HttpInstrumentation.removeCookies(reqData.headers),
211
- uriPath: reqData.uriPath,
212
- rawUrl: req.url,
213
- // TODO AGENT-203 - need to handle method-tampering rule.
214
- method: reqData.method,
66
+ headers: removeCookies(headers),
67
+ uriPath,
68
+ method
215
69
  };
216
70
 
217
71
  if (inputAnalysis.virtualPatchesEvaluators?.length) {
@@ -229,25 +83,61 @@ class HttpInstrumentation {
229
83
 
230
84
  block = block || inputAnalysis.handleConnect(store.protect, connectInputs);
231
85
  } catch (err) {
232
- this.logger.error({ err }, 'Error during input analysis');
86
+ logger.error({ err }, 'Error during input analysis');
233
87
  }
234
88
 
235
89
  if (!block) {
236
- setImmediate(() => method.call(instance, ...args));
90
+ setImmediate(() => next.call(data.obj, ...data.args));
237
91
  } else {
238
92
  store.protect.block(...block);
239
- this.logger.debug({ block }, 'request blocked by not emitting request event');
93
+ logger.debug({ block }, 'request blocked by not emitting request event');
240
94
  }
241
95
  }
242
96
 
243
- static removeCookies(headers) {
244
- for (let i = 0; i < headers.length; i += 2) {
245
- if (headers[i] === 'cookies') {
246
- headers = headers.slice();
247
- headers.splice(i, 2);
248
- return headers;
249
- }
250
- }
251
- return headers;
97
+ function install() {
98
+ [{
99
+ moduleName: 'http'
100
+ },
101
+ {
102
+ moduleName: 'https'
103
+ },
104
+ {
105
+ moduleName: 'spdy'
106
+ },
107
+ {
108
+ moduleName: 'http2',
109
+ patchObjectsProps: [
110
+ {
111
+ methods: ['createServer', 'createSecureServer'],
112
+ patchType,
113
+ patchObjects: [
114
+ {
115
+ name: 'Server.prototype',
116
+ methods: ['emit'],
117
+ patchType,
118
+ around
119
+ }
120
+ ]
121
+ }
122
+ ]
123
+ }].forEach(({ moduleName, patchObjectsProps }) => {
124
+ const patchObjects = patchObjectsProps || [
125
+ {
126
+ name: 'Server.prototype',
127
+ methods: ['emit'],
128
+ patchType,
129
+ around
130
+ }
131
+ ];
132
+ instrument({
133
+ moduleName,
134
+ patchObjects
135
+ });
136
+ });
252
137
  }
253
- }
138
+
139
+ return inputAnalysis.httpInstrumentation = {
140
+ install,
141
+ around
142
+ };
143
+ };
@@ -35,8 +35,8 @@ module.exports = function(core) {
35
35
  } = core;
36
36
 
37
37
  function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
38
- const { stacktraceData } = sinkContext;
39
- captureStacktrace(sinkContext, stacktraceData);
38
+ const { stacktraceOpts } = sinkContext;
39
+ captureStacktrace(sinkContext, stacktraceOpts);
40
40
  result.exploitMetadata.push({ sinkContext, findings });
41
41
 
42
42
  const mode = sourceContext.policy[ruleId];
@@ -142,18 +142,33 @@ module.exports = function(core) {
142
142
  }
143
143
  }
144
144
 
145
+
145
146
  if (stringInjectionResults) {
146
147
  let stringFindings = null;
147
148
 
148
149
  for (const result of stringInjectionResults) {
149
150
  if (typeof sinkContext.value === 'object') {
150
- traverseKeysAndValues(sinkContext.value, function(path, type, value) {
151
+ traverseKeysAndValues(sinkContext.value, function(path, type, value, obj) {
151
152
  if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
152
153
 
153
- stringFindings = handleStringValue(result, sinkContext.value[value], agentLib);
154
+ if (!stringFindings && type == 'Key' && value == '$accumulator') {
155
+ stringFindings =
156
+ handleStringValue(result, obj[value]?.['init'], agentLib) ||
157
+ handleStringValue(result, obj[value]?.['merge'], agentLib) ||
158
+ handleStringValue(result, obj[value]?.['finalize'], agentLib) ||
159
+ handleStringValue(result, obj[value]?.['accumulate'], agentLib);
160
+ }
161
+
162
+ if (!stringFindings && type == 'Key' && value == '$function') {
163
+ stringFindings =
164
+ handleStringValue(result, obj['$function']?.body, agentLib);
165
+ }
166
+
167
+ if (!stringFindings) {
168
+ stringFindings = handleStringValue(result, obj[value], agentLib);
169
+ }
154
170
 
155
- // halt traversal
156
- return true;
171
+ if (stringFindings) return true;
157
172
  });
158
173
  } else if (typeof sinkContext.value === 'string') {
159
174
  stringFindings = handleStringValue(result, sinkContext.value, agentLib);
@@ -254,7 +269,10 @@ function getResultsByRuleId(ruleId, context) {
254
269
  if (context.policy[ruleId] === OFF) {
255
270
  return;
256
271
  }
257
- return context.resultsMap[ruleId];
272
+ // because agent-lib stores all nosql-injection results under nosql-injection-mongo
273
+ const resultsMapRuleId = ruleId === 'nosql-injection' ? 'nosql-injection-mongo' : ruleId;
274
+
275
+ return context.resultsMap[resultsMapRuleId];
258
276
  }
259
277
 
260
278
  function handleObjectValue(result, object) {
@@ -289,9 +307,11 @@ function handleStringValue(result, cmd, agentLib) {
289
307
  if (typeof cmd !== 'string') {
290
308
  return null;
291
309
  }
310
+
292
311
  let findings = null;
312
+ let inputIndex = -1;
313
+ inputIndex = cmd.indexOf(result.value);
293
314
 
294
- const inputIndex = cmd.indexOf(result.value);
295
315
  // if the user input is not in the sink input, there is nothing to do.
296
316
  if (inputIndex === -1) {
297
317
  return findings;
@@ -27,6 +27,7 @@ module.exports = function(core) {
27
27
  require('./install/child-process')(core);
28
28
  require('./install/fs')(core);
29
29
  require('./install/mongodb')(core);
30
+ require('./install/marsdb')(core);
30
31
  require('./install/mysql')(core);
31
32
  require('./install/postgres')(core);
32
33
  require('./install/sequelize')(core);
@@ -37,7 +37,7 @@ module.exports = function(core) {
37
37
  const sinkContext = {
38
38
  name,
39
39
  value,
40
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
40
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
41
41
  };
42
42
 
43
43
  inputTracing.handleCommandInjection(sourceContext, sinkContext);
@@ -47,7 +47,7 @@ module.exports = function(core) {
47
47
  const sinkContext = {
48
48
  name: 'eval',
49
49
  value,
50
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
50
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
51
51
  };
52
52
  inputTracing.ssjsInjection(sourceContext, sinkContext);
53
53
  }
@@ -103,7 +103,7 @@ module.exports = function(core) {
103
103
  const sinkContext = {
104
104
  name,
105
105
  value,
106
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
106
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
107
107
  };
108
108
  inputTracing.handlePathTraversal(sourceContext, sinkContext);
109
109
  core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
@@ -48,7 +48,7 @@ module.exports = function(core) {
48
48
  const sinkContext = {
49
49
  name: 'Function',
50
50
  value: fnBody,
51
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
51
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
52
52
  };
53
53
 
54
54
  inputTracing.ssjsInjection(sourceContext, sinkContext);
@@ -46,7 +46,7 @@ module.exports = function(core) {
46
46
  const sinkContext = {
47
47
  name,
48
48
  value,
49
- stacktraceData: { constructorOpt: data.hooked },
49
+ stacktraceOpts: { constructorOpt: data.hooked },
50
50
  };
51
51
  inputTracing.handleReflectedXss(sourceContext, sinkContext);
52
52
  }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * Copyright: 2022 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
+
16
+ 'use strict';
17
+ const { patchType } = require('../constants');
18
+
19
+ module.exports = function (core) {
20
+ const {
21
+ depHooks,
22
+ patcher,
23
+ protect: { getSourceContext, inputTracing },
24
+ } = core;
25
+
26
+ const methods = ['find', 'findOne', 'update', 'insert', 'remove', 'ids', 'count'];
27
+
28
+ function getCursorQueryData(args) {
29
+ const query = args[0];
30
+ if (!query) {
31
+ return;
32
+ }
33
+
34
+ if (Object.prototype.toString.call(query) === '[object String]') {
35
+ return query.toString();
36
+ }
37
+
38
+ if (query['$where']) {
39
+ return query['$where'].toString();
40
+ }
41
+
42
+ return query;
43
+ }
44
+
45
+ function install() {
46
+ depHooks.resolve({ name: 'marsdb' }, marsdb => {
47
+ methods.forEach((method) => {
48
+ const name = `marsdb.Collection.prototype.${method}`;
49
+
50
+ patcher.patch(marsdb.Collection.prototype, method, {
51
+ name,
52
+ patchType,
53
+ pre({ args, hooked, orig }) {
54
+ const value = getCursorQueryData(args);
55
+ const sourceContext = getSourceContext(name);
56
+
57
+ if (
58
+ !sourceContext ||
59
+ !value ||
60
+ !Object.keys(value).length
61
+ ) return;
62
+
63
+ const sinkContext = {
64
+ name,
65
+ value,
66
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
67
+ };
68
+ inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
69
+ }
70
+ });
71
+ });
72
+ });
73
+ }
74
+
75
+ const marsdbInstr = (core.protect.inputTracing.marsdbInstrumentation = {
76
+ install,
77
+ });
78
+
79
+ return marsdbInstr;
80
+ };
@@ -62,7 +62,7 @@ module.exports = function (core) {
62
62
  const sinkContext = {
63
63
  name,
64
64
  value,
65
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
65
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
66
66
  };
67
67
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
68
68
  }
@@ -122,6 +122,7 @@ module.exports = function (core) {
122
122
  'countDocuments',
123
123
  'count',
124
124
  'distinct',
125
+ 'aggregate'
125
126
  ],
126
127
  patchType,
127
128
  preHookFn: v4CollectionVal
@@ -58,7 +58,7 @@ module.exports = function(core) {
58
58
  const sinkContext = {
59
59
  name,
60
60
  value,
61
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
61
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
62
62
  };
63
63
 
64
64
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -42,7 +42,7 @@ module.exports = function(core) {
42
42
  const sinkContext = {
43
43
  name,
44
44
  value,
45
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
45
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
46
46
  };
47
47
 
48
48
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -51,7 +51,7 @@ module.exports = function(core) {
51
51
  const sinkContext = {
52
52
  name,
53
53
  value,
54
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
54
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
55
55
  };
56
56
 
57
57
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -46,7 +46,7 @@ module.exports = function(core) {
46
46
  const sinkContext = {
47
47
  name,
48
48
  value,
49
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
49
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
50
50
  };
51
51
 
52
52
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -27,105 +27,49 @@ module.exports = function(core) {
27
27
  protect: { inputTracing }
28
28
  } = core;
29
29
 
30
+ function pre({ args, hooked, orig, name }) {
31
+ if (instrumentation.isLocked()) return;
32
+
33
+ const sourceContext = protect.getSourceContext(name);
34
+ if (!sourceContext) return;
35
+
36
+ for (let i = 0; i < (name === 'vm.runInNewContext' ? 2 : 1); i++) {
37
+ const arg = args[i];
38
+ if (!((arg && isString(arg)) || isNonEmptyObject(arg))) continue;
39
+
40
+ const sinkContext = {
41
+ name,
42
+ value: arg,
43
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
44
+ };
45
+ inputTracing.ssjsInjection(sourceContext, sinkContext);
46
+ }
47
+ }
48
+
30
49
  function install() {
31
50
  depHooks.resolve({ name: 'vm' }, (vm) => {
32
- ['Script', 'createScript', 'runInContext', 'runInThisContext'].forEach(
51
+ [
52
+ 'Script',
53
+ 'createScript',
54
+ 'runInContext',
55
+ 'runInThisContext',
56
+ 'createContext',
57
+ 'runInNewContext'
58
+ ].forEach(
33
59
  (method) => {
34
60
  const name = `vm.${method}`;
35
-
36
61
  patcher.patch(vm, method, {
37
62
  name,
38
63
  patchType,
39
- pre({ args, hooked, name, orig }) {
40
- if (instrumentation.isLocked()) return;
41
-
42
- const sourceContext = protect.getSourceContext(name);
43
- if (!sourceContext) return;
44
-
45
- const codeString = args[0];
46
- if (!codeString || !isString(codeString)) return;
47
-
48
- const sinkContext = {
49
- name,
50
- value: codeString,
51
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
52
- };
53
- inputTracing.ssjsInjection(sourceContext, sinkContext);
54
- }
64
+ pre
55
65
  });
56
66
  }
57
67
  );
58
68
 
59
- patcher.patch(vm, 'runInNewContext', {
60
- name: 'vm.runInNewContext',
61
- patchType,
62
- pre: ({ args, hooked, orig }) => {
63
- if (instrumentation.isLocked()) return;
64
-
65
- const sourceContext = protect.getSourceContext('vm.runInNewContext');
66
- if (!sourceContext) return;
67
-
68
- const codeString = args[0];
69
- const envObj = args[1];
70
-
71
- if ((!codeString || !isString(codeString)) && (!isNonEmptyObject(envObj))) return;
72
-
73
- const codeStringSinkContext = (codeString && isString(codeString)) ? {
74
- name: 'vm.runInNewContext',
75
- value: codeString,
76
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] }
77
- } : null;
78
- const envObjSinkContext = isNonEmptyObject(envObj) ? {
79
- name: 'vm.runInNewContext',
80
- value: envObj,
81
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] }
82
- } : null;
83
-
84
- codeStringSinkContext && inputTracing.ssjsInjection(sourceContext, codeStringSinkContext);
85
- envObjSinkContext && inputTracing.ssjsInjection(sourceContext, envObjSinkContext);
86
- }
87
- });
88
-
89
- patcher.patch(vm, 'createContext', {
90
- name: 'vm.createContext',
91
- patchType,
92
- pre: ({ args, hooked, orig }) => {
93
- if (instrumentation.isLocked()) return;
94
-
95
- const sourceContext = protect.getSourceContext('vm.createContext');
96
- if (!sourceContext) return;
97
-
98
- const envObj = args[0];
99
- if (!isNonEmptyObject(envObj)) return;
100
-
101
- const sinkContext = {
102
- name: 'vm.createContext',
103
- value: envObj,
104
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
105
- };
106
- inputTracing.ssjsInjection(sourceContext, sinkContext);
107
- }
108
- });
109
-
110
69
  patcher.patch(vm.Script.prototype, 'runInNewContext', {
111
70
  name: 'vm.Script.prototype.runInNewContext',
112
71
  patchType,
113
- pre: ({ args, hooked, orig }) => {
114
- if (instrumentation.isLocked()) return;
115
-
116
- const sourceContext = protect.getSourceContext('vm.Script.prototype.runInNewContext');
117
- if (!sourceContext) return;
118
-
119
- const envObj = args[0];
120
- if (!isNonEmptyObject(envObj)) return;
121
-
122
- const sinkContext = {
123
- name: 'vm.Script.prototype.runInNewContext',
124
- value: envObj,
125
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
126
- };
127
- inputTracing.ssjsInjection(sourceContext, sinkContext);
128
- }
72
+ pre
129
73
  });
130
74
  });
131
75
  }
@@ -41,8 +41,8 @@ module.exports = function(core) {
41
41
  const { protect: { agentLib, semanticAnalysis, throwSecurityException }, captureStacktrace } = core;
42
42
 
43
43
  function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
44
- const { value, stacktraceData } = sinkContext;
45
- captureStacktrace(sinkContext, stacktraceData);
44
+ const { value, stacktraceOpts } = sinkContext;
45
+ captureStacktrace(sinkContext, stacktraceOpts);
46
46
  const result = {
47
47
  blocked: false,
48
48
  ruleId,
@@ -62,7 +62,7 @@ module.exports = function(core) {
62
62
  const sinkContext = {
63
63
  name: 'libxmljs.parseXmlString',
64
64
  value,
65
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
65
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
66
66
  };
67
67
 
68
68
  try {
@@ -40,7 +40,7 @@ const ENTITY_TYPES = {
40
40
  // We only use this against lowercase strings; removed A-Z for speed
41
41
  const FILE_PATTERN_WINDOWS = /^[\\\\]*[a-z]{1,3}:.*/;
42
42
 
43
- const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+"(?<uri1>.*?)"\s*("(?<uri2>.*?)"\s*)?>/g;
43
+ const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+['"](?<uri1>.*?)['"]\s*(['"](?<uri2>.*?)['"]\s*)?>/g;
44
44
 
45
45
  // Helper Functions
46
46
  const indicators = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -17,12 +17,12 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/agent-lib": "^5.1.0",
21
- "@contrast/common": "1.3.0",
22
- "@contrast/core": "1.9.0",
23
- "@contrast/esm-hooks": "1.5.0",
20
+ "@contrast/agent-lib": "^5.3.0",
21
+ "@contrast/common": "1.3.1",
22
+ "@contrast/core": "1.10.1",
23
+ "@contrast/esm-hooks": "1.6.1",
24
24
  "@contrast/scopes": "1.2.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"
27
27
  }
28
- }
28
+ }