@contrast/protect 1.13.0 → 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.
package/lib/index.js
CHANGED
|
@@ -19,6 +19,8 @@ const agentLib = require('@contrast/agent-lib');
|
|
|
19
19
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
module.exports = function(core) {
|
|
22
|
+
if (!core.config.protect.enable) return;
|
|
23
|
+
|
|
22
24
|
const protect = core.protect = {
|
|
23
25
|
agentLib: module.exports.instantiateAgentLib(agentLib),
|
|
24
26
|
};
|
|
@@ -35,6 +37,7 @@ module.exports = function(core) {
|
|
|
35
37
|
require('./semantic-analysis')(core);
|
|
36
38
|
|
|
37
39
|
protect.install = function() {
|
|
40
|
+
core.rewriter.install('protect');
|
|
38
41
|
callChildComponentMethodsSync(protect, 'install');
|
|
39
42
|
};
|
|
40
43
|
|
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
457
|
-
|
|
458
|
-
value
|
|
459
|
-
|
|
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
|
-
|
|
479
|
+
findingsForScoreAtom[value][inputType] = resultByRuleId;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
466
482
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
483
|
+
dataType[key] = value;
|
|
484
|
+
valueToResultByRuleId[value] = resultByRuleId;
|
|
485
|
+
});
|
|
486
|
+
});
|
|
471
487
|
|
|
472
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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(
|
|
484
|
-
if (!resultsMap[
|
|
485
|
-
resultsMap[
|
|
532
|
+
Object.values(probes).forEach(probe => {
|
|
533
|
+
if (!resultsMap[probe.ruleId]) {
|
|
534
|
+
resultsMap[probe.ruleId] = [];
|
|
486
535
|
}
|
|
487
536
|
|
|
488
|
-
resultsMap[
|
|
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
|
|
33
|
-
const updatedIpDenyList = serverUpdate.features?.defend?.ipDenylist
|
|
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.
|
|
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.
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/esm-hooks": "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"
|
|
@@ -28,4 +28,4 @@
|
|
|
28
28
|
"optionalDependencies": {
|
|
29
29
|
"async-hook-domain": "^3.0.2"
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|