@contrast/protect 1.66.0 → 1.67.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/hardening/handlers.js +37 -20
- package/lib/index.d.ts +3 -2
- package/lib/index.js +9 -2
- package/lib/input-analysis/handlers.js +74 -38
- package/lib/input-analysis/install/http.js +2 -3
- package/lib/input-tracing/{handlers/index.js → handlers.js} +13 -13
- package/lib/semantic-analysis/handlers.js +14 -12
- package/package.json +11 -11
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
const {
|
|
19
19
|
BLOCKING_MODES,
|
|
20
20
|
isString,
|
|
21
|
+
InputType,
|
|
21
22
|
Rule: { UNTRUSTED_DESERIALIZATION }
|
|
22
23
|
} = require('@contrast/common');
|
|
23
24
|
|
|
@@ -25,6 +26,7 @@ const NODE_SERIALIZE_RCE_TOKEN = '_$$ND_FUNC$$_';
|
|
|
25
26
|
|
|
26
27
|
module.exports = function(core) {
|
|
27
28
|
const {
|
|
29
|
+
protect,
|
|
28
30
|
protect: {
|
|
29
31
|
hardening,
|
|
30
32
|
throwSecurityException,
|
|
@@ -40,32 +42,47 @@ module.exports = function(core) {
|
|
|
40
42
|
return results;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
+
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
46
|
+
const { stacktraceOpts } = sinkContext;
|
|
47
|
+
captureStacktrace(sinkContext, stacktraceOpts);
|
|
48
|
+
|
|
45
49
|
const mode = sourceContext.policy[ruleId];
|
|
46
|
-
|
|
50
|
+
getResults(sourceContext, ruleId).push(result);
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
let blockInfo;
|
|
53
|
+
if (BLOCKING_MODES.includes(mode)) {
|
|
54
|
+
result.blocked = true;
|
|
55
|
+
blockInfo = [mode, ruleId];
|
|
56
|
+
sourceContext.securityException = blockInfo;
|
|
57
|
+
}
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
if (!isString(value) || !value.indexOf(NODE_SERIALIZE_RCE_TOKEN)) return;
|
|
59
|
+
protect.reportFinding({ findings, result, sinkContext });
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
if (blockInfo) throwSecurityException(sourceContext);
|
|
62
|
+
}
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
exploitMetadata: [{ deserializer: name, command: false }],
|
|
61
|
-
sinkContext,
|
|
62
|
-
});
|
|
64
|
+
hardening.handleUntrustedDeserialization = function (sourceContext, sinkContext) {
|
|
65
|
+
const ruleId = UNTRUSTED_DESERIALIZATION;
|
|
66
|
+
const mode = sourceContext.policy[ruleId];
|
|
67
|
+
const { name, value } = sinkContext;
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
if (
|
|
70
|
+
mode === 'off' ||
|
|
71
|
+
name !== 'node-serialize.unserialize' ||
|
|
72
|
+
!isString(value) ||
|
|
73
|
+
!value.indexOf(NODE_SERIALIZE_RCE_TOKEN)
|
|
74
|
+
) return;
|
|
75
|
+
|
|
76
|
+
const result = {
|
|
77
|
+
value: sinkContext.value,
|
|
78
|
+
ruleId: UNTRUSTED_DESERIALIZATION,
|
|
79
|
+
blocked: false,
|
|
80
|
+
exploited: true,
|
|
81
|
+
inputType: InputType.UNKNOWN,
|
|
82
|
+
};
|
|
83
|
+
const findings = { deserializer: name, command: false };
|
|
84
|
+
|
|
85
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
69
86
|
};
|
|
70
87
|
|
|
71
88
|
return hardening;
|
package/lib/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode, Blocker } from '@contrast/common';
|
|
16
|
+
import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode, Blocker, ProtectFindingEventArg } from '@contrast/common';
|
|
17
17
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
18
18
|
import * as http from 'node:http';
|
|
19
19
|
import * as https from 'node:https';
|
|
@@ -60,7 +60,8 @@ export interface Protect {
|
|
|
60
60
|
makeSourceContext: (req: IncomingMessage, res: ServerResponse) => ProtectRequestStore,
|
|
61
61
|
throwSecurityException: (sourceContext: ProtectRequestStore) => void,
|
|
62
62
|
policy: ProtectPolicy,
|
|
63
|
-
getPolicy()
|
|
63
|
+
getPolicy: () => ProtectPolicy, // creates copy for request scope
|
|
64
|
+
reportFindings: (e: ProtectFindingEventArg) => void,
|
|
64
65
|
inputAnalysis: {
|
|
65
66
|
handleConnect: (sourceContext: ProtectRequestStore, connectInputs: ConnectInputs) => undefined | [string, string],
|
|
66
67
|
handleRequestEnd: (sourceContext: ProtectRequestStore) => void, //NYI
|
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { isMainThread } = require('node:worker_threads');
|
|
19
|
-
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
|
+
const { callChildComponentMethodsSync, Event } = require('@contrast/common');
|
|
20
20
|
const { ConfigSource } = require('@contrast/config');
|
|
21
21
|
|
|
22
22
|
module.exports = function(core) {
|
|
@@ -37,7 +37,7 @@ module.exports = function(core) {
|
|
|
37
37
|
require('./hardening')(core);
|
|
38
38
|
require('./semantic-analysis')(core);
|
|
39
39
|
|
|
40
|
-
protect.install = function() {
|
|
40
|
+
protect.install = function install() {
|
|
41
41
|
// only force instrumentation if assess is explicitly enabled in local config
|
|
42
42
|
const forceInstrumentation =
|
|
43
43
|
config.preinstrument &&
|
|
@@ -60,6 +60,13 @@ module.exports = function(core) {
|
|
|
60
60
|
ctx.store.protect = protect.makeSourceContext(ctx);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
protect.reportFinding = function reportFinding(data) {
|
|
64
|
+
core.messages.emit(Event.PROTECT_FINDING, {
|
|
65
|
+
store: core.scopes.sources.getStore(),
|
|
66
|
+
...data,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
63
70
|
return protect;
|
|
64
71
|
};
|
|
65
72
|
|
|
@@ -100,6 +100,7 @@ module.exports = Core.makeComponent({
|
|
|
100
100
|
factory(core) {
|
|
101
101
|
const {
|
|
102
102
|
logger,
|
|
103
|
+
protect,
|
|
103
104
|
protect: {
|
|
104
105
|
agentLib,
|
|
105
106
|
inputAnalysis,
|
|
@@ -183,6 +184,10 @@ module.exports = Core.makeComponent({
|
|
|
183
184
|
block = inputAnalysis.handleMethodTampering(sourceContext, connectInputs);
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
if (block) {
|
|
188
|
+
core.protect.reportFinding(block[2]);
|
|
189
|
+
}
|
|
190
|
+
|
|
186
191
|
return block;
|
|
187
192
|
};
|
|
188
193
|
|
|
@@ -216,6 +221,7 @@ module.exports = Core.makeComponent({
|
|
|
216
221
|
const block = commonObjectAnalyzer(sourceContext, queryParams, parameterInputTypes);
|
|
217
222
|
|
|
218
223
|
if (block) {
|
|
224
|
+
core.protect.reportFinding(block[2]);
|
|
219
225
|
core.protect.throwSecurityException(sourceContext);
|
|
220
226
|
}
|
|
221
227
|
};
|
|
@@ -284,6 +290,9 @@ module.exports = Core.makeComponent({
|
|
|
284
290
|
const block = mergeFindings(sourceContext, urlParamsFindings);
|
|
285
291
|
|
|
286
292
|
if (block) {
|
|
293
|
+
if (block[2]) {
|
|
294
|
+
core.protect.reportFinding(block[2]);
|
|
295
|
+
}
|
|
287
296
|
core.protect.throwSecurityException(sourceContext);
|
|
288
297
|
}
|
|
289
298
|
};
|
|
@@ -312,9 +321,11 @@ module.exports = Core.makeComponent({
|
|
|
312
321
|
|
|
313
322
|
const cookieFindings = agentLib.scoreRequestConnect(rulesMask, { cookies: cookiesArr }, preferWW);
|
|
314
323
|
|
|
324
|
+
|
|
315
325
|
const block = mergeFindings(sourceContext, cookieFindings);
|
|
316
326
|
|
|
317
327
|
if (block) {
|
|
328
|
+
protect.reportFinding(block[2]);
|
|
318
329
|
core.protect.throwSecurityException(sourceContext);
|
|
319
330
|
}
|
|
320
331
|
};
|
|
@@ -356,6 +367,7 @@ module.exports = Core.makeComponent({
|
|
|
356
367
|
sourceContext.bodyType = bodyType;
|
|
357
368
|
|
|
358
369
|
if (block) {
|
|
370
|
+
protect.reportFinding(block[2]);
|
|
359
371
|
core.protect.throwSecurityException(sourceContext);
|
|
360
372
|
}
|
|
361
373
|
};
|
|
@@ -402,6 +414,7 @@ module.exports = Core.makeComponent({
|
|
|
402
414
|
const block = mergeFindings(sourceContext, unsafeFilenameFindings);
|
|
403
415
|
|
|
404
416
|
if (block) {
|
|
417
|
+
core.protect.reportFinding(block[2]);
|
|
405
418
|
core.protect.throwSecurityException(sourceContext);
|
|
406
419
|
}
|
|
407
420
|
};
|
|
@@ -423,10 +436,17 @@ module.exports = Core.makeComponent({
|
|
|
423
436
|
if (!sourceContext.resultsMap[ruleId]) {
|
|
424
437
|
sourceContext.resultsMap[ruleId] = [];
|
|
425
438
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
439
|
+
|
|
440
|
+
const result = {
|
|
441
|
+
key: name,
|
|
442
|
+
inputType: 'UNKNOWN',
|
|
443
|
+
ruleId: Rule.VIRTUAL_PATCH,
|
|
444
|
+
value: 'Virtual Patch',
|
|
445
|
+
blocked: true,
|
|
446
|
+
};
|
|
447
|
+
const eventArg = { result, findings: { uuid } };
|
|
448
|
+
|
|
449
|
+
protect.reportFinding(eventArg);
|
|
430
450
|
sourceContext.securityException = ['block', ruleId];
|
|
431
451
|
core.protect.throwSecurityException(sourceContext);
|
|
432
452
|
}
|
|
@@ -453,7 +473,7 @@ module.exports = Core.makeComponent({
|
|
|
453
473
|
if (!sourceContext || !ipDenylist.length) return;
|
|
454
474
|
|
|
455
475
|
const { sourceInfo } = core.scopes.sources.getStore();
|
|
456
|
-
const match = ipListAnalysis(sourceInfo.
|
|
476
|
+
const match = ipListAnalysis(sourceInfo.ip, sourceInfo.rawHeaders, ipDenylist);
|
|
457
477
|
|
|
458
478
|
if (match) {
|
|
459
479
|
logger.info(match, 'Found a matching IP to an entry in ipDeny list');
|
|
@@ -461,10 +481,21 @@ module.exports = Core.makeComponent({
|
|
|
461
481
|
sourceContext.resultsMap[ruleId] = [];
|
|
462
482
|
}
|
|
463
483
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
484
|
+
const eventArg = {
|
|
485
|
+
result: {
|
|
486
|
+
key: 'IP Address',
|
|
487
|
+
inputType: 'UNKNOWN',
|
|
488
|
+
ruleId: Rule.IP_DENYLIST,
|
|
489
|
+
value: sourceInfo.ip,
|
|
490
|
+
blocked: true,
|
|
491
|
+
},
|
|
492
|
+
findings: {
|
|
493
|
+
uuid: match.uuid,
|
|
494
|
+
ip: match.matchedIp,
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
protect.reportFinding(eventArg);
|
|
498
|
+
|
|
468
499
|
return ['block', 'ip-denylist'];
|
|
469
500
|
}
|
|
470
501
|
};
|
|
@@ -481,14 +512,14 @@ module.exports = Core.makeComponent({
|
|
|
481
512
|
key: 'method',
|
|
482
513
|
value: method,
|
|
483
514
|
blocked: false,
|
|
484
|
-
exploitMetadata: null,
|
|
485
515
|
};
|
|
486
516
|
|
|
487
517
|
sourceContext.resultsMap[ruleId] = [result];
|
|
488
518
|
|
|
489
519
|
if (BLOCKING_MODES.includes(mode)) {
|
|
520
|
+
result.exploited = true;
|
|
490
521
|
result.blocked = true;
|
|
491
|
-
return sourceContext.securityException = ['block', ruleId];
|
|
522
|
+
return sourceContext.securityException = ['block', ruleId, { result }];
|
|
492
523
|
}
|
|
493
524
|
}
|
|
494
525
|
}
|
|
@@ -502,24 +533,24 @@ module.exports = Core.makeComponent({
|
|
|
502
533
|
* @param {Object} sourceContext
|
|
503
534
|
*/
|
|
504
535
|
inputAnalysis.handleRequestEnd = function handleRequestEnd(sourceContext) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
536
|
+
// check status code to verify method-tampering exploitation
|
|
537
|
+
const mtResult = sourceContext.resultsMap[Rule.METHOD_TAMPERING]?.[0];
|
|
538
|
+
if (mtResult) {
|
|
539
|
+
const { statusCode } = sourceContext.resData;
|
|
540
|
+
if (statusCode !== 405 || statusCode !== 501) {
|
|
541
|
+
mtResult.exploited = true;
|
|
542
|
+
protect.reportFindings({ result: mtResult, finding: { statusCode } });
|
|
513
543
|
}
|
|
514
544
|
}
|
|
515
545
|
|
|
516
546
|
if (!config.protect.probe_analysis.enable) return;
|
|
517
547
|
|
|
548
|
+
const probeReports = [];
|
|
549
|
+
|
|
518
550
|
// Detecting probes
|
|
519
551
|
const { resultsMap, policy: { rulesMask } } = sourceContext;
|
|
520
552
|
const probesRules = [Rule.CMD_INJECTION, Rule.PATH_TRAVERSAL, Rule.SQL_INJECTION, Rule.XXE];
|
|
521
553
|
const probes = {};
|
|
522
|
-
|
|
523
554
|
const findingsForScoreRequest = {
|
|
524
555
|
HeaderValue: {},
|
|
525
556
|
ParameterValue: {},
|
|
@@ -532,7 +563,7 @@ module.exports = Core.makeComponent({
|
|
|
532
563
|
resultsByRuleId.forEach(resultByRuleId => {
|
|
533
564
|
const {
|
|
534
565
|
ruleId,
|
|
535
|
-
|
|
566
|
+
exploited,
|
|
536
567
|
score,
|
|
537
568
|
value,
|
|
538
569
|
key,
|
|
@@ -541,9 +572,10 @@ module.exports = Core.makeComponent({
|
|
|
541
572
|
|
|
542
573
|
if (
|
|
543
574
|
!isMonitorMode(ruleId, sourceContext) ||
|
|
544
|
-
|
|
575
|
+
exploited === true || // todo: remove
|
|
545
576
|
score >= 90 ||
|
|
546
|
-
!probesRules.some((rule) => rule === ruleId)
|
|
577
|
+
!probesRules.some((rule) => rule === ruleId) ||
|
|
578
|
+
inputType == InputType.UNKNOWN
|
|
547
579
|
) {
|
|
548
580
|
return;
|
|
549
581
|
}
|
|
@@ -562,9 +594,7 @@ module.exports = Core.makeComponent({
|
|
|
562
594
|
valueToResultByRuleId[value] = resultByRuleId;
|
|
563
595
|
});
|
|
564
596
|
});
|
|
565
|
-
|
|
566
597
|
const { ParameterValue, HeaderValue, CookieValue } = findingsForScoreRequest;
|
|
567
|
-
|
|
568
598
|
const results =
|
|
569
599
|
agentLib.scoreRequestConnect(
|
|
570
600
|
rulesMask,
|
|
@@ -579,16 +609,17 @@ module.exports = Core.makeComponent({
|
|
|
579
609
|
).resultsList || [];
|
|
580
610
|
|
|
581
611
|
Object.entries(findingsForScoreAtom).forEach(([value, inputTypes]) => {
|
|
582
|
-
Object.entries(inputTypes).forEach(([inputType, resultByRuleId]) =>
|
|
583
|
-
(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
612
|
+
Object.entries(inputTypes).forEach(([inputType, resultByRuleId]) => {
|
|
613
|
+
if (agentLib.InputType[inputType] == null) return;
|
|
614
|
+
const alibResult = agentLib.scoreAtom(rulesMask, value, agentLib.InputType[inputType], {
|
|
615
|
+
preferWorthWatching: false,
|
|
616
|
+
}) || [];
|
|
617
|
+
alibResult.forEach(result => {
|
|
588
618
|
results.push({ value, ...result });
|
|
619
|
+
probeReports.push({ value, ...result });
|
|
589
620
|
valueToResultByRuleId[value] = resultByRuleId;
|
|
590
|
-
})
|
|
591
|
-
);
|
|
621
|
+
});
|
|
622
|
+
});
|
|
592
623
|
});
|
|
593
624
|
|
|
594
625
|
results
|
|
@@ -613,7 +644,12 @@ module.exports = Core.makeComponent({
|
|
|
613
644
|
}
|
|
614
645
|
|
|
615
646
|
resultsMap[probe.ruleId].push(probe);
|
|
647
|
+
probeReports.push(probe);
|
|
616
648
|
});
|
|
649
|
+
|
|
650
|
+
for (const result of probeReports) {
|
|
651
|
+
core.protect.reportFinding({ result });
|
|
652
|
+
}
|
|
617
653
|
};
|
|
618
654
|
|
|
619
655
|
/**
|
|
@@ -874,11 +910,11 @@ function normalizeFindings(policy, findings) {
|
|
|
874
910
|
// what additional augmentations are needed?
|
|
875
911
|
// the name/id might need to be mapped but keep the original so it's not lost
|
|
876
912
|
r.mappedId = agentLibRuleTypeToName[r.ruleId] || r.ruleId;
|
|
877
|
-
// this finding resulted in blocking, i.e., it is not a probe.
|
|
878
|
-
r.blocked = false;
|
|
879
913
|
|
|
880
|
-
// sink
|
|
881
|
-
|
|
914
|
+
// if we block this or the value is found in sink, we'll know not to check
|
|
915
|
+
// this result for probe analysis in handleRequestEnd().
|
|
916
|
+
r.blocked = false;
|
|
917
|
+
r.exploited = false;
|
|
882
918
|
|
|
883
919
|
// apply exclusions here.
|
|
884
920
|
//
|
|
@@ -893,7 +929,7 @@ function normalizeFindings(policy, findings) {
|
|
|
893
929
|
const mode = policy[r.ruleId];
|
|
894
930
|
if (r.score >= 90 && BLOCKING_MODES.includes(mode)) {
|
|
895
931
|
r.blocked = true;
|
|
896
|
-
findings.securityException = [mode, r.ruleId];
|
|
932
|
+
findings.securityException = [mode, r.ruleId, { result: r }];
|
|
897
933
|
}
|
|
898
934
|
}
|
|
899
935
|
}
|
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const onFinished = require('on-finished');
|
|
19
|
-
const {
|
|
19
|
+
const { primordials: { StringPrototypeToLowerCase, ArrayPrototypeSlice } } = require('@contrast/common');
|
|
20
20
|
const { patchType } = require('../constants');
|
|
21
21
|
|
|
22
22
|
module.exports = function (core) {
|
|
23
23
|
const {
|
|
24
24
|
logger,
|
|
25
|
-
messages,
|
|
26
25
|
scopes: { sources },
|
|
27
26
|
instrumentation: { instrument },
|
|
28
27
|
protect: {
|
|
@@ -75,8 +74,8 @@ module.exports = function (core) {
|
|
|
75
74
|
|
|
76
75
|
onFinished(res, (/* err, req */) => {
|
|
77
76
|
resData.statusCode = res.statusCode;
|
|
77
|
+
// check for probes and method-tampering outcome
|
|
78
78
|
inputAnalysis.handleRequestEnd(store.protect);
|
|
79
|
-
messages.emit(Event.PROTECT, store);
|
|
80
79
|
});
|
|
81
80
|
|
|
82
81
|
const connectInputs = {
|
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
|
|
30
30
|
module.exports = function(core) {
|
|
31
31
|
const {
|
|
32
|
+
protect,
|
|
32
33
|
protect: {
|
|
33
34
|
agentLib,
|
|
34
35
|
inputTracing,
|
|
@@ -40,16 +41,21 @@ module.exports = function(core) {
|
|
|
40
41
|
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
41
42
|
const { stacktraceOpts } = sinkContext;
|
|
42
43
|
captureStacktrace(sinkContext, stacktraceOpts);
|
|
43
|
-
result.
|
|
44
|
+
result.exploited = true;
|
|
44
45
|
|
|
45
46
|
const mode = sourceContext.policy[ruleId];
|
|
47
|
+
const eventArg = { findings, result, sinkContext };
|
|
46
48
|
|
|
49
|
+
let blockInfo;
|
|
47
50
|
if (BLOCKING_MODES.includes(mode)) {
|
|
48
51
|
result.blocked = true;
|
|
49
|
-
|
|
52
|
+
blockInfo = [mode, ruleId, eventArg];
|
|
50
53
|
sourceContext.securityException = blockInfo;
|
|
51
|
-
throwSecurityException(sourceContext);
|
|
52
54
|
}
|
|
55
|
+
|
|
56
|
+
protect.reportFinding(eventArg);
|
|
57
|
+
|
|
58
|
+
if (blockInfo) throwSecurityException(sourceContext);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
inputTracing.handlePathTraversal = function(sourceContext, sinkContext) {
|
|
@@ -61,7 +67,6 @@ module.exports = function(core) {
|
|
|
61
67
|
for (const result of results) {
|
|
62
68
|
const idx = sinkContext.value.indexOf(result.value);
|
|
63
69
|
const findings = idx !== -1 ? { path: sinkContext.value } : null;
|
|
64
|
-
|
|
65
70
|
if (findings) {
|
|
66
71
|
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
67
72
|
}
|
|
@@ -218,13 +223,7 @@ module.exports = function(core) {
|
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
if (stringFindings) {
|
|
221
|
-
const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
|
|
222
|
-
|
|
223
|
-
// don't modify ssjs-injection result items so use new exploit metadata array here
|
|
224
|
-
if (nosqlInjectionResult.idsList?.some?.((id) => id.startsWith('SSJS'))) {
|
|
225
|
-
nosqlInjectionResult.exploitMetadata = [];
|
|
226
|
-
}
|
|
227
|
-
|
|
226
|
+
const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId, exploited: false };
|
|
228
227
|
const nosqlInjectionResults = sourceContext.resultsMap[ruleId];
|
|
229
228
|
const isAlreadyPresentInNosqlresults = result.idsList &&
|
|
230
229
|
result.idsList.some(
|
|
@@ -312,12 +311,13 @@ module.exports = function(core) {
|
|
|
312
311
|
const findings = idx !== -1 ? { value: sinkContext.value } : null;
|
|
313
312
|
|
|
314
313
|
if (findings) {
|
|
315
|
-
result.
|
|
314
|
+
result.exploited = true;
|
|
315
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
316
|
+
break;
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
};
|
|
319
320
|
|
|
320
|
-
|
|
321
321
|
return inputTracing;
|
|
322
322
|
};
|
|
323
323
|
|
|
@@ -44,6 +44,7 @@ const getRuleResults = function(obj, prop) {
|
|
|
44
44
|
|
|
45
45
|
module.exports = function(core) {
|
|
46
46
|
const {
|
|
47
|
+
protect,
|
|
47
48
|
protect: {
|
|
48
49
|
agentLib,
|
|
49
50
|
semanticAnalysis,
|
|
@@ -52,27 +53,32 @@ module.exports = function(core) {
|
|
|
52
53
|
captureStacktrace,
|
|
53
54
|
} = core;
|
|
54
55
|
|
|
55
|
-
function handleResult(sourceContext, sinkContext, ruleId, mode,
|
|
56
|
+
function handleResult(sourceContext, sinkContext, ruleId, mode, findings) {
|
|
56
57
|
const { value, stacktraceOpts } = sinkContext;
|
|
57
58
|
captureStacktrace(sinkContext, stacktraceOpts);
|
|
58
59
|
|
|
59
60
|
// shoehorn findings into agent-lib result data model
|
|
60
61
|
const result = {
|
|
61
62
|
blocked: false,
|
|
63
|
+
inputType: InputType.UNKNOWN,
|
|
62
64
|
ruleId,
|
|
63
65
|
value,
|
|
64
66
|
mappedId: ruleId,
|
|
65
|
-
|
|
66
|
-
...finding
|
|
67
|
+
exploited: true,
|
|
67
68
|
};
|
|
69
|
+
|
|
68
70
|
getRuleResults(sourceContext.resultsMap, ruleId).push(result);
|
|
69
71
|
|
|
72
|
+
let blockInfo;
|
|
70
73
|
if (BLOCKING_MODES.includes(mode)) {
|
|
71
74
|
result.blocked = true;
|
|
72
|
-
|
|
75
|
+
blockInfo = [mode, ruleId];
|
|
73
76
|
sourceContext.securityException = blockInfo;
|
|
74
|
-
throwSecurityException(sourceContext);
|
|
75
77
|
}
|
|
78
|
+
|
|
79
|
+
protect.reportFinding({ findings, result, sinkContext });
|
|
80
|
+
|
|
81
|
+
if (blockInfo) throwSecurityException(sourceContext);
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
/**
|
|
@@ -180,7 +186,7 @@ module.exports = function(core) {
|
|
|
180
186
|
const finding = findBackdoorInjection(sourceContext, sinkContext.value);
|
|
181
187
|
|
|
182
188
|
if (finding) {
|
|
183
|
-
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode
|
|
189
|
+
handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode);
|
|
184
190
|
}
|
|
185
191
|
};
|
|
186
192
|
|
|
@@ -190,9 +196,7 @@ module.exports = function(core) {
|
|
|
190
196
|
if (mode == OFF) return;
|
|
191
197
|
|
|
192
198
|
if (agentLib.isDangerousPath(sinkContext.value, true)) {
|
|
193
|
-
handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode
|
|
194
|
-
exploitMetadata: [{ sinkContext, path: sinkContext.value }]
|
|
195
|
-
});
|
|
199
|
+
handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode);
|
|
196
200
|
}
|
|
197
201
|
};
|
|
198
202
|
|
|
@@ -202,9 +206,7 @@ module.exports = function(core) {
|
|
|
202
206
|
|
|
203
207
|
const findings = findExternalEntities(sinkContext.value);
|
|
204
208
|
if (findings.entities.length) {
|
|
205
|
-
handleResult(sourceContext, sinkContext, Rule.XXE, mode,
|
|
206
|
-
exploitMetadata: [{ sinkContext, ...findings }],
|
|
207
|
-
});
|
|
209
|
+
handleResult(sourceContext, sinkContext, Rule.XXE, mode, findings);
|
|
208
210
|
}
|
|
209
211
|
};
|
|
210
212
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.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.37.0",
|
|
25
|
+
"@contrast/config": "1.52.0",
|
|
26
|
+
"@contrast/core": "1.57.0",
|
|
27
|
+
"@contrast/dep-hooks": "1.26.0",
|
|
28
|
+
"@contrast/esm-hooks": "2.31.0",
|
|
29
|
+
"@contrast/instrumentation": "1.36.0",
|
|
30
|
+
"@contrast/logger": "1.30.0",
|
|
31
|
+
"@contrast/patcher": "1.29.0",
|
|
32
|
+
"@contrast/rewriter": "1.33.0",
|
|
33
|
+
"@contrast/scopes": "1.27.0",
|
|
34
34
|
"async-hook-domain": "^4.0.1",
|
|
35
35
|
"ipaddr.js": "^2.0.1",
|
|
36
36
|
"on-finished": "^2.4.1",
|