@contrast/protect 1.74.0 → 1.75.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/input-analysis/handlers.js +58 -41
- package/package.json +13 -13
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
BLOCKING_MODES,
|
|
21
21
|
Rule,
|
|
22
22
|
ProtectRuleMode: { OFF, MONITOR },
|
|
23
|
+
identity,
|
|
23
24
|
isString,
|
|
24
25
|
traverseKeysAndValues,
|
|
25
26
|
traverseValues,
|
|
@@ -28,7 +29,7 @@ const {
|
|
|
28
29
|
ArrayPrototypeJoin,
|
|
29
30
|
ArrayPrototypeSlice,
|
|
30
31
|
StringPrototypeToLowerCase,
|
|
31
|
-
|
|
32
|
+
StringPrototypeMatch,
|
|
32
33
|
}
|
|
33
34
|
} = require('@contrast/common');
|
|
34
35
|
const { Core } = require('@contrast/core/lib/ioc/core');
|
|
@@ -52,16 +53,13 @@ const { Core } = require('@contrast/core/lib/ioc/core');
|
|
|
52
53
|
// input and make a decision immediately (i.e., there is no sink). so these two
|
|
53
54
|
// rules will need to be implemented by the agent and called from these handlers.
|
|
54
55
|
|
|
55
|
-
|
|
56
56
|
// agent-lib treats each type of nosql database as a separate
|
|
57
57
|
// rule. they are all mapped to 'nosql-injection' in the agent.
|
|
58
58
|
// maybe this will need to change.
|
|
59
59
|
const agentLibRuleTypeToName = {
|
|
60
60
|
'nosql-injection-mongo': 'nosql-injection'
|
|
61
61
|
};
|
|
62
|
-
|
|
63
62
|
const preferWW = { preferWorthWatching: true };
|
|
64
|
-
|
|
65
63
|
const acceptedMethods = new Set([
|
|
66
64
|
'acl',
|
|
67
65
|
'baseline-control',
|
|
@@ -95,9 +93,11 @@ const acceptedMethods = new Set([
|
|
|
95
93
|
'update',
|
|
96
94
|
'version-control',
|
|
97
95
|
]);
|
|
96
|
+
const componentName = 'protect.inputAnalysis.handlers';
|
|
97
|
+
const noInstrumentationStore = { lock: true, name: componentName };
|
|
98
98
|
|
|
99
99
|
module.exports = Core.makeComponent({
|
|
100
|
-
name:
|
|
100
|
+
name: componentName,
|
|
101
101
|
factory(core) {
|
|
102
102
|
const {
|
|
103
103
|
logger,
|
|
@@ -106,6 +106,7 @@ module.exports = Core.makeComponent({
|
|
|
106
106
|
agentLib,
|
|
107
107
|
inputAnalysis,
|
|
108
108
|
},
|
|
109
|
+
scopes,
|
|
109
110
|
config,
|
|
110
111
|
} = core;
|
|
111
112
|
|
|
@@ -676,41 +677,48 @@ module.exports = Core.makeComponent({
|
|
|
676
677
|
const findingsForScoreAtom = {};
|
|
677
678
|
const valueToResultByRuleId = {};
|
|
678
679
|
|
|
679
|
-
Object.values(resultsMap).
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
680
|
+
const flattened = Object.values(resultsMap).flatMap(identity);
|
|
681
|
+
for (const result of flattened) {
|
|
682
|
+
const {
|
|
683
|
+
ruleId,
|
|
684
|
+
exploited,
|
|
685
|
+
score,
|
|
686
|
+
value,
|
|
687
|
+
key,
|
|
688
|
+
inputType
|
|
689
|
+
} = result;
|
|
690
|
+
if (
|
|
691
|
+
sourceContext.policy.getRuleMode(ruleId) !== MONITOR ||
|
|
692
|
+
exploited === true ||
|
|
693
|
+
score >= 90 ||
|
|
694
|
+
!probesRules.some((rule) => rule === ruleId) ||
|
|
695
|
+
inputType == InputType.UNKNOWN ||
|
|
696
|
+
flattened.some((maybeReported) => (
|
|
697
|
+
// remove chances of duplicate analysis for "similar" findings that
|
|
698
|
+
// would have already been reported for being blocked or exploited
|
|
699
|
+
maybeReported.value == result.value &&
|
|
700
|
+
maybeReported.inputType == result.inputType &&
|
|
701
|
+
maybeReported.key == result.key &&
|
|
702
|
+
(maybeReported.exploited || maybeReported.blocked)
|
|
703
|
+
))
|
|
704
|
+
) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
689
707
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
!probesRules.some((rule) => rule === ruleId) ||
|
|
695
|
-
inputType == InputType.UNKNOWN
|
|
696
|
-
) {
|
|
697
|
-
return;
|
|
708
|
+
const dataType = findingsForScoreRequest[inputType];
|
|
709
|
+
if (!dataType) {
|
|
710
|
+
if (!findingsForScoreAtom[value]) {
|
|
711
|
+
findingsForScoreAtom[value] = {};
|
|
698
712
|
}
|
|
699
713
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
findingsForScoreAtom[value] = {};
|
|
704
|
-
}
|
|
714
|
+
findingsForScoreAtom[value][inputType] = result;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
705
717
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
718
|
+
dataType[key] = value;
|
|
719
|
+
valueToResultByRuleId[value] = result;
|
|
720
|
+
}
|
|
709
721
|
|
|
710
|
-
dataType[key] = value;
|
|
711
|
-
valueToResultByRuleId[value] = resultByRuleId;
|
|
712
|
-
});
|
|
713
|
-
});
|
|
714
722
|
const { ParameterValue, HeaderValue, CookieValue } = findingsForScoreRequest;
|
|
715
723
|
const results =
|
|
716
724
|
agentLib.scoreRequestConnect(
|
|
@@ -938,12 +946,21 @@ module.exports = Core.makeComponent({
|
|
|
938
946
|
function ipListAnalysis(reqIp, reqHeaders, list) {
|
|
939
947
|
const forwardedIps = [];
|
|
940
948
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
949
|
+
// RegExp.exec will still get called under the hood even with using primordials
|
|
950
|
+
scopes.instrumentation.run(noInstrumentationStore, () => {
|
|
951
|
+
for (let i = 0; i < reqHeaders.length; i++) {
|
|
952
|
+
if (reqHeaders[i] === 'x-forwarded-for') {
|
|
953
|
+
// This matches one or more valid IPv4/IPv6 characters, automatically
|
|
954
|
+
// skipping delimiters, whitespace, and invalid characters
|
|
955
|
+
const matches = StringPrototypeMatch.call(reqHeaders[i + 1], /[0-9a-fA-F.:]+/g);
|
|
956
|
+
if (matches) {
|
|
957
|
+
for (const ip of matches) {
|
|
958
|
+
forwardedIps.push(ip);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
945
962
|
}
|
|
946
|
-
}
|
|
963
|
+
});
|
|
947
964
|
|
|
948
965
|
const ipsToCheck = [reqIp, ...forwardedIps];
|
|
949
966
|
const now = new Date().getTime();
|
|
@@ -960,7 +977,7 @@ module.exports = Core.makeComponent({
|
|
|
960
977
|
|
|
961
978
|
// Ignore bad IP values.
|
|
962
979
|
if (!address.isValid(currentIp)) {
|
|
963
|
-
logger.
|
|
980
|
+
logger.debug({ value: currentIp, length: currentIp.length }, 'Unable to parse IP address %s.', currentIp);
|
|
964
981
|
continue;
|
|
965
982
|
}
|
|
966
983
|
const expired = doesExpire ? expiresAt - now <= 0 : false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.75.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)",
|
|
@@ -20,18 +20,18 @@
|
|
|
20
20
|
"test": "bash ../scripts/test.sh"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@contrast/agent-lib": "
|
|
24
|
-
"@contrast/common": "1.41.
|
|
25
|
-
"@contrast/config": "1.
|
|
26
|
-
"@contrast/core": "1.
|
|
27
|
-
"@contrast/dep-hooks": "1.
|
|
28
|
-
"@contrast/esm-hooks": "2.
|
|
29
|
-
"@contrast/instrumentation": "1.
|
|
30
|
-
"@contrast/logger": "1.
|
|
31
|
-
"@contrast/patcher": "1.
|
|
32
|
-
"@contrast/rewriter": "1.
|
|
33
|
-
"@contrast/scopes": "1.
|
|
34
|
-
"@contrast/stack-trace-factory": "1.
|
|
23
|
+
"@contrast/agent-lib": "9.2.0",
|
|
24
|
+
"@contrast/common": "1.41.1",
|
|
25
|
+
"@contrast/config": "1.58.0",
|
|
26
|
+
"@contrast/core": "1.63.0",
|
|
27
|
+
"@contrast/dep-hooks": "1.32.0",
|
|
28
|
+
"@contrast/esm-hooks": "2.38.0",
|
|
29
|
+
"@contrast/instrumentation": "1.42.0",
|
|
30
|
+
"@contrast/logger": "1.36.0",
|
|
31
|
+
"@contrast/patcher": "1.35.0",
|
|
32
|
+
"@contrast/rewriter": "1.40.0",
|
|
33
|
+
"@contrast/scopes": "1.33.0",
|
|
34
|
+
"@contrast/stack-trace-factory": "1.3.0",
|
|
35
35
|
"async-hook-domain": "^4.0.1",
|
|
36
36
|
"ipaddr.js": "^2.0.1",
|
|
37
37
|
"on-finished": "^2.4.1",
|