@contrast/protect 1.16.0 → 1.18.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.
|
@@ -169,12 +169,8 @@ module.exports = function(core) {
|
|
|
169
169
|
{ URLS: connectInputs.rawUrl, HEADERS: connectInputs.headers }
|
|
170
170
|
);
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
let block =
|
|
174
|
-
if (rulesMask !== 0) {
|
|
175
|
-
const findings = agentLib.scoreRequestConnect(rulesMask, connectInputs, preferWW);
|
|
176
|
-
block = mergeFindings(sourceContext, findings);
|
|
177
|
-
}
|
|
172
|
+
const findings = agentLib.scoreRequestConnect(rulesMask, connectInputs, preferWW);
|
|
173
|
+
let block = mergeFindings(sourceContext, findings);
|
|
178
174
|
|
|
179
175
|
if (!block) {
|
|
180
176
|
block = inputAnalysis.handleMethodTampering(sourceContext, connectInputs);
|
|
@@ -510,7 +506,7 @@ module.exports = function(core) {
|
|
|
510
506
|
}
|
|
511
507
|
}
|
|
512
508
|
|
|
513
|
-
if (!config.protect.probe_analysis.enable
|
|
509
|
+
if (!config.protect.probe_analysis.enable) return;
|
|
514
510
|
|
|
515
511
|
// Detecting probes
|
|
516
512
|
const { resultsMap, policy: { rulesMask } } = sourceContext;
|
|
@@ -49,6 +49,20 @@ module.exports = function(core) {
|
|
|
49
49
|
let store, block;
|
|
50
50
|
const { args: [type, req, res] } = data;
|
|
51
51
|
|
|
52
|
+
function callNext() {
|
|
53
|
+
setImmediate(() => {
|
|
54
|
+
const domain = initDomain(req, res);
|
|
55
|
+
|
|
56
|
+
if (domain) {
|
|
57
|
+
domain.run(() => {
|
|
58
|
+
next.call(data.obj, ...data.args);
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
next.call(data.obj, ...data.args);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
if (type !== 'request') {
|
|
53
67
|
return next();
|
|
54
68
|
}
|
|
@@ -61,6 +75,11 @@ module.exports = function(core) {
|
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
store.protect = core.protect.makeSourceContext(req, res);
|
|
78
|
+
if (store.protect.allowed) {
|
|
79
|
+
callNext();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
const {
|
|
65
84
|
reqData: { headers, uriPath, method },
|
|
66
85
|
resData,
|
|
@@ -97,17 +116,7 @@ module.exports = function(core) {
|
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
if (!block) {
|
|
100
|
-
|
|
101
|
-
const domain = initDomain(req, res);
|
|
102
|
-
|
|
103
|
-
if (domain) {
|
|
104
|
-
domain.run(() => {
|
|
105
|
-
next.call(data.obj, ...data.args);
|
|
106
|
-
});
|
|
107
|
-
} else {
|
|
108
|
-
next.call(data.obj, ...data.args);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
119
|
+
callNext();
|
|
111
120
|
} else {
|
|
112
121
|
store.protect.block(...block);
|
|
113
122
|
logger.debug({ block }, 'request blocked by not emitting request event');
|
|
@@ -15,115 +15,101 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { isString } = require('@contrast/common');
|
|
18
|
+
const { FS_METHODS, isString } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
patcher.patch(fs, method, {
|
|
88
|
-
name,
|
|
89
|
-
patchType,
|
|
90
|
-
pre({ args, hooked, name, orig }) {
|
|
91
|
-
// don't proceed if instrumentation is off e.g. within require() call
|
|
92
|
-
if (instrumentation.isLocked()) return;
|
|
93
|
-
|
|
94
|
-
// obtain the Protect sourceContext
|
|
95
|
-
const sourceContext = protect.getSourceContext('fs');
|
|
96
|
-
|
|
97
|
-
if (!sourceContext) return;
|
|
98
|
-
|
|
99
|
-
// build list of values on which to perform INPUT TRACING
|
|
100
|
-
const values = getValues(indices, args);
|
|
101
|
-
if (!values.length) return;
|
|
102
|
-
|
|
103
|
-
// while we need to check whether instrumentation is locked above, we
|
|
104
|
-
// don't need to necessarily need to lock it here - there are no
|
|
105
|
-
// lower-level calls that we instrument
|
|
106
|
-
for (const value of values) {
|
|
107
|
-
const sinkContext = {
|
|
108
|
-
name,
|
|
109
|
-
value,
|
|
110
|
-
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
111
|
-
};
|
|
112
|
-
inputTracing.handlePathTraversal(sourceContext, sinkContext);
|
|
113
|
-
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
|
|
21
|
+
function getValues(indices, args) {
|
|
22
|
+
return indices.reduce((acc, idx) => {
|
|
23
|
+
const value = args[idx];
|
|
24
|
+
if (value && isString(value)) acc.push(value);
|
|
25
|
+
return acc;
|
|
26
|
+
}, []);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = function init(core) {
|
|
30
|
+
const { depHooks, patcher } = core;
|
|
31
|
+
|
|
32
|
+
const preHook = (indices) => ({ args, hooked, name, orig }) => {
|
|
33
|
+
// don't proceed if instrumentation is off e.g. within require() call
|
|
34
|
+
if (core.scopes.instrumentation.isLocked()) return;
|
|
35
|
+
|
|
36
|
+
// obtain the Protect sourceContext
|
|
37
|
+
const sourceContext = core.protect.getSourceContext('fs');
|
|
38
|
+
|
|
39
|
+
if (!sourceContext) return;
|
|
40
|
+
|
|
41
|
+
// build list of values on which to perform INPUT TRACING
|
|
42
|
+
const values = getValues(indices, args);
|
|
43
|
+
if (!values.length) return;
|
|
44
|
+
|
|
45
|
+
// while we need to check whether instrumentation is locked above, we
|
|
46
|
+
// don't need to necessarily need to lock it here - there are no
|
|
47
|
+
// lower-level calls that we instrument
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
const sinkContext = {
|
|
50
|
+
name,
|
|
51
|
+
value,
|
|
52
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
core.protect.inputTracing.handlePathTraversal(
|
|
56
|
+
sourceContext,
|
|
57
|
+
sinkContext
|
|
58
|
+
);
|
|
59
|
+
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(
|
|
60
|
+
sourceContext,
|
|
61
|
+
sinkContext,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return core.protect.inputTracing.fsInstrumentation = {
|
|
67
|
+
install() {
|
|
68
|
+
depHooks.resolve({ name: 'fs' }, (fs) => {
|
|
69
|
+
for (const method of FS_METHODS) {
|
|
70
|
+
// not all methods are available on every OS or Node version.
|
|
71
|
+
if (fs[method.name]) {
|
|
72
|
+
patcher.patch(fs, method.name, {
|
|
73
|
+
name: `fs.${method.name}`,
|
|
74
|
+
patchType,
|
|
75
|
+
pre: preHook(method.indices)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (method.sync) {
|
|
80
|
+
const syncName = `${method.name}Sync`;
|
|
81
|
+
if (fs[syncName]) {
|
|
82
|
+
patcher.patch(fs, syncName, {
|
|
83
|
+
name: `fs.${syncName}`,
|
|
84
|
+
patchType,
|
|
85
|
+
pre: preHook(method.indices)
|
|
86
|
+
});
|
|
114
87
|
}
|
|
115
88
|
}
|
|
116
|
-
|
|
89
|
+
|
|
90
|
+
if (method.promises && fs.promises && fs.promises[method.name]) {
|
|
91
|
+
patcher.patch(fs.promises, method.name, {
|
|
92
|
+
name: `fs.promises.${method.name}`,
|
|
93
|
+
patchType,
|
|
94
|
+
pre: preHook(method.indices)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
117
98
|
});
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
depHooks.resolve({ name: 'fs/promises' }, (fsPromises) => {
|
|
101
|
+
for (const method of FS_METHODS) {
|
|
102
|
+
if (method.promises && fsPromises[method.name]) {
|
|
103
|
+
patcher.patch(fsPromises, method.name, {
|
|
104
|
+
name: `fsPromises.${method.name}`,
|
|
105
|
+
patchType,
|
|
106
|
+
pre: preHook(method.indices)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
124
112
|
};
|
|
125
|
-
|
|
126
|
-
return fsInstrumentation;
|
|
127
113
|
};
|
|
128
114
|
|
|
129
|
-
module.exports.
|
|
115
|
+
module.exports.FS_METHODS = FS_METHODS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.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.9.0",
|
|
22
|
+
"@contrast/core": "1.16.0",
|
|
23
|
+
"@contrast/esm-hooks": "1.12.0",
|
|
24
24
|
"@contrast/scopes": "1.3.0",
|
|
25
25
|
"ipaddr.js": "^2.0.1",
|
|
26
26
|
"semver": "^7.3.7"
|