@contrast/protect 1.24.0 → 1.25.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.
@@ -24,6 +24,9 @@ const {
24
24
  traverseKeysAndValues,
25
25
  traverseValues,
26
26
  InputType,
27
+ toLowerCase,
28
+ split,
29
+ join
27
30
  } = require('@contrast/common');
28
31
 
29
32
  //
@@ -591,12 +594,12 @@ module.exports = function(core) {
591
594
  const probe = Object.assign({}, resultByRuleId, result, {
592
595
  mappedId: result.ruleId,
593
596
  });
594
- const key = [
597
+ const key = join([
595
598
  probe.ruleId,
596
599
  probe.inputType,
597
600
  ...probe.path,
598
601
  probe.value,
599
- ].join('|');
602
+ ], '|');
600
603
  probes[key] = probe;
601
604
  });
602
605
 
@@ -721,7 +724,7 @@ module.exports = function(core) {
721
724
 
722
725
  for (let i = 0; i < reqHeaders.length; i++) {
723
726
  if (reqHeaders[i] === 'x-forwarded-for') {
724
- const ipsFromHeaders = reqHeaders[i + 1]?.split(/[,;]+/);
727
+ const ipsFromHeaders = split(reqHeaders[i + 1], /[,;]+/);
725
728
  forwardedIps.push(...ipsFromHeaders);
726
729
  }
727
730
  }
@@ -793,7 +796,7 @@ function isResultExcluded(sourceContext, result) {
793
796
  }
794
797
  case 'HeaderKey':
795
798
  case 'HeaderValue': {
796
- if (path?.[0]?.toLowerCase() === 'cookie') {
799
+ if (path[0] && toLowerCase(path[0]) === 'cookie') {
797
800
  inputExclusions = exclusions.cookie;
798
801
  checkCookiesInHeader = true;
799
802
  } else {
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Event } = require('@contrast/common');
18
+ const { Event, substr } = require('@contrast/common');
19
19
  const address = require('ipaddr.js');
20
20
 
21
21
  module.exports = (core) => {
@@ -57,7 +57,7 @@ function ipEntryMap(ipEntry, startTime) {
57
57
  const slashIdx = ip.indexOf('/');
58
58
  const isCIDR = slashIdx >= 0;
59
59
  const ipInstance = isCIDR
60
- ? address.process(ip.substr(0, slashIdx))
60
+ ? address.process(substr(ip, 0, slashIdx))
61
61
  : address.process(ip);
62
62
 
63
63
  const normalizedValue = ipInstance.toNormalizedString();
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Event } = require('@contrast/common');
18
+ const { Event, toLowerCase } = require('@contrast/common');
19
19
 
20
20
  module.exports = (core) => {
21
21
  const {
@@ -47,7 +47,7 @@ function buildVPEvaluators(virtualPatches, evaluatorsArray) {
47
47
  acc.push(...entry);
48
48
  return acc;
49
49
  }, []);
50
- const keyIndex = headersArray.indexOf(name.toLowerCase());
50
+ const keyIndex = headersArray.indexOf(toLowerCase(name));
51
51
 
52
52
  result = keyIndex !== -1 && evalCheck(headersArray[keyIndex + 1], value);
53
53
  if (!result) break;
@@ -20,6 +20,7 @@ const {
20
20
  ProtectRuleMode: { OFF },
21
21
  BLOCKING_MODES,
22
22
  isString,
23
+ stringify,
23
24
  traverseKeys,
24
25
  traverseKeysAndValues,
25
26
  agentLibIDListTypes,
@@ -339,7 +340,7 @@ function handleObjectValue(result, object) {
339
340
  obj = obj[value];
340
341
  // does the found object in the query equal the saved object?
341
342
  if (util.isDeepStrictEqual(obj, result.mongoContext.inputToCheck)) {
342
- const start = JSON.stringify(object).indexOf(value);
343
+ const start = stringify(object).indexOf(value);
343
344
  const end = start + value.length;
344
345
  const inputBoundaryIndex = 0;
345
346
  findings = { start, end, boundaryOverrunIndex: start, inputBoundaryIndex };
@@ -15,6 +15,8 @@
15
15
 
16
16
  'use strict';
17
17
 
18
+ const { toUpperCase } = require('@contrast/common');
19
+
18
20
  module.exports = function(core) {
19
21
  // i think this should be a weakset. we don't want to accumulate
20
22
  // blocked requests for the entire life of a program. the req
@@ -27,7 +29,7 @@ module.exports = function(core) {
27
29
  if (blocked.has(res)) return;
28
30
 
29
31
  blocked.add(res);
30
- mode = mode.toUpperCase();
32
+ mode = toUpperCase(mode);
31
33
  const end = patcher.unwrap(res.end);
32
34
  const writeHead = patcher.unwrap(res.writeHead);
33
35
 
@@ -15,6 +15,8 @@
15
15
 
16
16
  'use strict';
17
17
 
18
+ const { toLowerCase, slice } = require('@contrast/common');
19
+
18
20
  module.exports = function(core) {
19
21
  const {
20
22
  protect: { getPolicy }
@@ -34,8 +36,8 @@ module.exports = function(core) {
34
36
  const ix = req.url.indexOf('?');
35
37
 
36
38
  if (ix >= 0) {
37
- uriPath = req.url.slice(0, ix);
38
- queries = req.url.slice(ix + 1);
39
+ uriPath = slice(req.url, 0, ix);
40
+ queries = slice(req.url, ix + 1);
39
41
  } else {
40
42
  uriPath = req.url;
41
43
  queries = '';
@@ -53,10 +55,10 @@ module.exports = function(core) {
53
55
  const headers = Array(req.rawHeaders.length);
54
56
 
55
57
  for (let i = 0; i < req.rawHeaders.length; i += 2) {
56
- headers[i] = req.rawHeaders[i].toLowerCase();
58
+ headers[i] = toLowerCase(req.rawHeaders[i]);
57
59
  headers[i + 1] = req.rawHeaders[i + 1];
58
60
  if (headers[i] === 'content-type') {
59
- contentType = headers[i + 1].toLowerCase();
61
+ contentType = toLowerCase(headers[i + 1]);
60
62
  }
61
63
  }
62
64
 
package/lib/policy.js CHANGED
@@ -19,12 +19,30 @@ const {
19
19
  Rule,
20
20
  ProtectRuleMode: {
21
21
  BLOCK_AT_PERIMETER,
22
- BLOCK,
23
- MONITOR,
24
22
  OFF,
25
23
  },
26
- Rule: { BOT_BLOCKER },
24
+ Rule: {
25
+ BOT_BLOCKER,
26
+ CMD_INJECTION,
27
+ CMD_INJECTION_COMMAND_BACKDOORS,
28
+ CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
29
+ CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
30
+ METHOD_TAMPERING,
31
+ NOSQL_INJECTION,
32
+ NOSQL_INJECTION_MONGO,
33
+ PATH_TRAVERSAL,
34
+ PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
35
+ REFLECTED_XSS,
36
+ SQL_INJECTION,
37
+ SSJS_INJECTION,
38
+ UNSAFE_FILE_UPLOAD,
39
+ UNTRUSTED_DESERIALIZATION,
40
+ XXE,
41
+ },
27
42
  Event: { SERVER_SETTINGS_UPDATE },
43
+ toLowerCase,
44
+ split,
45
+ join
28
46
  } = require('@contrast/common');
29
47
 
30
48
  module.exports = function (core) {
@@ -69,7 +87,7 @@ module.exports = function (core) {
69
87
  }
70
88
  }
71
89
  if (regExpNeeded) {
72
- const rx = new RegExp(`^${urls.join('|')}$`);
90
+ const rx = new RegExp(`^${join(urls, '|')}$`);
73
91
 
74
92
  return (uriPath) => rx ? rx.test(uriPath) : false;
75
93
  }
@@ -104,20 +122,6 @@ module.exports = function (core) {
104
122
  return config.protect.rules?.[ruleId]?.mode;
105
123
  }
106
124
 
107
- /**
108
- * Coerces ContrastUI mode names to match from common-agent-configuration
109
- * @param {} remoteSetting
110
- * @returns {string}
111
- */
112
- function readModeFromSetting(remoteSetting) {
113
- switch (remoteSetting.mode) {
114
- case 'OFF': return OFF;
115
- case 'MONITOR': return MONITOR;
116
- case 'BLOCK': return BLOCK;
117
- case 'BLOCK_AT_PERIMETER': return BLOCK_AT_PERIMETER;
118
- }
119
- }
120
-
121
125
  /**
122
126
  * Build out initial policy from configuration data
123
127
  */
@@ -128,26 +132,6 @@ module.exports = function (core) {
128
132
  updateRulesMask();
129
133
  }
130
134
 
131
- /**
132
- * When updates are given at runtime, we update our local rule policy while
133
- * respecting rules of precedence.
134
- * @param {[]} protectionRules
135
- */
136
- function updateFromProtectionRules(protectionRules) {
137
- for (const ruleId in protectionRules) {
138
- if (ruleId === 'nosql-injection' && !getModeFromConfig('nosql-injection-mongo')) {
139
- policy['nosql-injection-mongo'] = readModeFromSetting(protectionRules[ruleId]);
140
- }
141
-
142
- if (getModeFromConfig(ruleId)) {
143
- continue;
144
- }
145
-
146
- policy[ruleId] && (policy[ruleId] = readModeFromSetting(protectionRules[ruleId]));
147
-
148
- }
149
- }
150
-
151
135
  /**
152
136
  * Rebuild rules mask based on which agent-lib rules are enabled
153
137
  */
@@ -255,8 +239,29 @@ module.exports = function (core) {
255
239
 
256
240
  const protectionRules = remoteSettings?.protect?.rules;
257
241
  if (protectionRules) {
258
- updateFromProtectionRules(protectionRules);
259
- update = 'application-settings';
242
+ [
243
+ CMD_INJECTION,
244
+ CMD_INJECTION_COMMAND_BACKDOORS,
245
+ CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
246
+ CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
247
+ METHOD_TAMPERING,
248
+ NOSQL_INJECTION,
249
+ NOSQL_INJECTION_MONGO,
250
+ PATH_TRAVERSAL,
251
+ PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
252
+ REFLECTED_XSS,
253
+ SQL_INJECTION,
254
+ SSJS_INJECTION,
255
+ UNSAFE_FILE_UPLOAD,
256
+ UNTRUSTED_DESERIALIZATION,
257
+ XXE,
258
+ ].forEach((ruleId) => {
259
+ if (config.protect.rules.disabled_rules.includes(ruleId)) {
260
+ policy[ruleId] = OFF;
261
+ } else {
262
+ policy[ruleId] = config.getEffectiveValue(`protect.rules.${ruleId}.mode`);
263
+ }
264
+ });
260
265
 
261
266
  const bbEnabled = remoteSettings.protect.rules?.bot_blocker?.enable;
262
267
 
@@ -267,9 +272,7 @@ module.exports = function (core) {
267
272
  ) {
268
273
  policy[BOT_BLOCKER] = bbEnabled ? BLOCK_AT_PERIMETER : OFF;
269
274
  }
270
- }
271
275
 
272
- if (update) {
273
276
  updateRulesMask();
274
277
  protect.policy.exclusions = compiled;
275
278
  logger.info({ policy: protect.policy }, `protect policy updated from ${update}`);
@@ -289,7 +292,7 @@ module.exports = function (core) {
289
292
  exclusionDtm.type = exclusionDtm.type || 'URL';
290
293
 
291
294
  const { name, protect_rules, urls, type } = exclusionDtm;
292
- const key = type.toLowerCase();
295
+ const key = toLowerCase(type);
293
296
 
294
297
  if (!compiled[key]) continue;
295
298
 
@@ -324,8 +327,8 @@ module.exports = function (core) {
324
327
  }
325
328
  if (key === 'cookie') {
326
329
  e.checkCookieInHeader = (cookieHeader) => {
327
- for (const cookiePair of cookieHeader.split(';')) {
328
- const cookieKey = cookiePair.split('=')[0];
330
+ for (const cookiePair of split(cookieHeader, ';')) {
331
+ const cookieKey = split(cookiePair, '=')[0];
329
332
  if (e.matchesInputName(cookieKey)) {
330
333
  return true;
331
334
  }
@@ -21,6 +21,7 @@ const {
21
21
  ProtectRuleMode: { OFF },
22
22
  InputType,
23
23
  traverseValues,
24
+ replace
24
25
  } = require('@contrast/common');
25
26
 
26
27
  const {
@@ -28,7 +29,7 @@ const {
28
29
  } = require('./utils/xml-analysis');
29
30
 
30
31
  const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
31
- const stripWhiteSpace = (str) => str.replace(/\s/g, '');
32
+ const stripWhiteSpace = (str) => replace(str, /\s/g, '');
32
33
 
33
34
  const getRuleResults = function(obj, prop) {
34
35
  return obj[prop] || (obj[prop] = []);
@@ -14,6 +14,8 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
+ const { substr, toLowerCase } = require('@contrast/common');
18
+
17
19
  const PROTOCOLS = {
18
20
  FTP: 'FTP',
19
21
  HTTP: 'HTTP',
@@ -21,9 +23,9 @@ const PROTOCOLS = {
21
23
  TCP: 'TCP'
22
24
  };
23
25
 
24
- const FTP = `${PROTOCOLS.FTP.toLowerCase()}:`;
25
- const HTTP = `${PROTOCOLS.HTTP.toLowerCase()}:`;
26
- const HTTPS = `${PROTOCOLS.HTTPS.toLowerCase()}:`;
26
+ const FTP = `${toLowerCase(PROTOCOLS.FTP)}:`;
27
+ const HTTP = `${toLowerCase(PROTOCOLS.HTTP)}:`;
28
+ const HTTPS = `${toLowerCase(PROTOCOLS.HTTPS)}:`;
27
29
  const DTD_EXTENSION = '.dtd';
28
30
  const FILE_START = 'file:';
29
31
  const GOPHER_START = 'gopher:';
@@ -99,7 +101,7 @@ module.exports.findExternalEntities = function(xml = '') {
99
101
 
100
102
  return {
101
103
  entities,
102
- prolog: len && xml.substr(0, entities[len - 1].finish) || null
104
+ prolog: len && substr(xml, 0, entities[len - 1].finish) || null
103
105
  };
104
106
  };
105
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.24.0",
3
+ "version": "1.25.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)",
@@ -10,7 +10,7 @@
10
10
  "main": "lib/index.js",
11
11
  "types": "lib/index.d.ts",
12
12
  "engines": {
13
- "npm": ">= 8.4.0",
13
+ "npm": ">=6.13.7 <7 || >= 8.3.1",
14
14
  "node": ">= 14.15.0"
15
15
  },
16
16
  "scripts": {
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^7.0.1",
21
- "@contrast/common": "1.14.0",
22
- "@contrast/core": "1.22.0",
23
- "@contrast/esm-hooks": "1.18.0",
21
+ "@contrast/common": "1.15.0",
22
+ "@contrast/core": "1.23.0",
23
+ "@contrast/esm-hooks": "1.19.0",
24
24
  "@contrast/scopes": "1.4.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"
@@ -28,4 +28,4 @@
28
28
  "optionalDependencies": {
29
29
  "async-hook-domain": "^3.0.2"
30
30
  }
31
- }
31
+ }