@contrast/protect 1.3.0 → 1.5.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/express4.js +2 -3
- package/lib/error-handlers/install/{fastify3.js → fastify.js} +13 -15
- package/lib/error-handlers/install/hapi.js +75 -0
- package/lib/error-handlers/install/koa2.js +1 -2
- package/lib/{cli-rewriter.js → get-source-context.js} +13 -15
- package/lib/hardening/constants.js +20 -0
- package/lib/hardening/handlers.js +65 -0
- package/lib/hardening/index.js +29 -0
- package/lib/hardening/install/node-serialize0.js +59 -0
- package/lib/index.d.ts +3 -21
- package/lib/index.js +6 -46
- package/lib/input-analysis/handlers.js +198 -39
- package/lib/input-analysis/index.js +9 -6
- package/lib/input-analysis/install/body-parser1.js +20 -18
- package/lib/input-analysis/install/cookie-parser1.js +13 -15
- package/lib/input-analysis/install/express4.js +8 -13
- package/lib/input-analysis/install/fastify.js +86 -0
- package/lib/input-analysis/install/formidable1.js +4 -5
- package/lib/input-analysis/install/hapi.js +106 -0
- package/lib/input-analysis/install/http.js +80 -30
- package/lib/input-analysis/install/koa-body5.js +5 -10
- package/lib/input-analysis/install/koa-bodyparser4.js +6 -10
- package/lib/input-analysis/install/koa2.js +13 -24
- package/lib/input-analysis/install/multer1.js +5 -6
- package/lib/input-analysis/install/qs6.js +7 -11
- package/lib/input-analysis/install/universal-cookie4.js +3 -7
- package/lib/input-analysis/ip-analysis.js +76 -0
- package/lib/input-analysis/virtual-patches.js +109 -0
- package/lib/input-tracing/handlers/index.js +92 -23
- package/lib/input-tracing/index.js +14 -18
- package/lib/input-tracing/install/child-process.js +13 -7
- package/lib/input-tracing/install/eval.js +60 -0
- package/lib/input-tracing/install/fs.js +4 -2
- package/lib/input-tracing/install/function.js +60 -0
- package/lib/input-tracing/install/http.js +63 -0
- package/lib/input-tracing/install/mongodb.js +20 -20
- package/lib/input-tracing/install/mysql.js +3 -2
- package/lib/input-tracing/install/postgres.js +5 -4
- package/lib/input-tracing/install/sequelize.js +7 -5
- package/lib/input-tracing/install/sqlite3.js +6 -4
- package/lib/input-tracing/install/vm.js +132 -0
- package/lib/make-source-context.js +8 -49
- package/lib/policy.js +134 -0
- package/lib/semantic-analysis/handlers.js +161 -0
- package/lib/semantic-analysis/index.js +38 -0
- package/package.json +7 -9
- package/lib/input-analysis/install/fastify3.js +0 -107
- package/lib/utils.js +0 -84
|
@@ -0,0 +1,109 @@
|
|
|
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 { Event } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = (core) => {
|
|
21
|
+
const {
|
|
22
|
+
messages,
|
|
23
|
+
protect: { inputAnalysis },
|
|
24
|
+
} = core;
|
|
25
|
+
|
|
26
|
+
const virtualPatchesEvaluators = inputAnalysis.virtualPatchesEvaluators = [];
|
|
27
|
+
|
|
28
|
+
messages.on(Event.SERVER_SETTINGS_UPDATE, (serverUpdate) => {
|
|
29
|
+
const virtualPatches = serverUpdate.settings?.defend.virtualPatches;
|
|
30
|
+
if (virtualPatches) {
|
|
31
|
+
buildVPEvaluators(virtualPatches, virtualPatchesEvaluators);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function buildVPEvaluators(virtualPatches, evaluatorsArray) {
|
|
37
|
+
evaluatorsArray.length = 0;
|
|
38
|
+
for (const { headers, parameters, urls, uuid, name } of virtualPatches) {
|
|
39
|
+
const evaluators = new Map();
|
|
40
|
+
|
|
41
|
+
if (headers?.length) {
|
|
42
|
+
evaluators.set('HEADERS', (reqHeaders) => {
|
|
43
|
+
let result;
|
|
44
|
+
for (const { evaluation, name, value } of headers) {
|
|
45
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
46
|
+
const keyIndex = reqHeaders.indexOf(name.toLowerCase());
|
|
47
|
+
|
|
48
|
+
result = keyIndex !== -1 && evalCheck(reqHeaders[keyIndex + 1], value);
|
|
49
|
+
if (!result) break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (parameters?.length) {
|
|
57
|
+
evaluators.set('PARAMETERS', (reqParameters) => {
|
|
58
|
+
let result;
|
|
59
|
+
for (const { evaluation, name, value } of parameters) {
|
|
60
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
61
|
+
|
|
62
|
+
result = evalCheck(reqParameters[name], value);
|
|
63
|
+
if (!result) break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (urls?.length) {
|
|
71
|
+
evaluators.set('URLS', (reqUrl) => {
|
|
72
|
+
let result;
|
|
73
|
+
for (const { evaluation, value } of urls) {
|
|
74
|
+
const evalCheck = buildEvaluationCheck(evaluation);
|
|
75
|
+
|
|
76
|
+
result = evalCheck(reqUrl, value);
|
|
77
|
+
if (!result) break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (evaluators.size) {
|
|
85
|
+
evaluators.set('metadata', { name, uuid });
|
|
86
|
+
evaluatorsArray.push(evaluators);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildEvaluationCheck(evaluation) {
|
|
92
|
+
switch (evaluation) {
|
|
93
|
+
case 'MATCHES':
|
|
94
|
+
return (reqValue, matchedValue) => new RegExp(matchedValue, 'i').test(reqValue);
|
|
95
|
+
case 'EQUALS':
|
|
96
|
+
return (reqValue, matchedValue) => reqValue.toString() === matchedValue.toString();
|
|
97
|
+
case 'CONTAINS':
|
|
98
|
+
return (reqValue, matchedValue) => reqValue.includes(matchedValue);
|
|
99
|
+
case 'DOESNT_MATCH':
|
|
100
|
+
return (reqValue, matchedValue) => !new RegExp(matchedValue, 'i').test(reqValue);
|
|
101
|
+
// This is a typo but it is how it's passed from ContrastUI
|
|
102
|
+
case 'DOESNT_EQUALS':
|
|
103
|
+
return (reqValue, matchedValue) => reqValue.toString() !== matchedValue.toString();
|
|
104
|
+
case 'DOESNT_CONTAIN':
|
|
105
|
+
return (reqValue, matchedValue) => !reqValue.includes(matchedValue);
|
|
106
|
+
default:
|
|
107
|
+
return () => false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -16,30 +16,22 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const util = require('util');
|
|
19
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
ProtectRuleMode: { OFF },
|
|
21
|
+
BLOCKING_MODES,
|
|
22
|
+
isString,
|
|
23
|
+
simpleTraverse
|
|
24
|
+
} = require('@contrast/common');
|
|
20
25
|
|
|
21
26
|
module.exports = function(core) {
|
|
22
27
|
const { protect: { agentLib, inputTracing, throwSecurityException } } = core;
|
|
23
28
|
|
|
24
|
-
/**
|
|
25
|
-
* Util for pulling results from context for the particular rule id
|
|
26
|
-
* @param {string} ruleId rule id
|
|
27
|
-
* @param {object} context async storage data for protect
|
|
28
|
-
* @returns {AnalysisResult[]}
|
|
29
|
-
*/
|
|
30
|
-
function getResultsByRuleId(ruleId, context) {
|
|
31
|
-
if (context.rules.agentLibRules[ruleId].mode === 'off') {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
return context.findings.resultsMap[ruleId];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
29
|
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
38
30
|
result.details.push({ sinkContext, findings });
|
|
39
31
|
|
|
40
|
-
const
|
|
32
|
+
const mode = sourceContext.policy[ruleId];
|
|
41
33
|
|
|
42
|
-
if (
|
|
34
|
+
if (BLOCKING_MODES.includes(mode)) {
|
|
43
35
|
result.blocked = true;
|
|
44
36
|
const blockInfo = [mode, ruleId];
|
|
45
37
|
sourceContext.findings.securityException = blockInfo;
|
|
@@ -153,13 +145,87 @@ module.exports = function(core) {
|
|
|
153
145
|
};
|
|
154
146
|
|
|
155
147
|
inputTracing.ssjsInjection = function(sourceContext, sinkContext) {
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
const ruleId = 'ssjs-injection';
|
|
149
|
+
let sinkValuesArr = [];
|
|
150
|
+
|
|
151
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
152
|
+
|
|
153
|
+
if (!results) return;
|
|
154
|
+
|
|
155
|
+
if (isString(sinkContext.value)) {
|
|
156
|
+
sinkValuesArr.push(sinkContext.value);
|
|
157
|
+
} else {
|
|
158
|
+
const strValues = Object.values(sinkContext.value).filter((v) => isString(v) && v.length);
|
|
159
|
+
sinkValuesArr = [...strValues];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const result of results) {
|
|
163
|
+
let findings = null;
|
|
164
|
+
|
|
165
|
+
for (const v of sinkValuesArr) {
|
|
166
|
+
if (findings) break;
|
|
167
|
+
|
|
168
|
+
const inputIndex = v.indexOf(result.value);
|
|
169
|
+
|
|
170
|
+
if (inputIndex === 0 && v === result.value) {
|
|
171
|
+
findings = {
|
|
172
|
+
startIndex: 0,
|
|
173
|
+
endIndex: result?.value.length - 1,
|
|
174
|
+
boundaryIndex: 0,
|
|
175
|
+
codeString: result.value
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (inputIndex > 0) {
|
|
180
|
+
const endIndex = inputIndex + result?.value.length;
|
|
181
|
+
findings = agentLib.checkSsjsInjectionSink(v, inputIndex, endIndex) && {
|
|
182
|
+
startIndex: inputIndex,
|
|
183
|
+
endIndex,
|
|
184
|
+
boundaryIndex: inputIndex,
|
|
185
|
+
codeString: result.value
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (findings) {
|
|
191
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
158
194
|
};
|
|
159
195
|
|
|
196
|
+
inputTracing.handleReflectedXss = function(sourceContext, sinkContext) {
|
|
197
|
+
const ruleId = 'reflected-xss';
|
|
198
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
199
|
+
|
|
200
|
+
if (!results) return;
|
|
201
|
+
|
|
202
|
+
for (const result of results) {
|
|
203
|
+
const idx = sinkContext.value.indexOf(result.value);
|
|
204
|
+
const findings = idx !== -1 ? { value: sinkContext.value } : null;
|
|
205
|
+
|
|
206
|
+
if (findings) {
|
|
207
|
+
result.details.push({ sinkContext, findings });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
|
|
160
213
|
return inputTracing;
|
|
161
214
|
};
|
|
162
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Util for pulling results from context for the particular rule id
|
|
218
|
+
* @param {string} ruleId rule id
|
|
219
|
+
* @param {object} context async storage data for protect
|
|
220
|
+
* @returns {AnalysisResult[]}
|
|
221
|
+
*/
|
|
222
|
+
function getResultsByRuleId(ruleId, context) {
|
|
223
|
+
if (context.policy[ruleId] === OFF) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
return context.findings.resultsMap[ruleId];
|
|
227
|
+
}
|
|
228
|
+
|
|
163
229
|
function handleObjectValue(result, object) {
|
|
164
230
|
if (typeof object !== 'object') {
|
|
165
231
|
return null;
|
|
@@ -179,7 +245,10 @@ function handleObjectValue(result, object) {
|
|
|
179
245
|
obj = obj[value];
|
|
180
246
|
// does the found object in the query equal the saved object?
|
|
181
247
|
if (util.isDeepStrictEqual(obj, result.mongoContext.inputToCheck)) {
|
|
182
|
-
|
|
248
|
+
const start = JSON.stringify(object).indexOf(value);
|
|
249
|
+
const end = start + value.length;
|
|
250
|
+
const inputBoundaryIndex = 0;
|
|
251
|
+
findings = { start, end, boundaryOverrunIndex: start, inputBoundaryIndex };
|
|
183
252
|
}
|
|
184
253
|
}
|
|
185
254
|
});
|
|
@@ -201,10 +270,10 @@ function handleStringValue(result, string) {
|
|
|
201
270
|
|
|
202
271
|
if (inputIndex === 0 && string === result.value) {
|
|
203
272
|
findings = {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
273
|
+
start: 0,
|
|
274
|
+
end: result.value.length - 1,
|
|
275
|
+
boundaryOverrunIndex: 0,
|
|
276
|
+
inputBoundaryIndex: 0,
|
|
208
277
|
};
|
|
209
278
|
}
|
|
210
279
|
|
|
@@ -15,34 +15,30 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* The specification can be found here https://protect-spec.prod.dotnet.contsec.com/guide/input-tracing.html.
|
|
21
|
-
*
|
|
22
|
-
* To view other STAGES see https://protect-spec.prod.dotnet.contsec.com/guide/protect-types.html#protection-types
|
|
23
|
-
* @param {object} core composed dependencies
|
|
24
|
-
* @returns {object}
|
|
25
|
-
*/
|
|
18
|
+
const { installChildComponentsSync } = require('@contrast/common');
|
|
19
|
+
|
|
26
20
|
module.exports = function(core) {
|
|
27
21
|
const inputTracing = core.protect.inputTracing = {};
|
|
28
22
|
|
|
29
|
-
//
|
|
23
|
+
// api
|
|
30
24
|
require('./handlers')(core);
|
|
31
25
|
|
|
32
|
-
//
|
|
33
|
-
require('./install/fs')(core);
|
|
26
|
+
// instrumentation
|
|
34
27
|
require('./install/child-process')(core);
|
|
28
|
+
require('./install/fs')(core);
|
|
29
|
+
require('./install/mongodb')(core);
|
|
35
30
|
require('./install/mysql')(core);
|
|
36
31
|
require('./install/postgres')(core);
|
|
37
|
-
require('./install/
|
|
32
|
+
require('./install/sequelize')(core);
|
|
33
|
+
require('./install/sqlite3')(core);
|
|
34
|
+
require('./install/http')(core);
|
|
35
|
+
require('./install/vm')(core);
|
|
36
|
+
require('./install/eval')(core);
|
|
37
|
+
require('./install/function')(core);
|
|
38
|
+
// TODO: NODE-2360 (oracledb)
|
|
38
39
|
|
|
39
40
|
inputTracing.install = function() {
|
|
40
|
-
inputTracing
|
|
41
|
-
inputTracing.cpInstrumentation.install();
|
|
42
|
-
inputTracing.mysqlInstrumentation.install();
|
|
43
|
-
inputTracing.postgresInstrumentation.install();
|
|
44
|
-
inputTracing.mongodbInstrumentation.install();
|
|
45
|
-
// TODO: NODE-2360 (2260?)
|
|
41
|
+
installChildComponentsSync(inputTracing);
|
|
46
42
|
};
|
|
47
43
|
|
|
48
44
|
return inputTracing;
|
|
@@ -20,10 +20,11 @@ const { patchType } = require('../constants');
|
|
|
20
20
|
|
|
21
21
|
module.exports = function(core) {
|
|
22
22
|
const {
|
|
23
|
-
scopes: {
|
|
23
|
+
scopes: { instrumentation },
|
|
24
24
|
patcher,
|
|
25
25
|
depHooks,
|
|
26
26
|
captureStacktrace,
|
|
27
|
+
protect,
|
|
27
28
|
protect: { inputTracing }
|
|
28
29
|
} = core;
|
|
29
30
|
|
|
@@ -34,20 +35,25 @@ module.exports = function(core) {
|
|
|
34
35
|
patcher.patch(cp, method, {
|
|
35
36
|
name,
|
|
36
37
|
patchType,
|
|
37
|
-
pre(
|
|
38
|
+
pre({ args, hooked, orig }) {
|
|
38
39
|
if (instrumentation.isLocked()) return;
|
|
39
40
|
|
|
40
|
-
const sourceContext =
|
|
41
|
-
|
|
41
|
+
const sourceContext = protect.getSourceContext('child_process');
|
|
42
|
+
const value = args[0];
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
if (!value || !isString(value)) return;
|
|
44
|
+
if (!sourceContext || !value || !isString(value)) return;
|
|
45
45
|
|
|
46
46
|
const sinkContext = captureStacktrace(
|
|
47
47
|
{ name, value },
|
|
48
|
-
{ constructorOpt:
|
|
48
|
+
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
49
49
|
);
|
|
50
|
+
|
|
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);
|
|
51
57
|
}
|
|
52
58
|
});
|
|
53
59
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { isString } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../constants');
|
|
20
|
+
|
|
21
|
+
module.exports = function(core) {
|
|
22
|
+
const {
|
|
23
|
+
logger,
|
|
24
|
+
scopes: { instrumentation },
|
|
25
|
+
patcher,
|
|
26
|
+
captureStacktrace,
|
|
27
|
+
protect,
|
|
28
|
+
protect: { inputTracing }
|
|
29
|
+
} = core;
|
|
30
|
+
|
|
31
|
+
function install() {
|
|
32
|
+
if (!global.ContrastMethods.eval) {
|
|
33
|
+
logger.error('Cannot install `eval` instrumentation - Contrast method DNE');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
patcher.patch(global.ContrastMethods, 'eval', {
|
|
38
|
+
name: 'global.ContrastMethods.eval',
|
|
39
|
+
patchType,
|
|
40
|
+
pre: ({ args, hooked, orig }) => {
|
|
41
|
+
if (instrumentation.isLocked()) return;
|
|
42
|
+
|
|
43
|
+
const sourceContext = protect.getSourceContext('eval');
|
|
44
|
+
const value = args[0];
|
|
45
|
+
|
|
46
|
+
if (!sourceContext || !value || !isString(value)) return;
|
|
47
|
+
|
|
48
|
+
const sinkContext = captureStacktrace(
|
|
49
|
+
{ name: 'eval', value },
|
|
50
|
+
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
51
|
+
);
|
|
52
|
+
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const evalInstrumentation = inputTracing.evalInstrumentation = { install };
|
|
58
|
+
|
|
59
|
+
return evalInstrumentation;
|
|
60
|
+
};
|
|
@@ -61,10 +61,11 @@ const fsMethods = [
|
|
|
61
61
|
|
|
62
62
|
module.exports = function(core) {
|
|
63
63
|
const {
|
|
64
|
-
scopes: {
|
|
64
|
+
scopes: { instrumentation },
|
|
65
65
|
patcher,
|
|
66
66
|
depHooks,
|
|
67
67
|
captureStacktrace,
|
|
68
|
+
protect,
|
|
68
69
|
protect: { inputTracing }
|
|
69
70
|
} = core;
|
|
70
71
|
|
|
@@ -88,7 +89,8 @@ module.exports = function(core) {
|
|
|
88
89
|
if (instrumentation.isLocked()) return;
|
|
89
90
|
|
|
90
91
|
// obtain the Protect sourceContext
|
|
91
|
-
const sourceContext =
|
|
92
|
+
const sourceContext = protect.getSourceContext('fs');
|
|
93
|
+
|
|
92
94
|
if (!sourceContext) return;
|
|
93
95
|
|
|
94
96
|
// build list of values on which to perform INPUT TRACING
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { isString } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../constants');
|
|
20
|
+
|
|
21
|
+
module.exports = function(core) {
|
|
22
|
+
const {
|
|
23
|
+
logger,
|
|
24
|
+
scopes: { instrumentation },
|
|
25
|
+
patcher,
|
|
26
|
+
captureStacktrace,
|
|
27
|
+
protect,
|
|
28
|
+
protect: { inputTracing }
|
|
29
|
+
} = core;
|
|
30
|
+
|
|
31
|
+
function install() {
|
|
32
|
+
if (!global.ContrastMethods.Function) {
|
|
33
|
+
logger.error('Cannot install `Function` instrumentation - Contrast method DNE');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
patcher.patch(global.ContrastMethods, 'Function', {
|
|
38
|
+
name: 'global.ContrastMethods.Function',
|
|
39
|
+
patchType,
|
|
40
|
+
pre: ({ args, hooked, orig }) => {
|
|
41
|
+
if (instrumentation.isLocked()) return;
|
|
42
|
+
|
|
43
|
+
const sourceContext = protect.getSourceContext('Function');
|
|
44
|
+
const fnBody = args[args.length - 1];
|
|
45
|
+
|
|
46
|
+
if (!sourceContext || !fnBody || !isString(fnBody)) return;
|
|
47
|
+
|
|
48
|
+
const sinkContext = captureStacktrace(
|
|
49
|
+
{ name: 'Function', value: fnBody },
|
|
50
|
+
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
51
|
+
);
|
|
52
|
+
inputTracing.ssjsInjection(sourceContext, sinkContext);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const functionInstrumentation = inputTracing.functionInstrumentation = { install };
|
|
58
|
+
|
|
59
|
+
return functionInstrumentation;
|
|
60
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
|
|
20
|
+
module.exports = function(core) {
|
|
21
|
+
const {
|
|
22
|
+
scopes: { instrumentation },
|
|
23
|
+
patcher,
|
|
24
|
+
depHooks,
|
|
25
|
+
captureStacktrace,
|
|
26
|
+
protect,
|
|
27
|
+
protect: { inputTracing }
|
|
28
|
+
} = core;
|
|
29
|
+
|
|
30
|
+
function install() {
|
|
31
|
+
depHooks.resolve({ name: 'http' }, http => {
|
|
32
|
+
for (const method of ['write', 'end']) {
|
|
33
|
+
const name = `http.ServerResponse.prototype.${method}`;
|
|
34
|
+
patcher.patch(http.ServerResponse.prototype, method, {
|
|
35
|
+
name,
|
|
36
|
+
patchType,
|
|
37
|
+
pre(data) {
|
|
38
|
+
if (instrumentation.isLocked()) return;
|
|
39
|
+
|
|
40
|
+
const sourceContext = protect.getSourceContext(name);
|
|
41
|
+
|
|
42
|
+
if (!sourceContext) return;
|
|
43
|
+
|
|
44
|
+
const value = data.args[0]?.toString();
|
|
45
|
+
if (!value) return;
|
|
46
|
+
|
|
47
|
+
const sinkContext = captureStacktrace(
|
|
48
|
+
{ name, value },
|
|
49
|
+
{ constructorOpt: data.hooked }
|
|
50
|
+
);
|
|
51
|
+
inputTracing.handleReflectedXss(sourceContext, sinkContext);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const httpInstrumentation = inputTracing.httpInstrumentation = {
|
|
59
|
+
install
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return httpInstrumentation;
|
|
63
|
+
};
|