@contrast/protect 1.6.1 → 1.6.3

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.
@@ -38,7 +38,7 @@ module.exports = function(core) {
38
38
  data.result = patcher.patch(data.result, {
39
39
  name: 'finalHandler.returnedFunction',
40
40
  patchType,
41
- around(org, data) {
41
+ around(orig, data) {
42
42
  const [err] = data.args;
43
43
  const sourceContext = protect.getSourceContext('finalHandler');
44
44
  const isSecurityException = SecurityException.isSecurityException(err);
@@ -53,7 +53,7 @@ module.exports = function(core) {
53
53
  if (!sourceContext && isSecurityException) {
54
54
  logger.info('source context not found; unable to handle response');
55
55
  }
56
- org();
56
+ return orig();
57
57
  },
58
58
  });
59
59
  },
@@ -64,7 +64,7 @@ module.exports = function(core) {
64
64
  patcher.patch(Layer.prototype, 'handle_error', {
65
65
  name: 'Layer.prototype.handle_error',
66
66
  patchType,
67
- around(org, data) {
67
+ around(orig, data) {
68
68
  const [err] = data.args;
69
69
  const sourceContext = protect.getSourceContext('express.Layer.handle_error');
70
70
  const isSecurityException = SecurityException.isSecurityException(err);
@@ -79,10 +79,71 @@ module.exports = function(core) {
79
79
  if (!sourceContext && isSecurityException) {
80
80
  logger.info('source context not found; unable to handle response');
81
81
  }
82
- org();
82
+ return orig();
83
+ }
84
+ });
85
+
86
+ // This should be revisited after the research ticket NODE-2556
87
+ Object.defineProperty(Layer.prototype, 'handle', {
88
+ enumerable: true,
89
+ configurable: true,
90
+ get() {
91
+ return this.__handle;
92
+ },
93
+ set(fn) {
94
+ fn = patchFn(fn);
95
+ this.__handle = fn;
96
+ },
97
+ });
98
+ });
99
+
100
+ // This should be revisited after the research ticket NODE-2556
101
+ depHooks.resolve({ name: 'express', version: '>=4.0.0', file: 'lib/router/index.js' }, (Router) => {
102
+ patcher.patch(Router.prototype.constructor, 'param', {
103
+ name: 'Router.prototype.constructor.param',
104
+ patchType,
105
+ pre({ args }) {
106
+ if (typeof args[1] === 'function') {
107
+ args[1] = patchFn(args[1]);
108
+ }
83
109
  }
84
110
  });
85
111
  });
112
+
113
+ // This should be revisited after the research ticket NODE-2556
114
+ function patchFn(fn) {
115
+ return patcher.patch(fn, {
116
+ name: 'express.route-handler',
117
+ patchType,
118
+ around(orig) {
119
+ const ret = orig();
120
+ if (ret && ret.catch) {
121
+ return ret.catch((err) => {
122
+ const sourceContext = protect.getSourceContext('express.Layer.handle_error');
123
+ const isSecurityException = SecurityException.isSecurityException(err);
124
+
125
+ if (isSecurityException && sourceContext) {
126
+ const blockInfo = sourceContext.findings.securityException;
127
+
128
+ sourceContext.block(...blockInfo);
129
+ return;
130
+ }
131
+
132
+ if (!sourceContext && isSecurityException) {
133
+ logger.info('source context not found; unable to handle response');
134
+ return;
135
+ }
136
+
137
+ throw err;
138
+ });
139
+ }
140
+
141
+ return ret;
142
+ }
143
+ });
144
+ }
145
+
146
+
86
147
  };
87
148
 
88
149
  return express4ErrorHandler;
@@ -39,12 +39,12 @@ module.exports = function (core) {
39
39
  const isSecurityException = SecurityException.isSecurityException(err);
40
40
 
41
41
  if (isSecurityException && sourceContext && err.output.statusCode !== 403) {
42
- const [mode, ruleId] = sourceContext.findings.securityException;
42
+ const [ mode, ruleId ] = sourceContext.findings.securityException;
43
43
 
44
44
  err.output.statusCode = 403;
45
45
  err.reformat();
46
46
  err.output.payload = undefined;
47
- data.result = null;
47
+ data.result = err;
48
48
 
49
49
  logger.info({ mode, ruleId }, 'Request blocked');
50
50
 
@@ -39,7 +39,7 @@ module.exports = function(core) {
39
39
  patcher.patch(ctx, 'onerror', {
40
40
  name: 'koa.ctx.onerror',
41
41
  patchType,
42
- around(org, data) {
42
+ around(orig, data) {
43
43
  const [err] = data.args;
44
44
  const sourceContext = protect.getSourceContext('Koa.Application.handleRequest');
45
45
  const isSecurityException = SecurityException.isSecurityException(err);
@@ -55,7 +55,7 @@ module.exports = function(core) {
55
55
  logger.info('source context not found; unable to handle response');
56
56
  }
57
57
 
58
- org();
58
+ return orig();
59
59
  }
60
60
  });
61
61
  }
package/lib/index.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * way not consistent with the End User License Agreement.
15
15
  */
16
16
 
17
- import { Core } from '@contrast/core';
17
+ // import { Core } from '@contrast/core';
18
18
  import { Logger } from '@contrast/logger';
19
19
  import { Sources } from '@contrast/scopes';
20
20
  import RequireHook from '@contrast/require-hook';
@@ -39,7 +39,7 @@ export class HttpInstrumentation {
39
39
  maxBodySize: number;
40
40
  installed: boolean;
41
41
 
42
- constructor(core: Core);
42
+ constructor(core: {});
43
43
 
44
44
  install(): void;
45
45
  uninstall(): void; //NYI
package/lib/index.js CHANGED
@@ -34,9 +34,6 @@ module.exports = function(core) {
34
34
  require('./semantic-analysis')(core);
35
35
  require('./error-handlers')(core);
36
36
 
37
- const pkj = require('../package.json');
38
- protect.version = pkj.version;
39
-
40
37
  protect.install = function() {
41
38
  installChildComponentsSync(protect);
42
39
  };
@@ -119,7 +119,7 @@ module.exports = function(core) {
119
119
  inputTracing.nosqlInjectionMongo = function(sourceContext, sinkContext) {
120
120
  const ruleId = 'nosql-injection-mongo';
121
121
  const expansionResults = getResultsByRuleId(ruleId, sourceContext);
122
- const stringResults = getResultsByRuleId('ssjs-injection', sourceContext);
122
+ const stringInjectionResults = getResultsByRuleId('ssjs-injection', sourceContext);
123
123
 
124
124
  if (expansionResults) {
125
125
  let expansionFindings = null;
@@ -132,13 +132,31 @@ module.exports = function(core) {
132
132
  }
133
133
  }
134
134
 
135
- if (stringResults) {
135
+ if (stringInjectionResults) {
136
136
  let stringFindings = null;
137
- for (const result of stringResults) {
138
- stringFindings = handleStringValue(result, sinkContext.value);
137
+
138
+ for (const result of stringInjectionResults) {
139
+ if (typeof sinkContext.value === 'object') {
140
+ simpleTraverse(sinkContext.value, function(path, type, value) {
141
+ if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
142
+
143
+ stringFindings = handleStringValue(result, sinkContext.value[value], agentLib);
144
+ });
145
+ } else if (typeof sinkContext.value === 'string') {
146
+ stringFindings = handleStringValue(result, sinkContext.value, agentLib);
147
+ }
139
148
 
140
149
  if (stringFindings) {
141
- handleFindings(sourceContext, sinkContext, ruleId, result, stringFindings);
150
+ const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
151
+
152
+ const nosqlInjectionResults = sourceContext.findings.resultsMap[ruleId];
153
+ if (Array.isArray(nosqlInjectionResults)) {
154
+ nosqlInjectionResults.push(nosqlInjectionResult);
155
+ } else {
156
+ sourceContext.findings.resultsMap[ruleId] = [nosqlInjectionResult];
157
+ }
158
+
159
+ handleFindings(sourceContext, sinkContext, ruleId, nosqlInjectionResult, stringFindings);
142
160
  }
143
161
  }
144
162
  }
@@ -256,25 +274,35 @@ function handleObjectValue(result, object) {
256
274
  return findings;
257
275
  }
258
276
 
259
- function handleStringValue(result, string) {
260
- if (typeof string !== 'string') {
277
+ function handleStringValue(result, cmd, agentLib) {
278
+ if (typeof cmd !== 'string') {
261
279
  return null;
262
280
  }
263
281
  let findings = null;
264
282
 
265
- const inputIndex = string.indexOf(result.value);
283
+ const inputIndex = cmd.indexOf(result.value);
266
284
  // if the user input is not in the sink input, there is nothing to do.
267
285
  if (inputIndex === -1) {
268
286
  return findings;
269
287
  }
270
288
 
271
- if (inputIndex === 0 && string === result.value) {
289
+ if (inputIndex === 0 && cmd === result.value) {
272
290
  findings = {
273
291
  start: 0,
274
292
  end: result.value.length - 1,
275
293
  boundaryOverrunIndex: 0,
276
294
  inputBoundaryIndex: 0,
277
295
  };
296
+ } else {
297
+ const isAttack = agentLib.checkSsjsInjectionSink(cmd, inputIndex, result.value.length);
298
+ if (!isAttack) return findings;
299
+
300
+ findings = {
301
+ start: inputIndex,
302
+ end: inputIndex + result.value.length - 1,
303
+ boundaryOverrunIndex: 0,
304
+ inputBoundaryIndex: 0,
305
+ };
278
306
  }
279
307
 
280
308
  return findings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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)",
@@ -20,9 +20,9 @@
20
20
  "@babel/template": "^7.16.7",
21
21
  "@babel/types": "^7.16.8",
22
22
  "@contrast/agent-lib": "^5.1.0",
23
- "@contrast/common": "1.1.2",
24
- "@contrast/core": "1.5.1",
25
- "@contrast/esm-hooks": "1.1.7",
23
+ "@contrast/common": "1.1.3",
24
+ "@contrast/core": "1.6.0",
25
+ "@contrast/esm-hooks": "1.2.0",
26
26
  "@contrast/scopes": "1.1.2",
27
27
  "builtin-modules": "^3.2.0",
28
28
  "ipaddr.js": "^2.0.1",