@contrast/protect 1.13.1 → 1.14.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.
@@ -20,6 +20,7 @@ const {
20
20
  traverseKeysAndValues,
21
21
  traverseValues,
22
22
  Rule,
23
+ ProtectRuleMode,
23
24
  isString,
24
25
  ProtectRuleMode: { OFF },
25
26
  } = require('@contrast/common');
@@ -435,57 +436,105 @@ module.exports = function(core) {
435
436
  inputAnalysis.handleRequestEnd = function handleRequestEnd(sourceContext) {
436
437
  if (!config.protect.probe_analysis.enable || sourceContext.allowed) return;
437
438
 
438
- const { resultsMap } = sourceContext;
439
+ // Detecting probes
440
+ const { resultsMap, policy: { rulesMask } } = sourceContext;
441
+
439
442
  const probesRules = [Rule.CMD_INJECTION, Rule.PATH_TRAVERSAL, Rule.SQL_INJECTION, Rule.XXE];
440
- const props = {};
443
+ const probes = {};
444
+
445
+ const findingsForScoreRequest = {
446
+ HeaderValue: {},
447
+ ParameterValue: {},
448
+ CookieValue: {},
449
+ };
450
+ const findingsForScoreAtom = {};
451
+ const valueToResultByRuleId = {};
441
452
 
442
- // Detecting probes
443
453
  Object.values(resultsMap).forEach(resultsByRuleId => {
444
- resultsByRuleId.forEach((resultByRuleId) => {
454
+ resultsByRuleId.forEach(resultByRuleId => {
445
455
  const {
446
456
  ruleId,
447
- blocked,
448
457
  exploitMetadata,
458
+ score,
449
459
  value,
460
+ key,
450
461
  inputType
451
462
  } = resultByRuleId;
452
- if (blocked || !blocked && exploitMetadata.length > 0 || !probesRules.some(rule => rule === ruleId)) return;
453
463
 
454
- const { policy: { rulesMask } } = sourceContext;
464
+ if (
465
+ !isMonitorMode(ruleId, sourceContext) ||
466
+ exploitMetadata.length > 0 ||
467
+ score >= 90 ||
468
+ !probesRules.some((rule) => rule === ruleId)
469
+ ) {
470
+ return;
471
+ }
455
472
 
456
- const results = (agentLib.scoreAtom(
457
- rulesMask,
458
- value,
459
- agentLib.InputType[inputType],
460
- {
461
- preferWorthWatching: false
473
+ const dataType = findingsForScoreRequest[inputType];
474
+ if (!dataType) {
475
+ if (!findingsForScoreAtom[value]) {
476
+ findingsForScoreAtom[value] = {};
462
477
  }
463
- ) || []).filter(({ score }) => score >= 90);
464
478
 
465
- if (!results.length) return;
479
+ findingsForScoreAtom[value][inputType] = resultByRuleId;
480
+ return;
481
+ }
466
482
 
467
- results.forEach(result => {
468
- const isAlreadyBlocked = (resultsMap[result.ruleId] || []).some(element =>
469
- element.blocked && element.inputType === inputType && element.value === value
470
- );
483
+ dataType[key] = value;
484
+ valueToResultByRuleId[value] = resultByRuleId;
485
+ });
486
+ });
471
487
 
472
- if (isAlreadyBlocked) return;
488
+ const { ParameterValue, HeaderValue, CookieValue } = findingsForScoreRequest;
489
+
490
+ const results =
491
+ agentLib.scoreRequestConnect(
492
+ rulesMask,
493
+ {
494
+ queries: Object.entries(ParameterValue).flat(),
495
+ headers: Object.entries(HeaderValue).flat(),
496
+ cookies: Object.entries(CookieValue).flat(),
497
+ },
498
+ {
499
+ preferWorthWatching: false,
500
+ }
501
+ ).resultsList || [];
502
+
503
+ Object.entries(findingsForScoreAtom).forEach(([value, inputTypes]) => {
504
+ Object.entries(inputTypes).forEach(([inputType, resultByRuleId]) =>
505
+ (
506
+ agentLib.scoreAtom(rulesMask, value, agentLib.InputType[inputType], {
507
+ preferWorthWatching: false,
508
+ }) || []
509
+ ).forEach(result => {
510
+ results.push({ value, ...result });
511
+ valueToResultByRuleId[value] = resultByRuleId;
512
+ })
513
+ );
514
+ });
473
515
 
474
- const probe = Object.assign({}, resultByRuleId, result, {
475
- mappedId: result.ruleId
476
- });
477
- const key = [probe.ruleId, probe.inputType, ...probe.path, probe.value].join('|');
478
- props[key] = probe;
516
+ results
517
+ .filter(({ score, ruleId }) => score >= 90 && isMonitorMode(ruleId, sourceContext))
518
+ .forEach((result) => {
519
+ const resultByRuleId = valueToResultByRuleId[result.value];
520
+ const probe = Object.assign({}, resultByRuleId, result, {
521
+ mappedId: result.ruleId,
479
522
  });
523
+ const key = [
524
+ probe.ruleId,
525
+ probe.inputType,
526
+ ...probe.path,
527
+ probe.value,
528
+ ].join('|');
529
+ probes[key] = probe;
480
530
  });
481
- });
482
531
 
483
- Object.values(props).forEach(prop => {
484
- if (!resultsMap[prop.ruleId]) {
485
- resultsMap[prop.ruleId] = [];
532
+ Object.values(probes).forEach(probe => {
533
+ if (!resultsMap[probe.ruleId]) {
534
+ resultsMap[probe.ruleId] = [];
486
535
  }
487
536
 
488
- resultsMap[prop.ruleId].push(prop);
537
+ resultsMap[probe.ruleId].push(probe);
489
538
  });
490
539
  };
491
540
 
@@ -817,3 +866,7 @@ function getValueAtKey(obj, path, key) {
817
866
  }
818
867
  return key in obj ? obj[key] : undefined;
819
868
  }
869
+
870
+ function isMonitorMode(ruleId, sourceContext) {
871
+ return sourceContext.policy[ruleId] === ProtectRuleMode.MONITOR;
872
+ }
@@ -29,8 +29,8 @@ module.exports = (core) => {
29
29
 
30
30
  messages.on(Event.SERVER_SETTINGS_UPDATE, (serverUpdate) => {
31
31
  const now = new Date().getTime();
32
- const updatedIpAllowList = serverUpdate.features?.defend?.ipAllowlist.map((ipEntry) => ipEntryMap(ipEntry, now));
33
- const updatedIpDenyList = serverUpdate.features?.defend?.ipDenylist.map((ipEntry) => ipEntryMap(ipEntry, now));
32
+ const updatedIpAllowList = serverUpdate.features?.defend?.ipAllowlist?.map?.((ipEntry) => ipEntryMap(ipEntry, now));
33
+ const updatedIpDenyList = serverUpdate.features?.defend?.ipDenylist?.map?.((ipEntry) => ipEntryMap(ipEntry, now));
34
34
 
35
35
  if (updatedIpAllowList) {
36
36
  ipAllowlist.length = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.13.1",
3
+ "version": "1.14.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": "^5.3.4",
21
- "@contrast/common": "1.4.1",
22
- "@contrast/core": "1.11.1",
23
- "@contrast/esm-hooks": "1.7.1",
21
+ "@contrast/common": "1.5.0",
22
+ "@contrast/core": "1.12.0",
23
+ "@contrast/esm-hooks": "1.8.0",
24
24
  "@contrast/scopes": "1.3.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"