@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.
- package/lib/input-analysis/handlers.js +7 -4
- package/lib/input-analysis/ip-analysis.js +2 -2
- package/lib/input-analysis/virtual-patches.js +2 -2
- package/lib/input-tracing/handlers/index.js +2 -1
- package/lib/make-response-blocker.js +3 -1
- package/lib/make-source-context.js +6 -4
- package/lib/policy.js +48 -45
- package/lib/semantic-analysis/handlers.js +2 -1
- package/lib/semantic-analysis/utils/xml-analysis.js +6 -4
- package/package.json +6 -6
|
@@ -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
|
-
]
|
|
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]
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
38
|
-
queries = req.url
|
|
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]
|
|
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]
|
|
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: {
|
|
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(`^${
|
|
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
|
-
|
|
259
|
-
|
|
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 =
|
|
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
|
|
328
|
-
const cookieKey =
|
|
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) =>
|
|
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
|
|
25
|
-
const HTTP = `${PROTOCOLS.HTTP
|
|
26
|
-
const HTTPS = `${PROTOCOLS.HTTPS
|
|
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 &&
|
|
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.
|
|
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.
|
|
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.
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/esm-hooks": "1.
|
|
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
|
+
}
|