@contrast/protect 1.64.2 → 1.65.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/get-source-context.js +3 -1
- package/lib/index.js +6 -1
- package/lib/input-analysis/handlers.js +7 -9
- package/lib/input-analysis/install/http.js +18 -19
- package/lib/input-analysis/install/qs6.js +18 -17
- package/lib/input-analysis/install/universal-cookie4.js +2 -3
- package/lib/make-source-context.js +22 -66
- package/lib/semantic-analysis/handlers.js +73 -72
- package/package.json +11 -11
|
@@ -22,7 +22,9 @@ module.exports = function init(core) {
|
|
|
22
22
|
if (!core.config.getEffectiveValue('protect.enable')) return null;
|
|
23
23
|
|
|
24
24
|
const sourceContext = sources.getStore()?.protect;
|
|
25
|
-
|
|
25
|
+
if (!sourceContext) return null;
|
|
26
|
+
|
|
27
|
+
return sourceContext.allowed ? null : sourceContext;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
core.protect.getSourceContext = getSourceContext;
|
package/lib/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
|
20
20
|
const { ConfigSource } = require('@contrast/config');
|
|
21
21
|
|
|
22
22
|
module.exports = function(core) {
|
|
23
|
-
const { config } = core;
|
|
23
|
+
const { config, sources } = core;
|
|
24
24
|
|
|
25
25
|
const protect = core.protect = {
|
|
26
26
|
agentLib: module.exports.instantiateAgentLib(),
|
|
@@ -55,6 +55,11 @@ module.exports = function(core) {
|
|
|
55
55
|
callChildComponentMethodsSync(protect, 'install');
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
// append async state to store when request-scope sources are created
|
|
59
|
+
sources.addHook('onSource', (ctx) => {
|
|
60
|
+
ctx.store.protect = protect.makeSourceContext(ctx);
|
|
61
|
+
});
|
|
62
|
+
|
|
58
63
|
return protect;
|
|
59
64
|
};
|
|
60
65
|
|
|
@@ -117,7 +117,6 @@ module.exports = Core.makeComponent({
|
|
|
117
117
|
|
|
118
118
|
// all handlers will be invoked with two arguments:
|
|
119
119
|
// 1) sourceContext object containing:
|
|
120
|
-
// - reqData, the abstract request object containing only what is needed
|
|
121
120
|
// - protect, the protect context
|
|
122
121
|
// - rules, exclusions, virtual patches (TS data). what was in effect for this
|
|
123
122
|
// url *at the time the request was started*. these will not change.
|
|
@@ -162,7 +161,7 @@ module.exports = Core.makeComponent({
|
|
|
162
161
|
* 'connectInputs' makes sense; a flag similar to 'contentType' can be set and it can be
|
|
163
162
|
* used later to avoid calling 'handleQueryParams()'
|
|
164
163
|
*
|
|
165
|
-
* @param {Object} sourceContext {
|
|
164
|
+
* @param {Object} sourceContext { protect } that will be supplied to
|
|
166
165
|
* all handlers and sinks for this request. It will always be supplied by the caller
|
|
167
166
|
* to a handler; the handler is not aware of the implementation.
|
|
168
167
|
* @param {Object} connectInputs each property is an input to be evaluated by this
|
|
@@ -343,7 +342,8 @@ module.exports = Core.makeComponent({
|
|
|
343
342
|
|
|
344
343
|
let bodyType;
|
|
345
344
|
let inputTypes;
|
|
346
|
-
|
|
345
|
+
const { sourceInfo } = core.scopes.sources.getStore();
|
|
346
|
+
if (sourceInfo?.contentType?.includes?.('/json')) {
|
|
347
347
|
bodyType = 'json';
|
|
348
348
|
inputTypes = jsonInputTypes;
|
|
349
349
|
} else {
|
|
@@ -438,9 +438,8 @@ module.exports = Core.makeComponent({
|
|
|
438
438
|
inputAnalysis.handleIpAllowlist = function(sourceContext, ipAllowlist) {
|
|
439
439
|
if (!sourceContext || !ipAllowlist.length) return;
|
|
440
440
|
|
|
441
|
-
const {
|
|
442
|
-
|
|
443
|
-
const match = ipListAnalysis(reqIp, reqHeaders, ipAllowlist);
|
|
441
|
+
const { sourceInfo } = core.scopes.sources.getStore();
|
|
442
|
+
const match = ipListAnalysis(sourceInfo.ip, sourceInfo.rawHeaders, ipAllowlist);
|
|
444
443
|
|
|
445
444
|
if (match) {
|
|
446
445
|
logger.info(match, 'Found a matching IP to an entry in ipAllow list');
|
|
@@ -453,9 +452,8 @@ module.exports = Core.makeComponent({
|
|
|
453
452
|
|
|
454
453
|
if (!sourceContext || !ipDenylist.length) return;
|
|
455
454
|
|
|
456
|
-
const {
|
|
457
|
-
|
|
458
|
-
const match = ipListAnalysis(reqIp, reqHeaders, ipDenylist);
|
|
455
|
+
const { sourceInfo } = core.scopes.sources.getStore();
|
|
456
|
+
const match = ipListAnalysis(sourceInfo.Ip, sourceInfo.rawHeaders, ipDenylist);
|
|
459
457
|
|
|
460
458
|
if (match) {
|
|
461
459
|
logger.info(match, 'Found a matching IP to an entry in ipDeny list');
|
|
@@ -31,24 +31,19 @@ module.exports = function (core) {
|
|
|
31
31
|
},
|
|
32
32
|
} = core;
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
function removeCookies(headers) {
|
|
40
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
41
|
-
if (headers[i] === 'cookies') {
|
|
42
|
-
headers = ArrayPrototypeSlice.call(headers);
|
|
43
|
-
headers.splice(i, 2);
|
|
34
|
+
function removeCookies(rawHeaders) {
|
|
35
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
36
|
+
if (rawHeaders[i] === 'cookies') {
|
|
37
|
+
rawHeaders = ArrayPrototypeSlice.call(rawHeaders);
|
|
38
|
+
rawHeaders.splice(i, 2);
|
|
44
39
|
}
|
|
45
40
|
}
|
|
46
|
-
return
|
|
41
|
+
return rawHeaders;
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
function around(next, data) {
|
|
50
45
|
let store, block;
|
|
51
|
-
const { args: [type
|
|
46
|
+
const { args: [type,, res] } = data;
|
|
52
47
|
|
|
53
48
|
function callNext() {
|
|
54
49
|
setImmediate(() => {
|
|
@@ -63,21 +58,20 @@ module.exports = function (core) {
|
|
|
63
58
|
|
|
64
59
|
try {
|
|
65
60
|
store = sources.getStore();
|
|
66
|
-
if (!store) {
|
|
61
|
+
if (!store?.protect) {
|
|
67
62
|
logger.debug({ funcKey: data.funcKey }, 'request store not available during http input-analysis');
|
|
63
|
+
callNext();
|
|
68
64
|
return;
|
|
69
65
|
}
|
|
70
|
-
|
|
71
|
-
store.protect = core.protect.makeSourceContext(req, res);
|
|
72
66
|
if (store.protect.allowed) {
|
|
73
67
|
callNext();
|
|
74
68
|
return;
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
const {
|
|
78
|
-
|
|
79
|
-
resData
|
|
80
|
-
} = store
|
|
72
|
+
sourceInfo: { method, rawHeaders, uriPath },
|
|
73
|
+
protect: { resData }
|
|
74
|
+
} = store;
|
|
81
75
|
|
|
82
76
|
onFinished(res, (/* err, req */) => {
|
|
83
77
|
resData.statusCode = res.statusCode;
|
|
@@ -86,7 +80,7 @@ module.exports = function (core) {
|
|
|
86
80
|
});
|
|
87
81
|
|
|
88
82
|
const connectInputs = {
|
|
89
|
-
headers: removeCookies(
|
|
83
|
+
headers: removeCookies(rawHeaders),
|
|
90
84
|
uriPath,
|
|
91
85
|
method: StringPrototypeToLowerCase.call(method),
|
|
92
86
|
};
|
|
@@ -131,5 +125,10 @@ module.exports = function (core) {
|
|
|
131
125
|
});
|
|
132
126
|
}
|
|
133
127
|
|
|
128
|
+
const instr = inputAnalysis.httpInstrumentation = {
|
|
129
|
+
install,
|
|
130
|
+
around
|
|
131
|
+
};
|
|
132
|
+
|
|
134
133
|
return instr;
|
|
135
134
|
};
|
|
@@ -22,34 +22,35 @@ module.exports = (core) => {
|
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
24
|
protect,
|
|
25
|
-
|
|
25
|
+
scopes,
|
|
26
26
|
} = core;
|
|
27
27
|
|
|
28
28
|
// Patch `qs`
|
|
29
29
|
function install() {
|
|
30
|
-
depHooks.resolve({ name: 'qs', version: '<7' },
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
30
|
+
depHooks.resolve({ name: 'qs', version: '<7' }, (qs) => patcher.patch(qs, 'parse', {
|
|
31
|
+
name: 'qs',
|
|
32
|
+
patchType,
|
|
33
|
+
post({ args, result }) {
|
|
34
|
+
if (result && Object.keys(result).length) {
|
|
35
|
+
const sourceContext = protect.getSourceContext();
|
|
36
|
+
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
37
|
+
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
38
|
+
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
39
|
+
// some cases its use is optional and we cannot rely on it.
|
|
40
|
+
if (sourceContext) {
|
|
41
|
+
const { sourceInfo } = scopes.sources.getStore();
|
|
42
|
+
if (sourceInfo.queries === args[0]) {
|
|
43
43
|
sourceContext.parsedQuery = result;
|
|
44
|
-
inputAnalysis.handleQueryParams(sourceContext, result);
|
|
44
|
+
protect.inputAnalysis.handleQueryParams(sourceContext, result);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const qs6Instrumentation = inputAnalysis.qs6Instrumentation = {
|
|
53
|
+
const qs6Instrumentation = protect.inputAnalysis.qs6Instrumentation = {
|
|
53
54
|
install
|
|
54
55
|
};
|
|
55
56
|
|
|
@@ -22,7 +22,6 @@ module.exports = (core) => {
|
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
24
|
protect,
|
|
25
|
-
protect: { inputAnalysis },
|
|
26
25
|
} = core;
|
|
27
26
|
|
|
28
27
|
// Patch `universal-cookie` package
|
|
@@ -36,7 +35,7 @@ module.exports = (core) => {
|
|
|
36
35
|
|
|
37
36
|
if (sourceContext) {
|
|
38
37
|
sourceContext.parsedCookies = result;
|
|
39
|
-
inputAnalysis.handleCookies(sourceContext, result);
|
|
38
|
+
protect.inputAnalysis.handleCookies(sourceContext, result);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -44,7 +43,7 @@ module.exports = (core) => {
|
|
|
44
43
|
);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const universalCookie4Instrumentation = inputAnalysis.universalCookie4Instrumentation = {
|
|
46
|
+
const universalCookie4Instrumentation = protect.inputAnalysis.universalCookie4Instrumentation = {
|
|
48
47
|
install
|
|
49
48
|
};
|
|
50
49
|
|
|
@@ -15,80 +15,36 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { primordials: { StringPrototypeToLowerCase, StringPrototypeSlice } } = require('@contrast/common');
|
|
19
|
-
|
|
20
18
|
module.exports = function(core) {
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let uriPath, queries;
|
|
42
|
-
const ix = req.url.indexOf('?');
|
|
43
|
-
|
|
44
|
-
if (ix >= 0) {
|
|
45
|
-
uriPath = StringPrototypeSlice.call(req.url, 0, ix);
|
|
46
|
-
queries = StringPrototypeSlice.call(req.url, ix + 1);
|
|
47
|
-
} else {
|
|
48
|
-
uriPath = req.url;
|
|
49
|
-
queries = '';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const policy = getPolicy({ uriPath });
|
|
53
|
-
|
|
19
|
+
const { protect } = core;
|
|
20
|
+
|
|
21
|
+
const DISABLED_POLICY = { allowed: true };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} param
|
|
25
|
+
* @param {object} param.store
|
|
26
|
+
* @param {import('@contrast/common').SourceInfo} param.store.sourceInfo
|
|
27
|
+
* @param {import('node:http').IncomingMessage} param.incomingMessage
|
|
28
|
+
* @param {import('node:http').ServerResponse} param.serverResponse
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
function makeSourceContext({
|
|
32
|
+
store: { sourceInfo },
|
|
33
|
+
// incomingMessage,
|
|
34
|
+
serverResponse,
|
|
35
|
+
}) {
|
|
36
|
+
if (!core.config.getEffectiveValue('protect.enable')) return DISABLED_POLICY;
|
|
37
|
+
|
|
38
|
+
const policy = protect.getPolicy({ uriPath: sourceInfo.uriPath });
|
|
54
39
|
// URL exclusions can disable all rules
|
|
55
|
-
if (!policy || policy.rulesMask === 0)
|
|
56
|
-
return disabledPolicy;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// lowercase header keys and capture content-type
|
|
60
|
-
let contentType = '';
|
|
61
|
-
const headers = Array(req.rawHeaders.length);
|
|
62
|
-
|
|
63
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
64
|
-
headers[i] = StringPrototypeToLowerCase.call(req.rawHeaders[i]);
|
|
65
|
-
headers[i + 1] = req.rawHeaders[i + 1];
|
|
66
|
-
if (headers[i] === 'content-type') {
|
|
67
|
-
contentType = StringPrototypeToLowerCase.call(headers[i + 1]);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// contains request data and information derived from request data. it's
|
|
72
|
-
// possible for any derived information to be derived later, but doing
|
|
73
|
-
// so here is typically better; it makes clear what information is used to
|
|
74
|
-
// make decisions by different handlers.
|
|
75
|
-
const reqData = {
|
|
76
|
-
ip: req.socket.remoteAddress,
|
|
77
|
-
httpVersion: req.httpVersion,
|
|
78
|
-
method: req.method,
|
|
79
|
-
headers,
|
|
80
|
-
uriPath,
|
|
81
|
-
queries,
|
|
82
|
-
contentType,
|
|
83
|
-
};
|
|
40
|
+
if (!policy || policy.rulesMask === 0) return DISABLED_POLICY;
|
|
84
41
|
|
|
85
42
|
const protectStore = {
|
|
86
|
-
reqData,
|
|
87
43
|
resData: {
|
|
88
44
|
statusCode: null,
|
|
89
45
|
},
|
|
90
46
|
// block closure captures res so it isn't exposed to beyond here
|
|
91
|
-
blocker: new core.protect.Blocker(
|
|
47
|
+
blocker: new core.protect.Blocker(serverResponse),
|
|
92
48
|
policy,
|
|
93
49
|
exclusions: [],
|
|
94
50
|
virtualPatchesEvaluators: [],
|
|
@@ -75,6 +75,79 @@ module.exports = function(core) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Backdoor detection logic:
|
|
80
|
+
* - command is >= 2 chars
|
|
81
|
+
* - iterates over every piece of request and checks
|
|
82
|
+
* - the full value is the param to sink
|
|
83
|
+
* - the value matches a regex and ends the param to the sink
|
|
84
|
+
*/
|
|
85
|
+
function findBackdoorInjection(sourceContext, command) {
|
|
86
|
+
if (command?.length < 2) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const { sourceInfo } = core.scopes.sources.getStore();
|
|
91
|
+
const valuesOfInterest = {
|
|
92
|
+
[InputType.QUERYSTRING]: sourceContext.parsedQuery,
|
|
93
|
+
[InputType.PARAMETER_VALUE]: sourceContext.parsedParams,
|
|
94
|
+
[InputType.BODY]: sourceContext.parsedBody,
|
|
95
|
+
[InputType.COOKIE_VALUE]: sourceContext.parsedCookies,
|
|
96
|
+
[InputType.HEADER]: sourceInfo.rawHeaders,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let found;
|
|
100
|
+
for (const inputType in valuesOfInterest) {
|
|
101
|
+
if (found) break;
|
|
102
|
+
|
|
103
|
+
const values = valuesOfInterest[inputType];
|
|
104
|
+
|
|
105
|
+
if (values && Object.keys(values).length) {
|
|
106
|
+
traverseValues(values, (path, type, value, obj) => {
|
|
107
|
+
if (isBackdoorDetected(value, command)) {
|
|
108
|
+
let key;
|
|
109
|
+
if (inputType === InputType.HEADER) {
|
|
110
|
+
key = obj[path[0] - 1];
|
|
111
|
+
} else {
|
|
112
|
+
key = path[path.length - 1];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
found = {
|
|
116
|
+
key,
|
|
117
|
+
inputType: path.length > 1 ? InputType.JSON_VALUE : inputType,
|
|
118
|
+
path: ArrayPrototypeSlice.call(path, 0, -1),
|
|
119
|
+
value: command
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// halt traversal
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return found;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* strips the whitespace of the request value and the command,
|
|
134
|
+
* checks if the command equals the request value
|
|
135
|
+
* or if the command looks like the start of a shell execution
|
|
136
|
+
* and ends with the request value passed to the sink
|
|
137
|
+
*
|
|
138
|
+
* @param {string} value from request key
|
|
139
|
+
*/
|
|
140
|
+
function isBackdoorDetected(requestValue, command) {
|
|
141
|
+
const normalizedValue = stripWhiteSpace(requestValue);
|
|
142
|
+
const normalizedCommand = stripWhiteSpace(command);
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
normalizedValue === normalizedCommand ||
|
|
146
|
+
(normalizedCommand.endsWith(normalizedValue) &&
|
|
147
|
+
RegExpPrototypeTest.call(SINK_EXPLOIT_PATTERN_START, normalizedCommand))
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
78
151
|
semanticAnalysis.handleCmdInjectionSemanticDangerous = function(sourceContext, sinkContext) {
|
|
79
152
|
const mode = sourceContext.policy[Rule.CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS];
|
|
80
153
|
|
|
@@ -137,75 +210,3 @@ module.exports = function(core) {
|
|
|
137
210
|
|
|
138
211
|
return semanticAnalysis;
|
|
139
212
|
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Backdoor detection logic:
|
|
143
|
-
* - command is >= 2 chars
|
|
144
|
-
* - iterates over every piece of request and checks
|
|
145
|
-
* - the full value is the param to sink
|
|
146
|
-
* - the value matches a regex and ends the param to the sink
|
|
147
|
-
*/
|
|
148
|
-
function findBackdoorInjection(sourceContext, command) {
|
|
149
|
-
if (command?.length < 2) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const valuesOfInterest = {
|
|
154
|
-
[InputType.QUERYSTRING]: sourceContext.parsedQuery,
|
|
155
|
-
[InputType.PARAMETER_VALUE]: sourceContext.parsedParams,
|
|
156
|
-
[InputType.BODY]: sourceContext.parsedBody,
|
|
157
|
-
[InputType.COOKIE_VALUE]: sourceContext.parsedCookies,
|
|
158
|
-
[InputType.HEADER]: sourceContext.reqData.headers,
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
let found;
|
|
162
|
-
for (const inputType in valuesOfInterest) {
|
|
163
|
-
if (found) break;
|
|
164
|
-
|
|
165
|
-
const values = valuesOfInterest[inputType];
|
|
166
|
-
|
|
167
|
-
if (values && Object.keys(values).length) {
|
|
168
|
-
traverseValues(values, (path, type, value, obj) => {
|
|
169
|
-
if (isBackdoorDetected(value, command)) {
|
|
170
|
-
let key;
|
|
171
|
-
if (inputType === InputType.HEADER) {
|
|
172
|
-
key = obj[path[0] - 1];
|
|
173
|
-
} else {
|
|
174
|
-
key = path[path.length - 1];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
found = {
|
|
178
|
-
key,
|
|
179
|
-
inputType: path.length > 1 ? InputType.JSON_VALUE : inputType,
|
|
180
|
-
path: ArrayPrototypeSlice.call(path, 0, -1),
|
|
181
|
-
value: command
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// halt traversal
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return found;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* strips the whitespace of the request value and the command,
|
|
196
|
-
* checks if the command equals the request value
|
|
197
|
-
* or if the command looks like the start of a shell execution
|
|
198
|
-
* and ends with the request value passed to the sink
|
|
199
|
-
*
|
|
200
|
-
* @param {string} value from request key
|
|
201
|
-
*/
|
|
202
|
-
function isBackdoorDetected(requestValue, command) {
|
|
203
|
-
const normalizedValue = stripWhiteSpace(requestValue);
|
|
204
|
-
const normalizedCommand = stripWhiteSpace(command);
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
normalizedValue === normalizedCommand ||
|
|
208
|
-
(normalizedCommand.endsWith(normalizedValue) &&
|
|
209
|
-
RegExpPrototypeTest.call(SINK_EXPLOIT_PATTERN_START, normalizedCommand))
|
|
210
|
-
);
|
|
211
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.65.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)",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@contrast/agent-lib": "^9.1.0",
|
|
24
|
-
"@contrast/common": "1.
|
|
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.
|
|
24
|
+
"@contrast/common": "1.35.0",
|
|
25
|
+
"@contrast/config": "1.50.0",
|
|
26
|
+
"@contrast/core": "1.55.0",
|
|
27
|
+
"@contrast/dep-hooks": "1.24.0",
|
|
28
|
+
"@contrast/esm-hooks": "2.29.0",
|
|
29
|
+
"@contrast/instrumentation": "1.34.0",
|
|
30
|
+
"@contrast/logger": "1.28.0",
|
|
31
|
+
"@contrast/patcher": "1.27.0",
|
|
32
|
+
"@contrast/rewriter": "1.31.0",
|
|
33
|
+
"@contrast/scopes": "1.25.0",
|
|
34
34
|
"async-hook-domain": "^4.0.1",
|
|
35
35
|
"ipaddr.js": "^2.0.1",
|
|
36
36
|
"on-finished": "^2.4.1",
|