@contrast/protect 1.2.1 → 1.4.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/constants.js +15 -0
- package/lib/error-handlers/index.js +17 -0
- package/lib/error-handlers/install/express4.js +89 -0
- package/lib/error-handlers/install/fastify3.js +17 -4
- package/lib/error-handlers/install/koa2.js +16 -2
- package/lib/esm-loader.mjs +15 -0
- package/lib/get-source-context.js +33 -0
- 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 +127 -19
- package/lib/index.js +19 -0
- package/lib/input-analysis/constants.js +20 -0
- package/lib/input-analysis/handlers.js +201 -16
- package/lib/input-analysis/index.js +40 -3
- package/lib/input-analysis/install/body-parser1.js +122 -0
- package/lib/input-analysis/install/cookie-parser1.js +80 -0
- package/lib/input-analysis/install/express4.js +103 -0
- package/lib/input-analysis/install/fastify3.js +51 -24
- package/lib/input-analysis/install/formidable1.js +72 -0
- package/lib/input-analysis/install/http.js +30 -4
- package/lib/input-analysis/install/koa-body5.js +63 -0
- package/lib/input-analysis/install/koa-bodyparser4.js +64 -0
- package/lib/input-analysis/install/koa2.js +38 -48
- package/lib/input-analysis/install/multer1.js +88 -0
- package/lib/input-analysis/install/qs6.js +57 -0
- package/lib/input-analysis/install/universal-cookie4.js +52 -0
- package/lib/input-analysis/ip-analysis.js +76 -0
- package/lib/input-analysis/virtual-patches.js +109 -0
- package/lib/input-tracing/constants.js +15 -0
- package/lib/input-tracing/handlers/index.js +225 -66
- package/lib/input-tracing/index.js +25 -2
- package/lib/input-tracing/install/child-process.js +28 -7
- package/lib/input-tracing/install/eval.js +60 -0
- package/lib/input-tracing/install/fs.js +21 -4
- package/lib/input-tracing/install/http.js +63 -0
- package/lib/input-tracing/install/mongodb.js +233 -0
- package/lib/input-tracing/install/mysql.js +21 -4
- package/lib/input-tracing/install/postgres.js +20 -4
- package/lib/input-tracing/install/sequelize.js +22 -5
- package/lib/input-tracing/install/sqlite3.js +21 -4
- package/lib/input-tracing/install/vm.js +132 -0
- package/lib/make-response-blocker.js +15 -0
- package/lib/make-source-context.js +22 -1
- package/lib/security-exception.js +15 -0
- package/lib/semantic-analysis/handlers.js +160 -0
- package/lib/semantic-analysis/index.js +38 -0
- package/lib/throw-security-exception.js +17 -6
- package/package.json +10 -12
- package/lib/cli-rewriter.js +0 -20
- package/lib/input-analysis/install/co-body.js +0 -51
- package/lib/input-analysis/install/cookie-parser.js +0 -48
- package/lib/input-analysis/install/formidable.js +0 -53
- package/lib/input-analysis/install/multer.js +0 -52
- package/lib/input-analysis/install/qs.js +0 -40
- package/lib/input-analysis/install/universal-cookie.js +0 -34
- package/lib/input-tracing/handlers/nosql-injection-mongo.js +0 -48
- package/lib/utils.js +0 -88
|
@@ -1,95 +1,103 @@
|
|
|
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
|
+
|
|
1
16
|
'use strict';
|
|
2
17
|
|
|
18
|
+
const util = require('util');
|
|
19
|
+
const { BLOCKING_MODES, isString, simpleTraverse } = require('@contrast/common');
|
|
20
|
+
|
|
3
21
|
module.exports = function(core) {
|
|
4
22
|
const { protect: { agentLib, inputTracing, throwSecurityException } } = core;
|
|
5
23
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @param {string} ruleId rule id
|
|
9
|
-
* @param {object} context async storage data for protect
|
|
10
|
-
* @returns {AnalysisResult[]}
|
|
11
|
-
*/
|
|
12
|
-
inputTracing.getResultsByRuleId = function(ruleId, context) {
|
|
13
|
-
return context.findings.resultsMap[ruleId];
|
|
14
|
-
};
|
|
24
|
+
function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
|
|
25
|
+
result.details.push({ sinkContext, findings });
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
* Given a ruleId and an analysis function, wrap the analysis function
|
|
18
|
-
* with common setup and post-processing code.
|
|
19
|
-
*/
|
|
20
|
-
inputTracing.handlerFactory = function(ruleId, analysisFn) {
|
|
21
|
-
/**
|
|
22
|
-
* This is the common API for INPUT TRACING instrumentation.
|
|
23
|
-
* @param {object} sourceContext
|
|
24
|
-
* @param {object} sinkContext
|
|
25
|
-
*/
|
|
26
|
-
return function(sourceContext, sinkContext) {
|
|
27
|
-
if (sourceContext.rules.agentLibRules[ruleId].mode === 'off') {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
27
|
+
const { mode } = sourceContext.rules.agentLibRules[ruleId];
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (BLOCKING_MODES.includes(mode)) {
|
|
30
|
+
result.blocked = true;
|
|
31
|
+
const blockInfo = [mode, ruleId];
|
|
32
|
+
sourceContext.findings.securityException = blockInfo;
|
|
33
|
+
throwSecurityException(sourceContext);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
inputTracing.handlePathTraversal = function(sourceContext, sinkContext) {
|
|
38
|
+
const ruleId = 'path-traversal';
|
|
39
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
result.details.push({ sinkContext, findings });
|
|
41
|
+
if (!results) return;
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
43
|
+
for (const result of results) {
|
|
44
|
+
const idx = sinkContext.value.indexOf(result.value);
|
|
45
|
+
const findings = idx !== -1 ? { path: sinkContext.value } : null;
|
|
46
|
+
|
|
47
|
+
if (findings) {
|
|
48
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
47
49
|
}
|
|
48
|
-
}
|
|
50
|
+
}
|
|
49
51
|
};
|
|
50
52
|
|
|
51
|
-
inputTracing.
|
|
52
|
-
'
|
|
53
|
-
|
|
54
|
-
const idx = sinkContext.value.indexOf(result.value);
|
|
55
|
-
return idx !== -1 ? { path: sinkContext.value } : null;
|
|
56
|
-
}
|
|
57
|
-
);
|
|
53
|
+
inputTracing.handleCommandInjection = function(sourceContext, sinkContext) {
|
|
54
|
+
const ruleId = 'cmd-injection';
|
|
55
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
if (!results) return;
|
|
58
|
+
|
|
59
|
+
for (const result of results) {
|
|
60
|
+
let findings = null;
|
|
62
61
|
const inputIndex = sinkContext.value.indexOf(result.value);
|
|
63
62
|
if (inputIndex !== -1) {
|
|
64
|
-
|
|
63
|
+
findings = agentLib.checkCommandInjectionSink(
|
|
65
64
|
inputIndex,
|
|
66
65
|
result.value.length,
|
|
67
66
|
sinkContext.value,
|
|
68
67
|
);
|
|
69
68
|
}
|
|
69
|
+
|
|
70
|
+
if (findings) {
|
|
71
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
72
|
+
}
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
};
|
|
72
75
|
|
|
73
|
-
inputTracing.handleSqlInjection =
|
|
74
|
-
'sql-injection'
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
inputTracing.handleSqlInjection = function(sourceContext, sinkContext) {
|
|
77
|
+
const ruleId = 'sql-injection';
|
|
78
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
79
|
+
|
|
80
|
+
if (!results) return;
|
|
81
|
+
|
|
82
|
+
for (const result of results) {
|
|
83
|
+
let findings = null;
|
|
77
84
|
|
|
78
85
|
const inputIndex = sinkContext.value.indexOf(result.value);
|
|
86
|
+
|
|
79
87
|
// if the user input is not in the sink input, there is nothing to do.
|
|
80
88
|
if (inputIndex === -1) {
|
|
81
|
-
|
|
89
|
+
continue;
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
if (inputIndex === 0 && sinkContext.value === result.value) {
|
|
85
|
-
|
|
93
|
+
findings = {
|
|
86
94
|
startIndex: 0,
|
|
87
95
|
endIndex: result.value.length - 1,
|
|
88
96
|
overrunIndex: 0,
|
|
89
97
|
boundaryIndex: 0,
|
|
90
98
|
};
|
|
91
99
|
} else {
|
|
92
|
-
|
|
100
|
+
findings = agentLib.checkSqlInjectionSink(
|
|
93
101
|
inputIndex,
|
|
94
102
|
result.value.length,
|
|
95
103
|
2,
|
|
@@ -97,21 +105,172 @@ module.exports = function(core) {
|
|
|
97
105
|
);
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
|
|
108
|
+
if (findings) {
|
|
109
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
inputTracing.nosqlInjectionMongo = function(sourceContext, sinkContext) {
|
|
115
|
+
const ruleId = 'nosql-injection-mongo';
|
|
116
|
+
const expansionResults = getResultsByRuleId(ruleId, sourceContext);
|
|
117
|
+
const stringResults = getResultsByRuleId('ssjs-injection', sourceContext);
|
|
118
|
+
|
|
119
|
+
if (expansionResults) {
|
|
120
|
+
let expansionFindings = null;
|
|
121
|
+
for (const result of expansionResults) {
|
|
122
|
+
expansionFindings = handleObjectValue(result, sinkContext.value);
|
|
123
|
+
|
|
124
|
+
if (expansionFindings) {
|
|
125
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, expansionFindings);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (stringResults) {
|
|
131
|
+
let stringFindings = null;
|
|
132
|
+
for (const result of stringResults) {
|
|
133
|
+
stringFindings = handleStringValue(result, sinkContext.value);
|
|
134
|
+
|
|
135
|
+
if (stringFindings) {
|
|
136
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, stringFindings);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
inputTracing.ssjsInjection = function(sourceContext, sinkContext) {
|
|
143
|
+
const ruleId = 'ssjs-injection';
|
|
144
|
+
let sinkValuesArr = [];
|
|
145
|
+
|
|
146
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
147
|
+
|
|
148
|
+
if (!results) return;
|
|
149
|
+
|
|
150
|
+
if (isString(sinkContext.value)) {
|
|
151
|
+
sinkValuesArr.push(sinkContext.value);
|
|
152
|
+
} else {
|
|
153
|
+
const strValues = Object.values(sinkContext.value).filter((v) => isString(v) && v.length);
|
|
154
|
+
sinkValuesArr = [...strValues];
|
|
101
155
|
}
|
|
102
|
-
);
|
|
103
156
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
157
|
+
for (const result of results) {
|
|
158
|
+
let findings = null;
|
|
159
|
+
|
|
160
|
+
for (const v of sinkValuesArr) {
|
|
161
|
+
if (findings) break;
|
|
162
|
+
|
|
163
|
+
const inputIndex = v.indexOf(result.value);
|
|
164
|
+
|
|
165
|
+
if (inputIndex === 0 && v === result.value) {
|
|
166
|
+
findings = {
|
|
167
|
+
startIndex: 0,
|
|
168
|
+
endIndex: result?.value.length - 1,
|
|
169
|
+
boundaryIndex: 0,
|
|
170
|
+
codeString: result.value
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (inputIndex > 0) {
|
|
175
|
+
const endIndex = inputIndex + result?.value.length;
|
|
176
|
+
findings = agentLib.checkSsjsInjectionSink(v, inputIndex, endIndex) && {
|
|
177
|
+
startIndex: inputIndex,
|
|
178
|
+
endIndex,
|
|
179
|
+
boundaryIndex: inputIndex,
|
|
180
|
+
codeString: result.value
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (findings) {
|
|
186
|
+
handleFindings(sourceContext, sinkContext, ruleId, result, findings);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
inputTracing.handleReflectedXss = function(sourceContext, sinkContext) {
|
|
192
|
+
const ruleId = 'reflected-xss';
|
|
193
|
+
const results = getResultsByRuleId(ruleId, sourceContext);
|
|
107
194
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
195
|
+
if (!results) return;
|
|
196
|
+
|
|
197
|
+
for (const result of results) {
|
|
198
|
+
const idx = sinkContext.value.indexOf(result.value);
|
|
199
|
+
const findings = idx !== -1 ? { value: sinkContext.value } : null;
|
|
200
|
+
|
|
201
|
+
if (findings) {
|
|
202
|
+
result.details.push({ sinkContext, findings });
|
|
203
|
+
}
|
|
112
204
|
}
|
|
113
|
-
|
|
205
|
+
};
|
|
206
|
+
|
|
114
207
|
|
|
115
208
|
return inputTracing;
|
|
116
209
|
};
|
|
117
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Util for pulling results from context for the particular rule id
|
|
213
|
+
* @param {string} ruleId rule id
|
|
214
|
+
* @param {object} context async storage data for protect
|
|
215
|
+
* @returns {AnalysisResult[]}
|
|
216
|
+
*/
|
|
217
|
+
function getResultsByRuleId(ruleId, context) {
|
|
218
|
+
if (context.rules.agentLibRules[ruleId].mode === 'off') {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
return context.findings.resultsMap[ruleId];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function handleObjectValue(result, object) {
|
|
225
|
+
if (typeof object !== 'object') {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
let findings = null;
|
|
229
|
+
simpleTraverse(object, function(path, type, value) {
|
|
230
|
+
if (type !== 'Key' || findings) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// the result value is the key that was found
|
|
234
|
+
if (result.key === value) {
|
|
235
|
+
// does the object at this path equal the user input?
|
|
236
|
+
let obj = object;
|
|
237
|
+
for (const p of path) {
|
|
238
|
+
obj = obj[p];
|
|
239
|
+
}
|
|
240
|
+
obj = obj[value];
|
|
241
|
+
// does the found object in the query equal the saved object?
|
|
242
|
+
if (util.isDeepStrictEqual(obj, result.mongoContext.inputToCheck)) {
|
|
243
|
+
const start = JSON.stringify(object).indexOf(value);
|
|
244
|
+
const end = start + value.length;
|
|
245
|
+
const inputBoundaryIndex = 0;
|
|
246
|
+
findings = { start, end, boundaryOverrunIndex: start, inputBoundaryIndex };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return findings;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function handleStringValue(result, string) {
|
|
255
|
+
if (typeof string !== 'string') {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
let findings = null;
|
|
259
|
+
|
|
260
|
+
const inputIndex = string.indexOf(result.value);
|
|
261
|
+
// if the user input is not in the sink input, there is nothing to do.
|
|
262
|
+
if (inputIndex === -1) {
|
|
263
|
+
return findings;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (inputIndex === 0 && string === result.value) {
|
|
267
|
+
findings = {
|
|
268
|
+
start: 0,
|
|
269
|
+
end: result.value.length - 1,
|
|
270
|
+
boundaryOverrunIndex: 0,
|
|
271
|
+
inputBoundaryIndex: 0,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return findings;
|
|
276
|
+
}
|
|
@@ -1,3 +1,18 @@
|
|
|
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
|
+
|
|
1
16
|
'use strict';
|
|
2
17
|
|
|
3
18
|
/**
|
|
@@ -15,16 +30,24 @@ module.exports = function(core) {
|
|
|
15
30
|
require('./handlers')(core);
|
|
16
31
|
|
|
17
32
|
// load the instrumentation installers
|
|
18
|
-
require('./install/fs')(core);
|
|
19
33
|
require('./install/child-process')(core);
|
|
34
|
+
require('./install/fs')(core);
|
|
35
|
+
require('./install/mongodb')(core);
|
|
20
36
|
require('./install/mysql')(core);
|
|
21
37
|
require('./install/postgres')(core);
|
|
38
|
+
require('./install/sequelize')(core);
|
|
39
|
+
require('./install/sqlite3')(core);
|
|
40
|
+
require('./install/http')(core);
|
|
22
41
|
|
|
23
42
|
inputTracing.install = function() {
|
|
24
|
-
inputTracing.fsInstrumentation.install();
|
|
25
43
|
inputTracing.cpInstrumentation.install();
|
|
44
|
+
inputTracing.fsInstrumentation.install();
|
|
45
|
+
inputTracing.mongodbInstrumentation.install();
|
|
26
46
|
inputTracing.mysqlInstrumentation.install();
|
|
27
47
|
inputTracing.postgresInstrumentation.install();
|
|
48
|
+
inputTracing.sequelizeInstrumentation.install();
|
|
49
|
+
inputTracing.sqlite3Instrumentation.install();
|
|
50
|
+
inputTracing.httpInstrumentation.install();
|
|
28
51
|
// TODO: NODE-2360 (2260?)
|
|
29
52
|
};
|
|
30
53
|
|
|
@@ -1,3 +1,18 @@
|
|
|
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
|
+
|
|
1
16
|
'use strict';
|
|
2
17
|
|
|
3
18
|
const { isString } = require('@contrast/common');
|
|
@@ -5,10 +20,11 @@ const { patchType } = require('../constants');
|
|
|
5
20
|
|
|
6
21
|
module.exports = function(core) {
|
|
7
22
|
const {
|
|
8
|
-
scopes: {
|
|
23
|
+
scopes: { instrumentation },
|
|
9
24
|
patcher,
|
|
10
25
|
depHooks,
|
|
11
26
|
captureStacktrace,
|
|
27
|
+
protect,
|
|
12
28
|
protect: { inputTracing }
|
|
13
29
|
} = core;
|
|
14
30
|
|
|
@@ -19,20 +35,25 @@ module.exports = function(core) {
|
|
|
19
35
|
patcher.patch(cp, method, {
|
|
20
36
|
name,
|
|
21
37
|
patchType,
|
|
22
|
-
pre(
|
|
38
|
+
pre({ args, hooked, orig }) {
|
|
23
39
|
if (instrumentation.isLocked()) return;
|
|
24
40
|
|
|
25
|
-
const sourceContext =
|
|
26
|
-
|
|
41
|
+
const sourceContext = protect.getSourceContext('child_process');
|
|
42
|
+
const value = args[0];
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
if (!value || !isString(value)) return;
|
|
44
|
+
if (!sourceContext || !value || !isString(value)) return;
|
|
30
45
|
|
|
31
46
|
const sinkContext = captureStacktrace(
|
|
32
47
|
{ name, value },
|
|
33
|
-
{ constructorOpt:
|
|
48
|
+
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
34
49
|
);
|
|
50
|
+
|
|
35
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);
|
|
36
57
|
}
|
|
37
58
|
});
|
|
38
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.__contrastEval) {
|
|
33
|
+
logger.error('Cannot install `eval` instrumentation - Contrast method DNE');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
patcher.patch(global.ContrastMethods, '__contrastEval', {
|
|
38
|
+
name: 'global.ContrastMethods.__contrastEval',
|
|
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
|
+
};
|
|
@@ -1,3 +1,18 @@
|
|
|
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
|
+
|
|
1
16
|
'use strict';
|
|
2
17
|
|
|
3
18
|
const { isString } = require('@contrast/common');
|
|
@@ -46,10 +61,11 @@ const fsMethods = [
|
|
|
46
61
|
|
|
47
62
|
module.exports = function(core) {
|
|
48
63
|
const {
|
|
49
|
-
scopes: {
|
|
64
|
+
scopes: { instrumentation },
|
|
50
65
|
patcher,
|
|
51
66
|
depHooks,
|
|
52
67
|
captureStacktrace,
|
|
68
|
+
protect,
|
|
53
69
|
protect: { inputTracing }
|
|
54
70
|
} = core;
|
|
55
71
|
|
|
@@ -68,12 +84,13 @@ module.exports = function(core) {
|
|
|
68
84
|
patcher.patch(fs, method, {
|
|
69
85
|
name,
|
|
70
86
|
patchType,
|
|
71
|
-
pre({ args, hooked, name }) {
|
|
87
|
+
pre({ args, hooked, name, orig }) {
|
|
72
88
|
// don't proceed if instrumentation is off e.g. within require() call
|
|
73
89
|
if (instrumentation.isLocked()) return;
|
|
74
90
|
|
|
75
91
|
// obtain the Protect sourceContext
|
|
76
|
-
const sourceContext =
|
|
92
|
+
const sourceContext = protect.getSourceContext('fs');
|
|
93
|
+
|
|
77
94
|
if (!sourceContext) return;
|
|
78
95
|
|
|
79
96
|
// build list of values on which to perform INPUT TRACING
|
|
@@ -86,7 +103,7 @@ module.exports = function(core) {
|
|
|
86
103
|
for (const value of values) {
|
|
87
104
|
const sinkContext = captureStacktrace(
|
|
88
105
|
{ name, value },
|
|
89
|
-
{ constructorOpt: hooked }
|
|
106
|
+
{ constructorOpt: hooked, prependFrames: [orig] }
|
|
90
107
|
);
|
|
91
108
|
inputTracing.handlePathTraversal(sourceContext, sinkContext);
|
|
92
109
|
}
|
|
@@ -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
|
+
};
|