@contrast/protect 1.8.0 → 1.9.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/install/express4.js +3 -3
- package/lib/error-handlers/install/fastify.js +1 -1
- package/lib/error-handlers/install/hapi.js +1 -1
- package/lib/error-handlers/install/koa2.js +1 -1
- package/lib/hardening/handlers.js +9 -6
- package/lib/hardening/install/node-serialize0.js +5 -5
- package/lib/index.d.ts +5 -2
- package/lib/index.js +2 -2
- package/lib/input-analysis/handlers.js +26 -25
- package/lib/input-analysis/index.js +2 -2
- package/lib/input-analysis/install/http.js +0 -6
- package/lib/input-tracing/handlers/index.js +27 -16
- package/lib/input-tracing/index.js +2 -2
- package/lib/input-tracing/install/child-process.js +24 -24
- package/lib/input-tracing/install/eval.js +5 -5
- package/lib/input-tracing/install/fs.js +5 -5
- package/lib/input-tracing/install/function.js +6 -5
- package/lib/input-tracing/install/http.js +5 -5
- package/lib/input-tracing/install/mongodb.js +148 -160
- package/lib/input-tracing/install/mysql.js +5 -5
- package/lib/input-tracing/install/postgres.js +5 -5
- package/lib/input-tracing/install/sequelize.js +6 -5
- package/lib/input-tracing/install/sqlite3.js +5 -5
- package/lib/input-tracing/install/vm.js +25 -21
- package/lib/make-source-context.js +6 -23
- package/lib/policy.js +13 -8
- package/lib/semantic-analysis/handlers.js +20 -14
- package/lib/semantic-analysis/index.js +2 -5
- package/lib/semantic-analysis/install/libxmljs.js +5 -5
- package/lib/throw-security-exception.js +1 -3
- package/package.json +5 -5
|
@@ -44,7 +44,7 @@ module.exports = function(core) {
|
|
|
44
44
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
45
45
|
|
|
46
46
|
if (isSecurityException && sourceContext) {
|
|
47
|
-
const blockInfo = sourceContext.
|
|
47
|
+
const blockInfo = sourceContext.securityException;
|
|
48
48
|
|
|
49
49
|
sourceContext.block(...blockInfo);
|
|
50
50
|
return;
|
|
@@ -70,7 +70,7 @@ module.exports = function(core) {
|
|
|
70
70
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
71
71
|
|
|
72
72
|
if (isSecurityException && sourceContext) {
|
|
73
|
-
const blockInfo = sourceContext.
|
|
73
|
+
const blockInfo = sourceContext.securityException;
|
|
74
74
|
|
|
75
75
|
sourceContext.block(...blockInfo);
|
|
76
76
|
return;
|
|
@@ -123,7 +123,7 @@ module.exports = function(core) {
|
|
|
123
123
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
124
124
|
|
|
125
125
|
if (isSecurityException && sourceContext) {
|
|
126
|
-
const blockInfo = sourceContext.
|
|
126
|
+
const blockInfo = sourceContext.securityException;
|
|
127
127
|
|
|
128
128
|
sourceContext.block(...blockInfo);
|
|
129
129
|
return;
|
|
@@ -62,7 +62,7 @@ module.exports = function(core) {
|
|
|
62
62
|
if (!sourceContext) {
|
|
63
63
|
normalHandler.call(this, err, request, reply);
|
|
64
64
|
} else {
|
|
65
|
-
const blockInfo = sourceContext.
|
|
65
|
+
const blockInfo = sourceContext.securityException;
|
|
66
66
|
sourceContext.block(...blockInfo);
|
|
67
67
|
}
|
|
68
68
|
} else {
|
|
@@ -39,7 +39,7 @@ module.exports = function (core) {
|
|
|
39
39
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
40
40
|
|
|
41
41
|
if (isSecurityException && sourceContext && err.output.statusCode !== 403) {
|
|
42
|
-
const [mode, ruleId] = sourceContext.
|
|
42
|
+
const [mode, ruleId] = sourceContext.securityException;
|
|
43
43
|
|
|
44
44
|
err.output.statusCode = 403;
|
|
45
45
|
err.reformat();
|
|
@@ -46,7 +46,7 @@ module.exports = function(core) {
|
|
|
46
46
|
|
|
47
47
|
if (isSecurityException && sourceContext) {
|
|
48
48
|
data.obj.body = '';
|
|
49
|
-
const blockInfo = sourceContext.
|
|
49
|
+
const blockInfo = sourceContext.securityException;
|
|
50
50
|
sourceContext.block(...blockInfo);
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
@@ -24,13 +24,14 @@ module.exports = function(core) {
|
|
|
24
24
|
protect: {
|
|
25
25
|
hardening,
|
|
26
26
|
throwSecurityException,
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
|
+
captureStacktrace,
|
|
28
29
|
} = core;
|
|
29
30
|
|
|
30
31
|
function getResults(sourceContext, ruleId) {
|
|
31
|
-
let results = sourceContext.
|
|
32
|
+
let results = sourceContext.resultsMap[ruleId];
|
|
32
33
|
if (!results) {
|
|
33
|
-
results = sourceContext.
|
|
34
|
+
results = sourceContext.resultsMap[ruleId] = [];
|
|
34
35
|
}
|
|
35
36
|
return results;
|
|
36
37
|
}
|
|
@@ -38,7 +39,7 @@ module.exports = function(core) {
|
|
|
38
39
|
hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
|
|
39
40
|
const ruleId = 'untrusted-deserialization';
|
|
40
41
|
const mode = sourceContext.policy[ruleId];
|
|
41
|
-
const { name, value } = sinkContext;
|
|
42
|
+
const { name, value, stacktraceData } = sinkContext;
|
|
42
43
|
|
|
43
44
|
if (mode === 'off') return;
|
|
44
45
|
|
|
@@ -48,14 +49,16 @@ module.exports = function(core) {
|
|
|
48
49
|
const blocked = BLOCKING_MODES.includes(mode);
|
|
49
50
|
const results = getResults(sourceContext, ruleId);
|
|
50
51
|
|
|
52
|
+
captureStacktrace(sinkContext, stacktraceData);
|
|
51
53
|
results.push({
|
|
54
|
+
value: sinkContext.value,
|
|
52
55
|
blocked,
|
|
53
|
-
|
|
56
|
+
exploitMetadata: [{ deserializer: name, command: false }],
|
|
54
57
|
sinkContext,
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
if (blocked) {
|
|
58
|
-
sourceContext.
|
|
61
|
+
sourceContext.securityException = [mode, ruleId];
|
|
59
62
|
throwSecurityException(sourceContext);
|
|
60
63
|
}
|
|
61
64
|
}
|
|
@@ -21,7 +21,6 @@ module.exports = function(core) {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
captureStacktrace,
|
|
25
24
|
protect,
|
|
26
25
|
protect: {
|
|
27
26
|
hardening
|
|
@@ -43,10 +42,11 @@ module.exports = function(core) {
|
|
|
43
42
|
|
|
44
43
|
if (!sourceContext || !value) return;
|
|
45
44
|
|
|
46
|
-
const sinkContext =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const sinkContext = {
|
|
46
|
+
name: `${name}.${method}`,
|
|
47
|
+
value,
|
|
48
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
49
|
+
};
|
|
50
50
|
hardening.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
51
51
|
},
|
|
52
52
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { Logger } from '@contrast/logger';
|
|
17
17
|
import { Sources } from '@contrast/scopes';
|
|
18
18
|
import RequireHook from '@contrast/require-hook';
|
|
19
|
-
import { RulesConfig, Messages, ReqData, ProtectMessage,
|
|
19
|
+
import { RulesConfig, Messages, ReqData, ProtectMessage, ResultMap, ProtectRuleMode } from '@contrast/common';
|
|
20
20
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
21
21
|
import { Config } from '@contrast/config';
|
|
22
22
|
import * as http from 'node:http';
|
|
@@ -58,7 +58,10 @@ export interface ProtectRequestStore {
|
|
|
58
58
|
};
|
|
59
59
|
exclusions: any[]; // TODO
|
|
60
60
|
virtualPatches: any[]; // TODO
|
|
61
|
-
|
|
61
|
+
trackRequest: boolean;
|
|
62
|
+
securityException?: [mode: ProtectRuleMode, ruleId: string];
|
|
63
|
+
bodyType?: 'json' | 'urlencoded';
|
|
64
|
+
resultsMap: Partial<ResultMap>
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export interface ConnectInputs {
|
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const agentLib = require('@contrast/agent-lib');
|
|
19
|
-
const {
|
|
19
|
+
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
module.exports = function(core) {
|
|
22
22
|
const protect = core.protect = {
|
|
@@ -35,7 +35,7 @@ module.exports = function(core) {
|
|
|
35
35
|
require('./error-handlers')(core);
|
|
36
36
|
|
|
37
37
|
protect.install = function() {
|
|
38
|
-
|
|
38
|
+
callChildComponentMethodsSync(protect, 'install');
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
return protect;
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
19
|
BLOCKING_MODES,
|
|
20
|
-
|
|
20
|
+
traverseKeysAndValues,
|
|
21
|
+
traverseValues,
|
|
21
22
|
Rule,
|
|
22
23
|
isString,
|
|
23
24
|
ProtectRuleMode: { OFF },
|
|
@@ -199,7 +200,7 @@ module.exports = function(core) {
|
|
|
199
200
|
const resultsList = [];
|
|
200
201
|
const { UrlParameter } = agentLib.InputType;
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
traverseValues(urlParams, function(path, type, value) {
|
|
203
204
|
// url param names are not checked.
|
|
204
205
|
if (type !== 'Value') {
|
|
205
206
|
return;
|
|
@@ -306,7 +307,7 @@ module.exports = function(core) {
|
|
|
306
307
|
|
|
307
308
|
const block = commonObjectAnalyzer(sourceContext, parsedBody, inputTypes);
|
|
308
309
|
|
|
309
|
-
sourceContext.
|
|
310
|
+
sourceContext.bodyType = bodyType;
|
|
310
311
|
|
|
311
312
|
if (block) {
|
|
312
313
|
core.protect.throwSecurityException(sourceContext);
|
|
@@ -373,14 +374,14 @@ module.exports = function(core) {
|
|
|
373
374
|
const { name, uuid } = vpEvaluators.get('metadata');
|
|
374
375
|
|
|
375
376
|
if (vpEvaluators.size === 1 && uuid) {
|
|
376
|
-
if (!sourceContext.
|
|
377
|
-
sourceContext.
|
|
377
|
+
if (!sourceContext.resultsMap[ruleId]) {
|
|
378
|
+
sourceContext.resultsMap[ruleId] = [];
|
|
378
379
|
}
|
|
379
|
-
sourceContext.
|
|
380
|
+
sourceContext.resultsMap[ruleId].push({
|
|
380
381
|
name,
|
|
381
382
|
uuid
|
|
382
383
|
});
|
|
383
|
-
sourceContext.
|
|
384
|
+
sourceContext.securityException = ['block', ruleId];
|
|
384
385
|
core.protect.throwSecurityException(sourceContext);
|
|
385
386
|
}
|
|
386
387
|
}
|
|
@@ -412,11 +413,11 @@ module.exports = function(core) {
|
|
|
412
413
|
|
|
413
414
|
if (match) {
|
|
414
415
|
logger.info(match, 'Found a matching IP to an entry in ipDeny list');
|
|
415
|
-
if (!sourceContext.
|
|
416
|
-
sourceContext.
|
|
416
|
+
if (!sourceContext.resultsMap[ruleId]) {
|
|
417
|
+
sourceContext.resultsMap[ruleId] = [];
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
sourceContext.
|
|
420
|
+
sourceContext.resultsMap[ruleId].push({
|
|
420
421
|
ip: match.matchedIp,
|
|
421
422
|
uuid: match.uuid,
|
|
422
423
|
});
|
|
@@ -434,7 +435,7 @@ module.exports = function(core) {
|
|
|
434
435
|
inputAnalysis.handleRequestEnd = function handleRequestEnd(sourceContext) {
|
|
435
436
|
if (!config.protect.probe_analysis.enable || sourceContext.allowed) return;
|
|
436
437
|
|
|
437
|
-
const { resultsMap } = sourceContext
|
|
438
|
+
const { resultsMap } = sourceContext;
|
|
438
439
|
const probesRules = [Rule.CMD_INJECTION, Rule.PATH_TRAVERSAL, Rule.SQL_INJECTION, Rule.XXE];
|
|
439
440
|
const props = {};
|
|
440
441
|
|
|
@@ -444,11 +445,11 @@ module.exports = function(core) {
|
|
|
444
445
|
const {
|
|
445
446
|
ruleId,
|
|
446
447
|
blocked,
|
|
447
|
-
|
|
448
|
+
exploitMetadata,
|
|
448
449
|
value,
|
|
449
450
|
inputType
|
|
450
451
|
} = resultByRuleId;
|
|
451
|
-
if (blocked || !blocked &&
|
|
452
|
+
if (blocked || !blocked && exploitMetadata.length > 0 || !probesRules.some(rule => rule === ruleId)) return;
|
|
452
453
|
|
|
453
454
|
const { policy: { rulesMask } } = sourceContext;
|
|
454
455
|
|
|
@@ -514,19 +515,19 @@ module.exports = function(core) {
|
|
|
514
515
|
|
|
515
516
|
// it's possible to optimize this if qs (or a similar package) is not loaded
|
|
516
517
|
// or if none of the values of queryParams are objects. a quick '.includes()'
|
|
517
|
-
// could be used to determine that. if none are objects then
|
|
518
|
+
// could be used to determine that. if none are objects then traverseKeysAndValues()
|
|
518
519
|
// wouldn't be used, just a simple "for (const key in queryParams) {...}" to
|
|
519
520
|
// check each key and value associated with the key.
|
|
520
521
|
//
|
|
521
522
|
// otoh, it's a fair amount of additional logic and the gain is likely to be
|
|
522
523
|
// small, so it probably only makes sense to check if qs (or similar) is actually
|
|
523
|
-
// in use. a benchmark of "for (const key in queryParams) {...}" vs
|
|
524
|
+
// in use. a benchmark of "for (const key in queryParams) {...}" vs traverseKeysAndValues
|
|
524
525
|
// should be created to see if, and in what cases, it makes sense.
|
|
525
526
|
//
|
|
526
527
|
// another day.
|
|
527
528
|
|
|
528
529
|
/* eslint-disable-next-line complexity */
|
|
529
|
-
|
|
530
|
+
traverseKeysAndValues(object, function(path, type, value) {
|
|
530
531
|
let itemType;
|
|
531
532
|
let isMongoQueryType;
|
|
532
533
|
// this is a bit awkward now because nosql-injection-mongo is not integrated
|
|
@@ -708,10 +709,10 @@ function isResultExcluded(sourceContext, result) {
|
|
|
708
709
|
* @returns {undefined|[String]} undefined to permit else [mode, rule] to block.
|
|
709
710
|
*/
|
|
710
711
|
function mergeFindings(sourceContext, newFindings) {
|
|
711
|
-
const {
|
|
712
|
+
const { policy, securityException, resultsMap } = sourceContext;
|
|
712
713
|
|
|
713
714
|
if (!newFindings.trackRequest) {
|
|
714
|
-
return
|
|
715
|
+
return securityException;
|
|
715
716
|
}
|
|
716
717
|
|
|
717
718
|
newFindings.resultsList = newFindings.resultsList.filter(
|
|
@@ -720,18 +721,18 @@ function mergeFindings(sourceContext, newFindings) {
|
|
|
720
721
|
|
|
721
722
|
normalizeFindings(policy, newFindings);
|
|
722
723
|
|
|
723
|
-
|
|
724
|
-
|
|
724
|
+
sourceContext.trackRequest = sourceContext.trackRequest || newFindings.trackRequest;
|
|
725
|
+
sourceContext.securityException = sourceContext.securityException || newFindings.securityException;
|
|
725
726
|
|
|
726
727
|
// merge them into a ruleId-indexed map (pojo)
|
|
727
728
|
for (const result of newFindings.resultsList) {
|
|
728
|
-
if (!
|
|
729
|
-
|
|
729
|
+
if (!resultsMap[result.ruleId]) {
|
|
730
|
+
resultsMap[result.ruleId] = [];
|
|
730
731
|
}
|
|
731
|
-
|
|
732
|
+
resultsMap[result.ruleId].push(result);
|
|
732
733
|
}
|
|
733
734
|
|
|
734
|
-
return
|
|
735
|
+
return sourceContext.securityException;
|
|
735
736
|
}
|
|
736
737
|
|
|
737
738
|
//
|
|
@@ -749,7 +750,7 @@ function normalizeFindings(policy, findings) {
|
|
|
749
750
|
r.blocked = false;
|
|
750
751
|
|
|
751
752
|
// sink analysis will add findings here
|
|
752
|
-
r.
|
|
753
|
+
r.exploitMetadata = [];
|
|
753
754
|
|
|
754
755
|
// apply exclusions here.
|
|
755
756
|
//
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
module.exports = function(core) {
|
|
21
21
|
const inputAnalysis = core.protect.inputAnalysis = {};
|
|
@@ -47,7 +47,7 @@ module.exports = function(core) {
|
|
|
47
47
|
require('./ip-analysis')(core);
|
|
48
48
|
|
|
49
49
|
inputAnalysis.install = function() {
|
|
50
|
-
|
|
50
|
+
callChildComponentMethodsSync(inputAnalysis, 'install');
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
return inputAnalysis;
|
|
@@ -214,12 +214,6 @@ class HttpInstrumentation {
|
|
|
214
214
|
method: reqData.method,
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
-
// only add queries if it's known that 'qs' or equivalent won't be used.
|
|
218
|
-
/* c8 ignore next 3 */
|
|
219
|
-
if (reqData.standardUrlParsing) {
|
|
220
|
-
connectInputs.queries = reqData.queries;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
217
|
if (inputAnalysis.virtualPatchesEvaluators?.length) {
|
|
224
218
|
store.protect.virtualPatchesEvaluators.push(...inputAnalysis.virtualPatchesEvaluators.map((e) => new Map(e)));
|
|
225
219
|
}
|
|
@@ -20,21 +20,31 @@ const {
|
|
|
20
20
|
ProtectRuleMode: { OFF },
|
|
21
21
|
BLOCKING_MODES,
|
|
22
22
|
isString,
|
|
23
|
-
|
|
23
|
+
traverseKeys,
|
|
24
|
+
traverseKeysAndValues,
|
|
24
25
|
} = require('@contrast/common');
|
|
25
26
|
|
|
26
27
|
module.exports = function(core) {
|
|
27
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
protect: {
|
|
30
|
+
agentLib,
|
|
31
|
+
inputTracing,
|
|
32
|
+
throwSecurityException
|
|
33
|
+
},
|
|
34
|
+
captureStacktrace,
|
|
35
|
+
} = core;
|
|
28
36
|
|
|
29
37
|
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
30
|
-
|
|
38
|
+
const { stacktraceData } = sinkContext;
|
|
39
|
+
captureStacktrace(sinkContext, stacktraceData);
|
|
40
|
+
result.exploitMetadata.push({ sinkContext, findings });
|
|
31
41
|
|
|
32
42
|
const mode = sourceContext.policy[ruleId];
|
|
33
43
|
|
|
34
44
|
if (BLOCKING_MODES.includes(mode)) {
|
|
35
45
|
result.blocked = true;
|
|
36
46
|
const blockInfo = [mode, ruleId];
|
|
37
|
-
sourceContext.
|
|
47
|
+
sourceContext.securityException = blockInfo;
|
|
38
48
|
throwSecurityException(sourceContext);
|
|
39
49
|
}
|
|
40
50
|
}
|
|
@@ -137,10 +147,13 @@ module.exports = function(core) {
|
|
|
137
147
|
|
|
138
148
|
for (const result of stringInjectionResults) {
|
|
139
149
|
if (typeof sinkContext.value === 'object') {
|
|
140
|
-
|
|
150
|
+
traverseKeysAndValues(sinkContext.value, function(path, type, value) {
|
|
141
151
|
if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
|
|
142
152
|
|
|
143
153
|
stringFindings = handleStringValue(result, sinkContext.value[value], agentLib);
|
|
154
|
+
|
|
155
|
+
// halt traversal
|
|
156
|
+
return true;
|
|
144
157
|
});
|
|
145
158
|
} else if (typeof sinkContext.value === 'string') {
|
|
146
159
|
stringFindings = handleStringValue(result, sinkContext.value, agentLib);
|
|
@@ -149,11 +162,11 @@ module.exports = function(core) {
|
|
|
149
162
|
if (stringFindings) {
|
|
150
163
|
const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
|
|
151
164
|
|
|
152
|
-
const nosqlInjectionResults = sourceContext.
|
|
165
|
+
const nosqlInjectionResults = sourceContext.resultsMap[ruleId];
|
|
153
166
|
if (Array.isArray(nosqlInjectionResults)) {
|
|
154
167
|
nosqlInjectionResults.push(nosqlInjectionResult);
|
|
155
168
|
} else {
|
|
156
|
-
sourceContext.
|
|
169
|
+
sourceContext.resultsMap[ruleId] = [nosqlInjectionResult];
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
handleFindings(sourceContext, sinkContext, ruleId, nosqlInjectionResult, stringFindings);
|
|
@@ -222,7 +235,7 @@ module.exports = function(core) {
|
|
|
222
235
|
const findings = idx !== -1 ? { value: sinkContext.value } : null;
|
|
223
236
|
|
|
224
237
|
if (findings) {
|
|
225
|
-
result.
|
|
238
|
+
result.exploitMetadata.push({ sinkContext, findings });
|
|
226
239
|
}
|
|
227
240
|
}
|
|
228
241
|
};
|
|
@@ -241,18 +254,13 @@ function getResultsByRuleId(ruleId, context) {
|
|
|
241
254
|
if (context.policy[ruleId] === OFF) {
|
|
242
255
|
return;
|
|
243
256
|
}
|
|
244
|
-
return context.
|
|
257
|
+
return context.resultsMap[ruleId];
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
function handleObjectValue(result, object) {
|
|
248
|
-
if (typeof object !== 'object') {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
261
|
let findings = null;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
262
|
+
|
|
263
|
+
traverseKeys(object, function(path, type, value) {
|
|
256
264
|
// the result value is the key that was found
|
|
257
265
|
if (result.key === value) {
|
|
258
266
|
// does the object at this path equal the user input?
|
|
@@ -267,6 +275,9 @@ function handleObjectValue(result, object) {
|
|
|
267
275
|
const end = start + value.length;
|
|
268
276
|
const inputBoundaryIndex = 0;
|
|
269
277
|
findings = { start, end, boundaryOverrunIndex: start, inputBoundaryIndex };
|
|
278
|
+
|
|
279
|
+
// halt traversal
|
|
280
|
+
return true;
|
|
270
281
|
}
|
|
271
282
|
}
|
|
272
283
|
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
module.exports = function(core) {
|
|
21
21
|
const inputTracing = core.protect.inputTracing = {};
|
|
@@ -38,7 +38,7 @@ module.exports = function(core) {
|
|
|
38
38
|
// TODO: NODE-2360 (oracledb)
|
|
39
39
|
|
|
40
40
|
inputTracing.install = function() {
|
|
41
|
-
|
|
41
|
+
callChildComponentMethodsSync(inputTracing, 'install');
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
return inputTracing;
|
|
@@ -23,11 +23,31 @@ module.exports = function(core) {
|
|
|
23
23
|
scopes: { instrumentation },
|
|
24
24
|
patcher,
|
|
25
25
|
depHooks,
|
|
26
|
-
|
|
27
|
-
protect,
|
|
28
|
-
protect: { inputTracing }
|
|
26
|
+
protect: { getSourceContext, inputTracing }
|
|
29
27
|
} = core;
|
|
30
28
|
|
|
29
|
+
function pre({ args, name, hooked, orig }) {
|
|
30
|
+
if (instrumentation.isLocked()) return;
|
|
31
|
+
|
|
32
|
+
const sourceContext = getSourceContext('child_process');
|
|
33
|
+
const value = args[0];
|
|
34
|
+
|
|
35
|
+
if (!sourceContext || !value || !isString(value)) return;
|
|
36
|
+
|
|
37
|
+
const sinkContext = {
|
|
38
|
+
name,
|
|
39
|
+
value,
|
|
40
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
inputTracing.handleCommandInjection(sourceContext, sinkContext);
|
|
44
|
+
core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors(sourceContext, sinkContext);
|
|
45
|
+
core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands(sourceContext, sinkContext);
|
|
46
|
+
core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous(sourceContext, sinkContext);
|
|
47
|
+
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
31
51
|
function install() {
|
|
32
52
|
depHooks.resolve({ name: 'child_process' }, cp => {
|
|
33
53
|
['exec', 'execSync'].forEach((method) => {
|
|
@@ -35,27 +55,7 @@ module.exports = function(core) {
|
|
|
35
55
|
patcher.patch(cp, method, {
|
|
36
56
|
name,
|
|
37
57
|
patchType,
|
|
38
|
-
pre
|
|
39
|
-
if (instrumentation.isLocked()) return;
|
|
40
|
-
|
|
41
|
-
const sourceContext = protect.getSourceContext('child_process');
|
|
42
|
-
const value = args[0];
|
|
43
|
-
|
|
44
|
-
if (!sourceContext || !value || !isString(value)) return;
|
|
45
|
-
|
|
46
|
-
const sinkContext = captureStacktrace(
|
|
47
|
-
{ name, value },
|
|
48
|
-
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
inputTracing.handleCommandInjection(sourceContext, sinkContext);
|
|
52
|
-
// To evade code duplication we are using these INPUT TRACING instrumentation
|
|
53
|
-
// to do the checks for SEMANTIC ANALYSIS too
|
|
54
|
-
core.protect.semanticAnalysis.handleCommandInjectionCommandBackdoors(sourceContext, sinkContext);
|
|
55
|
-
core.protect.semanticAnalysis.handleCmdInjectionSemanticChainedCommands(sourceContext, sinkContext);
|
|
56
|
-
core.protect.semanticAnalysis.handleCmdInjectionSemanticDangerous(sourceContext, sinkContext);
|
|
57
|
-
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
|
|
58
|
-
}
|
|
58
|
+
pre
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
});
|
|
@@ -23,7 +23,6 @@ module.exports = function(core) {
|
|
|
23
23
|
logger,
|
|
24
24
|
scopes: { instrumentation },
|
|
25
25
|
patcher,
|
|
26
|
-
captureStacktrace,
|
|
27
26
|
protect,
|
|
28
27
|
protect: { inputTracing }
|
|
29
28
|
} = core;
|
|
@@ -45,10 +44,11 @@ module.exports = function(core) {
|
|
|
45
44
|
|
|
46
45
|
if (!sourceContext || !value || !isString(value)) return;
|
|
47
46
|
|
|
48
|
-
const sinkContext =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
const sinkContext = {
|
|
48
|
+
name: 'eval',
|
|
49
|
+
value,
|
|
50
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
51
|
+
};
|
|
52
52
|
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
53
53
|
}
|
|
54
54
|
});
|
|
@@ -64,7 +64,6 @@ module.exports = function(core) {
|
|
|
64
64
|
scopes: { instrumentation },
|
|
65
65
|
patcher,
|
|
66
66
|
depHooks,
|
|
67
|
-
captureStacktrace,
|
|
68
67
|
protect,
|
|
69
68
|
protect: { inputTracing }
|
|
70
69
|
} = core;
|
|
@@ -101,10 +100,11 @@ module.exports = function(core) {
|
|
|
101
100
|
// don't need to necessarily need to lock it here - there are no
|
|
102
101
|
// lower-level calls that we instrument
|
|
103
102
|
for (const value of values) {
|
|
104
|
-
const sinkContext =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
const sinkContext = {
|
|
104
|
+
name,
|
|
105
|
+
value,
|
|
106
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
107
|
+
};
|
|
108
108
|
inputTracing.handlePathTraversal(sourceContext, sinkContext);
|
|
109
109
|
core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
|
|
110
110
|
}
|
|
@@ -23,7 +23,6 @@ module.exports = function(core) {
|
|
|
23
23
|
logger,
|
|
24
24
|
scopes: { instrumentation },
|
|
25
25
|
patcher,
|
|
26
|
-
captureStacktrace,
|
|
27
26
|
protect,
|
|
28
27
|
protect: { inputTracing }
|
|
29
28
|
} = core;
|
|
@@ -46,10 +45,12 @@ module.exports = function(core) {
|
|
|
46
45
|
|
|
47
46
|
if (!sourceContext || !fnBody || !isString(fnBody)) return;
|
|
48
47
|
|
|
49
|
-
const sinkContext =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
const sinkContext = {
|
|
49
|
+
name: 'Function',
|
|
50
|
+
value: fnBody,
|
|
51
|
+
stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
|
|
52
|
+
};
|
|
53
|
+
|
|
53
54
|
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
54
55
|
}
|
|
55
56
|
})
|
|
@@ -22,7 +22,6 @@ module.exports = function(core) {
|
|
|
22
22
|
scopes: { instrumentation },
|
|
23
23
|
patcher,
|
|
24
24
|
depHooks,
|
|
25
|
-
captureStacktrace,
|
|
26
25
|
protect,
|
|
27
26
|
protect: { inputTracing }
|
|
28
27
|
} = core;
|
|
@@ -44,10 +43,11 @@ module.exports = function(core) {
|
|
|
44
43
|
const value = data.args[0]?.toString();
|
|
45
44
|
if (!value) return;
|
|
46
45
|
|
|
47
|
-
const sinkContext =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const sinkContext = {
|
|
47
|
+
name,
|
|
48
|
+
value,
|
|
49
|
+
stacktraceData: { constructorOpt: data.hooked },
|
|
50
|
+
};
|
|
51
51
|
inputTracing.handleReflectedXss(sourceContext, sinkContext);
|
|
52
52
|
}
|
|
53
53
|
});
|