@contrast/protect 1.21.0 → 1.22.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.
@@ -15,14 +15,14 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- module.exports = function(core) {
18
+ module.exports = function (core) {
19
19
  const { scopes: { sources }, logger } = core;
20
20
 
21
21
  function getSourceContext(callPoint) {
22
22
  const sourceContext = sources.getStore()?.protect;
23
23
 
24
24
  if (!sourceContext) {
25
- logger.debug(`source context not available in ${callPoint}`);
25
+ logger.debug('source context not available in %s', callPoint);
26
26
  return null;
27
27
  }
28
28
 
@@ -15,7 +15,11 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { BLOCKING_MODES, isString } = require('@contrast/common');
18
+ const {
19
+ BLOCKING_MODES,
20
+ isString,
21
+ Rule: { UNTRUSTED_DESERIALIZATION }
22
+ } = require('@contrast/common');
19
23
 
20
24
  const NODE_SERIALIZE_RCE_TOKEN = '_$$ND_FUNC$$_';
21
25
 
@@ -37,7 +41,7 @@ module.exports = function(core) {
37
41
  }
38
42
 
39
43
  hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
40
- const ruleId = 'untrusted-deserialization';
44
+ const ruleId = UNTRUSTED_DESERIALIZATION;
41
45
  const mode = sourceContext.policy[ruleId];
42
46
  const { name, value, stacktraceOpts } = sinkContext;
43
47
 
@@ -398,7 +398,7 @@ module.exports = function(core) {
398
398
  };
399
399
 
400
400
  inputAnalysis.handleVirtualPatches = function(sourceContext, requestInput) {
401
- const ruleId = 'virtual-patch';
401
+ const ruleId = Rule.VIRTUAL_PATCH;
402
402
 
403
403
  if (!Object.keys(requestInput).filter(Boolean).length || !sourceContext?.virtualPatchesEvaluators.length) return;
404
404
 
@@ -440,7 +440,7 @@ module.exports = function(core) {
440
440
  };
441
441
 
442
442
  inputAnalysis.handleIpDenylist = function(sourceContext, ipDenylist) {
443
- const ruleId = 'ip-denylist';
443
+ const ruleId = Rule.IP_DENYLIST;
444
444
 
445
445
  if (!sourceContext || !ipDenylist.length) return;
446
446
 
@@ -36,63 +36,80 @@ module.exports = (core) => {
36
36
  * registers a depHook for express module instrumentation
37
37
  */
38
38
  function install() {
39
- depHooks.resolve({ name: 'express', version: '>=4.0.0 <5.0.0', file: 'lib/middleware/query.js' }, (query) => patcher.patch(query, {
40
- name: 'Express.query',
41
- patchType,
42
- post(data) {
43
- data.result = patcher.patch(data.result, {
44
- name: 'Express.query',
45
- patchType,
46
- pre(data) {
47
- const [req, , origNext] = data.args;
48
-
49
- function contrastNext(origErr) {
50
- const sourceContext = protect.getSourceContext('Express.query');
51
- let securityException;
52
-
53
- // It is possible for the query to be already parsed by `qs`
54
- // which means that we've already handled/analyzed it.
55
- // So we check whether we already have the `parsedQuery` property in the context
56
- if (sourceContext && req.query && Object.keys(req.query).length && (!('parsedQuery' in sourceContext))) {
57
- sourceContext.parsedQuery = req.query;
58
-
59
- try {
60
- inputAnalysis.handleQueryParams(sourceContext, req.query);
61
- } catch (err) {
62
- if (isSecurityException(err)) {
63
- securityException = err;
64
- } else {
65
- logger.error({ err }, 'Unexpected error during input analysis');
39
+ depHooks.resolve(
40
+ { name: 'express', version: '>=4.0.0 <5.0.0', file: 'lib/middleware/query.js' },
41
+ (query) => patcher.patch(query, {
42
+ name: 'express.query',
43
+ patchType,
44
+ post(data) {
45
+ data.result = patcher.patch(data.result, {
46
+ name: 'express.query',
47
+ patchType,
48
+ pre(data) {
49
+ const [req, , origNext] = data.args;
50
+
51
+ function contrastNext(origErr) {
52
+ const sourceContext = protect.getSourceContext('express.query');
53
+ let securityException;
54
+
55
+ // It is possible for the query to be already parsed by `qs`
56
+ // which means that we've already handled/analyzed it.
57
+ // So we check whether we already have the `parsedQuery` property in the context
58
+ if (sourceContext && req.query && Object.keys(req.query).length && (!('parsedQuery' in sourceContext))) {
59
+ sourceContext.parsedQuery = req.query;
60
+
61
+ try {
62
+ inputAnalysis.handleQueryParams(sourceContext, req.query);
63
+ } catch (err) {
64
+ if (isSecurityException(err)) {
65
+ securityException = err;
66
+ } else {
67
+ logger.error({ err }, 'Unexpected error during input analysis');
68
+ }
66
69
  }
67
70
  }
71
+
72
+ const error = securityException || origErr;
73
+
74
+ origNext(error);
68
75
  }
69
76
 
70
- const error = securityException || origErr;
77
+ data.args[2] = contrastNext;
78
+ }
79
+ });
80
+ }
81
+ }));
82
+
83
+ depHooks.resolve(
84
+ { name: 'express', version: '>=4.0.0 <5.0.0', file: 'lib/router/layer.js' },
85
+ (Layer) => {
86
+ const name = 'express.Layer.prototype.match';
87
+ patcher.patch(Layer.prototype, 'match', {
88
+ name,
89
+ patchType,
90
+ post(data) {
91
+ const layer = data.obj;
71
92
 
72
- origNext(error);
93
+ // we can exit early if
94
+ // the layer doesn't match the request or
95
+ // the layer doesn't recognize any parameters
96
+ if (!data.result || !layer.keys || layer.keys.length === 0) {
97
+ return;
73
98
  }
74
99
 
75
- data.args[2] = contrastNext;
76
- }
77
- });
78
- }
79
- }));
100
+ const sourceContext = protect.getSourceContext(name);
80
101
 
81
- depHooks.resolve({ name: 'express', version: '>=4.0.0 <5.0.0', file: 'lib/router/layer.js' }, (Layer) => {
82
- patcher.patch(Layer.prototype, 'handle_request', {
83
- name: 'express.Layer.prototype.handle_request',
84
- patchType,
85
- pre(data) {
86
- const { obj: { params } } = data;
87
- const sourceContext = protect.getSourceContext('Express.Layer.handle_request');
102
+ if (!sourceContext) {
103
+ return;
104
+ }
88
105
 
89
- if (sourceContext && params && Object.keys(params).length) {
90
- sourceContext.parsedParams = params;
91
- inputAnalysis.handleUrlParams(sourceContext, params);
106
+ sourceContext.parsedParams = layer.params;
107
+ inputAnalysis.handleUrlParams(sourceContext, layer.params);
92
108
  }
93
- }
109
+ });
110
+
111
+ return Layer;
94
112
  });
95
- });
96
113
  }
97
114
 
98
115
  const express4Instrumentation = inputAnalysis.express4Instrumentation = {
@@ -22,7 +22,8 @@ const {
22
22
  isString,
23
23
  traverseKeys,
24
24
  traverseKeysAndValues,
25
- agentLibIDListTypes
25
+ agentLibIDListTypes,
26
+ Rule: { SQL_INJECTION, PATH_TRAVERSAL, CMD_INJECTION, NOSQL_INJECTION_MONGO, SSJS_INJECTION, REFLECTED_XSS }
26
27
  } = require('@contrast/common');
27
28
 
28
29
  module.exports = function(core) {
@@ -51,7 +52,7 @@ module.exports = function(core) {
51
52
  }
52
53
 
53
54
  inputTracing.handlePathTraversal = function(sourceContext, sinkContext) {
54
- const ruleId = 'path-traversal';
55
+ const ruleId = PATH_TRAVERSAL;
55
56
  const results = getResultsByRuleId(ruleId, sourceContext);
56
57
 
57
58
  if (!results) return;
@@ -67,7 +68,7 @@ module.exports = function(core) {
67
68
  };
68
69
 
69
70
  inputTracing.handleCommandInjection = function(sourceContext, sinkContext) {
70
- const ruleId = 'cmd-injection';
71
+ const ruleId = CMD_INJECTION;
71
72
  const results = getResultsByRuleId(ruleId, sourceContext);
72
73
 
73
74
  if (!results) return;
@@ -93,7 +94,7 @@ module.exports = function(core) {
93
94
  };
94
95
 
95
96
  inputTracing.handleSqlInjection = function(sourceContext, sinkContext) {
96
- const ruleId = 'sql-injection';
97
+ const ruleId = SQL_INJECTION;
97
98
  const results = getResultsByRuleId(ruleId, sourceContext);
98
99
 
99
100
  if (!results) return;
@@ -129,7 +130,7 @@ module.exports = function(core) {
129
130
  };
130
131
 
131
132
  inputTracing.nosqlInjectionMongo = function (sourceContext, sinkContext) {
132
- const ruleId = 'nosql-injection-mongo';
133
+ const ruleId = NOSQL_INJECTION_MONGO;
133
134
  const nosqlInjectionMongoResults =
134
135
  getResultsByRuleId(ruleId, sourceContext) || [];
135
136
  const ssjsInjectionResults =
@@ -238,7 +239,7 @@ module.exports = function(core) {
238
239
  };
239
240
 
240
241
  inputTracing.ssjsInjection = function(sourceContext, sinkContext) {
241
- const ruleId = 'ssjs-injection';
242
+ const ruleId = SSJS_INJECTION;
242
243
  let sinkValuesArr = [];
243
244
 
244
245
  const results = getResultsByRuleId(ruleId, sourceContext);
@@ -289,7 +290,7 @@ module.exports = function(core) {
289
290
  };
290
291
 
291
292
  inputTracing.handleReflectedXss = function(sourceContext, sinkContext) {
292
- const ruleId = 'reflected-xss';
293
+ const ruleId = REFLECTED_XSS;
293
294
  const results = getResultsByRuleId(ruleId, sourceContext);
294
295
 
295
296
  if (!results) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.21.0",
3
+ "version": "1.22.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)",
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^7.0.1",
21
- "@contrast/common": "1.12.0",
22
- "@contrast/core": "1.19.0",
23
- "@contrast/esm-hooks": "1.15.0",
21
+ "@contrast/common": "1.13.0",
22
+ "@contrast/core": "1.20.0",
23
+ "@contrast/esm-hooks": "1.16.0",
24
24
  "@contrast/scopes": "1.4.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"