@contrast/protect 1.4.0 → 1.6.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/error-handlers/index.js +7 -4
- package/lib/error-handlers/install/{fastify3.js → fastify.js} +12 -12
- package/lib/error-handlers/install/hapi.js +75 -0
- package/lib/hardening/handlers.js +1 -1
- package/lib/index.js +3 -47
- package/lib/input-analysis/handlers.js +92 -37
- package/lib/input-analysis/index.js +5 -6
- package/lib/input-analysis/install/body-parser1.js +11 -1
- package/lib/input-analysis/install/fastify.js +84 -0
- package/lib/input-analysis/install/hapi.js +106 -0
- package/lib/input-analysis/install/http.js +74 -31
- package/lib/input-tracing/handlers/index.js +8 -3
- package/lib/input-tracing/index.js +9 -19
- package/lib/input-tracing/install/child-process.js +1 -0
- package/lib/input-tracing/install/eval.js +3 -3
- package/lib/input-tracing/install/fs.js +1 -0
- package/lib/input-tracing/install/function.js +62 -0
- package/lib/make-source-context.js +4 -48
- package/lib/policy.js +134 -0
- package/lib/semantic-analysis/handlers.js +43 -25
- package/package.json +5 -5
- package/lib/input-analysis/install/fastify3.js +0 -106
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
|
+
Rule,
|
|
19
20
|
BLOCKING_MODES,
|
|
21
|
+
ProtectRuleMode: { OFF },
|
|
20
22
|
InputType,
|
|
21
23
|
isString,
|
|
22
24
|
simpleTraverse
|
|
@@ -25,12 +27,17 @@ const {
|
|
|
25
27
|
const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
|
|
26
28
|
const stripWhiteSpace = (str) => str.replace(/\s/g, '');
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
const getRuleResults = function(obj, prop) {
|
|
31
|
+
return obj[prop] || (obj[prop] = []);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Semantic analysis currently shares instrumentation with the input-tracing sinks.
|
|
35
|
+
// See files in protect/lib/input-tracing/install/.
|
|
36
|
+
|
|
29
37
|
module.exports = function(core) {
|
|
30
38
|
const { protect: { agentLib, semanticAnalysis, throwSecurityException } } = core;
|
|
31
39
|
|
|
32
40
|
function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
|
|
33
|
-
const sinkResults = sourceContext.findings.semanticResultsMap[ruleId];
|
|
34
41
|
const result = {
|
|
35
42
|
blocked: false,
|
|
36
43
|
findings: { command: sinkContext.value },
|
|
@@ -38,7 +45,7 @@ module.exports = function(core) {
|
|
|
38
45
|
...finding
|
|
39
46
|
};
|
|
40
47
|
|
|
41
|
-
sourceContext.findings.semanticResultsMap
|
|
48
|
+
getRuleResults(sourceContext.findings.semanticResultsMap, ruleId).push(result);
|
|
42
49
|
|
|
43
50
|
if (BLOCKING_MODES.includes(mode)) {
|
|
44
51
|
result.blocked = true;
|
|
@@ -49,41 +56,50 @@ module.exports = function(core) {
|
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
semanticAnalysis.handleCmdInjectionSemanticDangerous = function(sourceContext, sinkContext) {
|
|
52
|
-
const
|
|
53
|
-
const { mode } = sourceContext.rules.agentRules[ruleId];
|
|
59
|
+
const mode = sourceContext.policy[Rule.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS];
|
|
54
60
|
|
|
55
|
-
if (mode ==
|
|
61
|
+
if (mode == OFF) return;
|
|
56
62
|
|
|
57
63
|
const result = agentLib.containsDangerousPath(sinkContext.value);
|
|
58
64
|
|
|
59
65
|
if (result) {
|
|
60
|
-
handleResult(sourceContext, sinkContext,
|
|
66
|
+
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS, mode);
|
|
61
67
|
}
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
semanticAnalysis.handleCmdInjectionSemanticChainedCommands = function(sourceContext, sinkContext) {
|
|
65
|
-
const
|
|
66
|
-
const { mode } = sourceContext.rules.agentRules[ruleId];
|
|
71
|
+
const mode = sourceContext.policy[Rule.CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS];
|
|
67
72
|
|
|
68
|
-
if (mode ==
|
|
73
|
+
if (mode == OFF) return;
|
|
69
74
|
|
|
70
75
|
const indexOfChaining = agentLib.indexOfChaining(sinkContext.value);
|
|
71
76
|
|
|
72
77
|
if (indexOfChaining != -1) {
|
|
73
|
-
handleResult(sourceContext, sinkContext,
|
|
78
|
+
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS, mode);
|
|
74
79
|
}
|
|
75
80
|
};
|
|
76
81
|
|
|
77
82
|
semanticAnalysis.handleCommandInjectionCommandBackdoors = function(sourceContext, sinkContext) {
|
|
78
|
-
const
|
|
79
|
-
const { mode } = sourceContext.rules.agentRules[ruleId];
|
|
83
|
+
const mode = sourceContext.policy[Rule.CMD_INJECTION_COMMAND_BACKDOORS];
|
|
80
84
|
|
|
81
|
-
if (mode ==
|
|
85
|
+
if (mode == OFF) return;
|
|
82
86
|
|
|
83
87
|
const finding = findBackdoorInjection(sourceContext, sinkContext.value);
|
|
84
88
|
|
|
85
89
|
if (finding) {
|
|
86
|
-
handleResult(sourceContext, sinkContext,
|
|
90
|
+
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode, finding);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
semanticAnalysis.handlePathTraversalFileSecurityBypass = function(sourceContext, sinkContext) {
|
|
95
|
+
const mode = sourceContext.policy[Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS];
|
|
96
|
+
|
|
97
|
+
if (mode == OFF) return;
|
|
98
|
+
|
|
99
|
+
if (agentLib.isDangerousPath(sinkContext.value, true)) {
|
|
100
|
+
handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode, {
|
|
101
|
+
findings: { path: sinkContext.value }
|
|
102
|
+
});
|
|
87
103
|
}
|
|
88
104
|
};
|
|
89
105
|
|
|
@@ -118,20 +134,22 @@ function findBackdoorInjection(sourceContext, command) {
|
|
|
118
134
|
simpleTraverse(values, (path, type, value, obj) => {
|
|
119
135
|
if (
|
|
120
136
|
!found &&
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
isString(value) &&
|
|
124
|
-
isBackdoorDetected(value, command)
|
|
137
|
+
type === 'Value' &&
|
|
138
|
+
isBackdoorDetected(value, command)
|
|
125
139
|
) {
|
|
126
140
|
let key;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
if (inputType === InputType.HEADER) {
|
|
142
|
+
key = obj[path[0] - 1]
|
|
143
|
+
} else {
|
|
144
|
+
key = path[path.length - 1];
|
|
130
145
|
}
|
|
131
|
-
path = path.length === 1 ? [] : Array.from(path).slice(0, path.length - 1);
|
|
132
|
-
inputType = path.length > 1 ? InputType.JSON_VALUE : inputType;
|
|
133
146
|
|
|
134
|
-
found = {
|
|
147
|
+
found = {
|
|
148
|
+
key,
|
|
149
|
+
inputType: path.length > 1 ? InputType.JSON_VALUE : inputType,
|
|
150
|
+
path: path.slice(0, -1),
|
|
151
|
+
value: command
|
|
152
|
+
};
|
|
135
153
|
}
|
|
136
154
|
});
|
|
137
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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)",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/template": "^7.16.7",
|
|
21
21
|
"@babel/types": "^7.16.8",
|
|
22
|
-
"@contrast/agent-lib": "^5.
|
|
23
|
-
"@contrast/common": "1.1.
|
|
24
|
-
"@contrast/core": "1.
|
|
25
|
-
"@contrast/esm-hooks": "1.1.
|
|
22
|
+
"@contrast/agent-lib": "^5.1.0",
|
|
23
|
+
"@contrast/common": "1.1.2",
|
|
24
|
+
"@contrast/core": "1.5.0",
|
|
25
|
+
"@contrast/esm-hooks": "1.1.6",
|
|
26
26
|
"@contrast/scopes": "1.1.1",
|
|
27
27
|
"builtin-modules": "^3.2.0",
|
|
28
28
|
"ipaddr.js": "^2.0.1",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
-
* Contact: support@contrastsecurity.com
|
|
4
|
-
* License: Commercial
|
|
5
|
-
|
|
6
|
-
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
-
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
-
* made available through public repositories, use of this Software is subject to
|
|
9
|
-
* the applicable End User Licensing Agreement found at
|
|
10
|
-
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
-
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
-
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
-
* way not consistent with the End User License Agreement.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
const { patchType } = require('../constants');
|
|
19
|
-
const { isSecurityException } = require('../../security-exception');
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Function that exports an install method to patch Fastify framework with our instrumentation
|
|
23
|
-
* @param {Object} core - the core Contrast object in v5
|
|
24
|
-
* @return {Object} object with install method and the other relative functions exported for testing purposes
|
|
25
|
-
*/
|
|
26
|
-
module.exports = (core) => {
|
|
27
|
-
const {
|
|
28
|
-
depHooks,
|
|
29
|
-
patcher,
|
|
30
|
-
logger,
|
|
31
|
-
protect,
|
|
32
|
-
protect: { inputAnalysis },
|
|
33
|
-
} = core;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* registers a depHook for fastify module instrumentation
|
|
37
|
-
*/
|
|
38
|
-
function install() {
|
|
39
|
-
depHooks.resolve({ name: 'fastify', version: '>=3.0.0' }, (fastify) => patchFastify(fastify));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The patch function for the depHooks callback
|
|
44
|
-
* @param {Object} fastify the fastify object returned from requiring the module
|
|
45
|
-
* @returns a patched fastify object
|
|
46
|
-
*/
|
|
47
|
-
function patchFastify(fastify) {
|
|
48
|
-
return patcher.patch(fastify, {
|
|
49
|
-
name: 'fastify.build',
|
|
50
|
-
patchType,
|
|
51
|
-
post({ result: server }) {
|
|
52
|
-
server.addHook('preValidation', preValidationHook);
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Fastify lifecycle hook as defined in official docs.
|
|
59
|
-
* @external https://www.fastify.io/docs/latest/Reference/Hooks/#prevalidation
|
|
60
|
-
* @param {Fastify.Request} request incoming request
|
|
61
|
-
* @param {Fastify.Reply} reply unbuilt outgoing response
|
|
62
|
-
* @param {Function} done callback to signal the hook is finished.
|
|
63
|
-
*/
|
|
64
|
-
function preValidationHook(request, reply, done) {
|
|
65
|
-
const sourceContext = protect.getSourceContext('Fastify.preValidationHook');
|
|
66
|
-
|
|
67
|
-
let securityException;
|
|
68
|
-
|
|
69
|
-
if (sourceContext) {
|
|
70
|
-
try {
|
|
71
|
-
if (request.params) {
|
|
72
|
-
sourceContext.parsedParams = request.params;
|
|
73
|
-
inputAnalysis.handleUrlParams(sourceContext, request.params);
|
|
74
|
-
}
|
|
75
|
-
if (request.cookies) {
|
|
76
|
-
sourceContext.parsedCookies = request.cookies;
|
|
77
|
-
inputAnalysis.handleCookies(sourceContext, request.cookies);
|
|
78
|
-
}
|
|
79
|
-
if (request.body) {
|
|
80
|
-
sourceContext.parsedBody = request.body;
|
|
81
|
-
inputAnalysis.handleParsedBody(sourceContext, request.body);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (request.query) {
|
|
85
|
-
sourceContext.parsedQuery = request.query;
|
|
86
|
-
inputAnalysis.handleQueryParams(sourceContext, request.query);
|
|
87
|
-
}
|
|
88
|
-
} catch (err) {
|
|
89
|
-
if (isSecurityException(err)) {
|
|
90
|
-
securityException = err;
|
|
91
|
-
} else {
|
|
92
|
-
logger.error({ err }, 'Unexpected error during input analysis');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
done(securityException);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const fastify3Instrumentation = inputAnalysis.fastify3Instrumentation = {
|
|
101
|
-
preValidationHook,
|
|
102
|
-
install,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return fastify3Instrumentation;
|
|
106
|
-
};
|