@contrast/assess 1.18.0 → 1.20.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/LICENSE +1 -1
- package/lib/constants.js +26 -0
- package/lib/crypto-analysis/common.js +20 -0
- package/lib/crypto-analysis/index.js +44 -0
- package/lib/crypto-analysis/install/crypto.js +156 -0
- package/lib/crypto-analysis/install/math.js +104 -0
- package/lib/dataflow/index.js +1 -1
- package/lib/dataflow/propagation/common.js +1 -1
- package/lib/dataflow/propagation/index.js +1 -1
- package/lib/dataflow/propagation/install/JSON/index.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +15 -14
- package/lib/dataflow/propagation/install/JSON/stringify.js +2 -2
- package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
- package/lib/dataflow/propagation/install/buffer.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/number.js +4 -3
- package/lib/dataflow/propagation/install/contrast-methods/string.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +1 -1
- package/lib/dataflow/propagation/install/decode-uri-component.js +1 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -3
- package/lib/dataflow/propagation/install/ejs/index.js +2 -1
- package/lib/dataflow/propagation/install/ejs/template.js +79 -0
- package/lib/dataflow/propagation/install/encode-uri.js +1 -1
- package/lib/dataflow/propagation/install/escape-html.js +1 -1
- package/lib/dataflow/propagation/install/escape.js +1 -1
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +1 -1
- package/lib/dataflow/propagation/install/isnumeric-0.js +3 -3
- package/lib/dataflow/propagation/install/joi/any.js +1 -1
- package/lib/dataflow/propagation/install/joi/boolean.js +1 -1
- package/lib/dataflow/propagation/install/joi/expression.js +1 -1
- package/lib/dataflow/propagation/install/joi/index.js +1 -1
- package/lib/dataflow/propagation/install/joi/keys.js +1 -1
- package/lib/dataflow/propagation/install/joi/number.js +1 -1
- package/lib/dataflow/propagation/install/joi/object.js +1 -1
- package/lib/dataflow/propagation/install/joi/string-schema.js +1 -1
- package/lib/dataflow/propagation/install/joi/utils.js +1 -1
- package/lib/dataflow/propagation/install/joi/values.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/common.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/index.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +1 -1
- package/lib/dataflow/propagation/install/mustache-escape.js +1 -1
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +1 -1
- package/lib/dataflow/propagation/install/parse-int.js +3 -3
- package/lib/dataflow/propagation/install/path/basename.js +1 -1
- package/lib/dataflow/propagation/install/path/common.js +1 -1
- package/lib/dataflow/propagation/install/path/dirname.js +1 -1
- package/lib/dataflow/propagation/install/path/extname.js +1 -1
- package/lib/dataflow/propagation/install/path/format.js +1 -1
- package/lib/dataflow/propagation/install/path/index.js +1 -1
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -1
- package/lib/dataflow/propagation/install/path/normalize.js +1 -1
- package/lib/dataflow/propagation/install/path/parse.js +1 -1
- package/lib/dataflow/propagation/install/path/relative.js +1 -1
- package/lib/dataflow/propagation/install/path/toNamespacedPath.js +1 -1
- package/lib/dataflow/propagation/install/pug/index.js +3 -3
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +1 -1
- package/lib/dataflow/propagation/install/querystring/escape.js +1 -1
- package/lib/dataflow/propagation/install/querystring/index.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +1 -1
- package/lib/dataflow/propagation/install/querystring/stringify.js +1 -1
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +1 -1
- package/lib/dataflow/propagation/install/send.js +1 -1
- package/lib/dataflow/propagation/install/sequelize/index.js +1 -1
- package/lib/dataflow/propagation/install/sequelize/query-generator.js +1 -1
- package/lib/dataflow/propagation/install/sequelize/sql-string.js +1 -1
- package/lib/dataflow/propagation/install/sql-template-strings.js +1 -1
- package/lib/dataflow/propagation/install/string/concat.js +1 -1
- package/lib/dataflow/propagation/install/string/format-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/index.js +1 -1
- package/lib/dataflow/propagation/install/string/match-all.js +1 -1
- package/lib/dataflow/propagation/install/string/match.js +1 -1
- package/lib/dataflow/propagation/install/string/replace.js +1 -1
- package/lib/dataflow/propagation/install/string/slice.js +1 -1
- package/lib/dataflow/propagation/install/string/split.js +1 -1
- package/lib/dataflow/propagation/install/string/substring.js +1 -1
- package/lib/dataflow/propagation/install/string/trim.js +1 -1
- package/lib/dataflow/propagation/install/unescape.js +1 -1
- package/lib/dataflow/propagation/install/url/domain-parsers.js +1 -1
- package/lib/dataflow/propagation/install/url/index.js +1 -1
- package/lib/dataflow/propagation/install/url/parse.js +1 -1
- package/lib/dataflow/propagation/install/url/searchParams.js +1 -1
- package/lib/dataflow/propagation/install/url/url.js +1 -1
- package/lib/dataflow/propagation/install/util-format.js +10 -4
- package/lib/dataflow/propagation/install/validator/hooks.js +1 -1
- package/lib/dataflow/propagation/install/validator/index.js +1 -1
- package/lib/dataflow/propagation/install/validator/methods.js +1 -1
- package/lib/dataflow/sinks/common.js +1 -1
- package/lib/dataflow/sinks/index.js +1 -1
- package/lib/dataflow/sinks/install/child-process.js +21 -15
- package/lib/dataflow/sinks/install/eval.js +17 -15
- package/lib/dataflow/sinks/install/express/index.js +1 -1
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +15 -9
- package/lib/dataflow/sinks/install/fastify/index.js +1 -1
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +13 -6
- package/lib/dataflow/sinks/install/fs.js +8 -8
- package/lib/dataflow/sinks/install/function.js +9 -13
- package/lib/dataflow/sinks/install/http/index.js +1 -1
- package/lib/dataflow/sinks/install/http/request.js +17 -9
- package/lib/dataflow/sinks/install/http/server-response.js +12 -3
- package/lib/dataflow/sinks/install/koa/index.js +1 -1
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +16 -9
- package/lib/dataflow/sinks/install/libxmljs.js +16 -11
- package/lib/dataflow/sinks/install/marsdb.js +17 -12
- package/lib/dataflow/sinks/install/mongodb.js +32 -22
- package/lib/dataflow/sinks/install/mssql.js +21 -10
- package/lib/dataflow/sinks/install/mysql.js +16 -9
- package/lib/dataflow/sinks/install/node-serialize.js +16 -18
- package/lib/dataflow/sinks/install/postgres.js +18 -5
- package/lib/dataflow/sinks/install/sequelize.js +23 -17
- package/lib/dataflow/sinks/install/sqlite3.js +21 -8
- package/lib/dataflow/sinks/install/vm.js +20 -18
- package/lib/dataflow/sources/common.js +1 -1
- package/lib/dataflow/sources/handler.js +11 -10
- package/lib/dataflow/sources/index.js +2 -2
- package/lib/dataflow/sources/install/body-parser1.js +11 -13
- package/lib/dataflow/sources/install/{busboy1.js → busboy.js} +15 -15
- package/lib/dataflow/sources/install/cookie-parser1.js +7 -6
- package/lib/dataflow/sources/install/express/index.js +1 -1
- package/lib/dataflow/sources/install/express/params.js +9 -10
- package/lib/dataflow/sources/install/express/parsedUrl.js +1 -1
- package/lib/dataflow/sources/install/fastify/fastify.js +6 -7
- package/lib/dataflow/sources/install/fastify/index.js +1 -1
- package/lib/dataflow/sources/install/formidable1.js +8 -6
- package/lib/dataflow/sources/install/http.js +17 -45
- package/lib/dataflow/sources/install/koa/index.js +2 -1
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +10 -9
- package/lib/dataflow/sources/install/koa/koa-multer.js +102 -0
- package/lib/dataflow/sources/install/koa/koa-routers.js +6 -8
- package/lib/dataflow/sources/install/koa/koa2.js +42 -38
- package/lib/dataflow/sources/install/multer1.js +26 -52
- package/lib/dataflow/sources/install/qs6.js +7 -6
- package/lib/dataflow/sources/install/querystring.js +5 -8
- package/lib/dataflow/tag-utils.js +1 -1
- package/lib/dataflow/tracker.js +1 -1
- package/lib/dataflow/utils/is-safe-content-type.js +1 -1
- package/lib/dataflow/utils/is-vulnerable.js +1 -1
- package/lib/event-factory.js +75 -26
- package/lib/get-policy.js +68 -0
- package/lib/get-source-context.js +62 -0
- package/lib/index.d.ts +64 -0
- package/lib/index.js +21 -20
- package/lib/make-source-context.js +78 -0
- package/lib/response-scanning/handlers/index.js +56 -29
- package/lib/response-scanning/handlers/utils.js +1 -1
- package/lib/response-scanning/index.js +1 -1
- package/lib/response-scanning/install/http.js +14 -8
- package/lib/rule-scopes.js +48 -0
- package/lib/session-configuration/common.js +1 -1
- package/lib/session-configuration/handlers.js +66 -47
- package/lib/session-configuration/index.js +3 -1
- package/lib/session-configuration/install/express-session.js +23 -26
- package/lib/session-configuration/install/fastify-cookie.js +110 -0
- package/lib/session-configuration/install/hapi.js +8 -11
- package/lib/session-configuration/install/koa.js +101 -0
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -28,17 +28,14 @@ module.exports = (core) => {
|
|
|
28
28
|
(querystring) => patcher.patch(querystring, 'parse', {
|
|
29
29
|
name,
|
|
30
30
|
patchType,
|
|
31
|
-
post({ args, hooked, orig, result }) {
|
|
31
|
+
post({ args, hooked, orig, result, funcKey }) {
|
|
32
32
|
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
33
33
|
const inputType = InputType.QUERYSTRING;
|
|
34
34
|
|
|
35
|
-
if (!sourceContext)
|
|
36
|
-
logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
35
|
+
if (!sourceContext) return;
|
|
39
36
|
|
|
40
37
|
if (sourceContext.parsedQuery) {
|
|
41
|
-
logger.trace({
|
|
38
|
+
logger.trace({ funcKey }, 'values already tracked');
|
|
42
39
|
return;
|
|
43
40
|
}
|
|
44
41
|
|
|
@@ -61,7 +58,7 @@ module.exports = (core) => {
|
|
|
61
58
|
// we do not set the `parsedQuery` value here so that frameworks
|
|
62
59
|
// may handle queries in their own more specific manner.
|
|
63
60
|
} catch (err) {
|
|
64
|
-
logger.error({ err,
|
|
61
|
+
logger.error({ err, funcKey }, 'unable to handle source');
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
}
|
package/lib/dataflow/tracker.js
CHANGED
package/lib/event-factory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType, match } = require('@contrast/common');
|
|
19
|
-
const
|
|
19
|
+
const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
|
|
20
|
+
const SOURCE_EVENT_MSG = 'Source event not created: %s';
|
|
21
|
+
const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
|
|
20
22
|
|
|
21
|
-
module.exports = function(core) {
|
|
23
|
+
module.exports = function (core) {
|
|
22
24
|
const {
|
|
23
25
|
createSnapshot,
|
|
24
26
|
config,
|
|
@@ -30,7 +32,7 @@ module.exports = function(core) {
|
|
|
30
32
|
|
|
31
33
|
eventFactory.createdEvents = new WeakSet();
|
|
32
34
|
|
|
33
|
-
eventFactory.createSourceEvent = function(data = {}) {
|
|
35
|
+
eventFactory.createSourceEvent = function (data = {}) {
|
|
34
36
|
const {
|
|
35
37
|
name,
|
|
36
38
|
result = { value: null, tracked: false },
|
|
@@ -39,31 +41,29 @@ module.exports = function(core) {
|
|
|
39
41
|
stack,
|
|
40
42
|
} = data;
|
|
41
43
|
|
|
42
|
-
const baseMessage = 'Source event not created: %s';
|
|
43
|
-
|
|
44
44
|
if (!result.value) {
|
|
45
|
-
logger.debug({
|
|
45
|
+
logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid result');
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
if (!name) {
|
|
50
|
-
logger.debug({ data },
|
|
50
|
+
logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid name');
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (!(inputType in InputType)) {
|
|
55
|
-
logger.debug({
|
|
55
|
+
logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid inputType');
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (!tags) {
|
|
60
|
-
logger.debug({ data },
|
|
60
|
+
logger.debug({ data }, SOURCE_EVENT_MSG, 'event has no tags');
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
if (!stack || !Array.isArray(stack)) {
|
|
66
|
-
logger.debug({ data },
|
|
66
|
+
logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid stack');
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -73,7 +73,7 @@ module.exports = function(core) {
|
|
|
73
73
|
return data;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
eventFactory.createPropagationEvent = function(data) {
|
|
76
|
+
eventFactory.createPropagationEvent = function (data) {
|
|
77
77
|
const {
|
|
78
78
|
name = '',
|
|
79
79
|
moduleName,
|
|
@@ -94,30 +94,32 @@ module.exports = function(core) {
|
|
|
94
94
|
const sourceContext = sources.getStore()?.assess;
|
|
95
95
|
|
|
96
96
|
if (!sourceContext) {
|
|
97
|
-
logger.debug({
|
|
97
|
+
logger.debug({ data }, 'No sourceContext found during Propagation event creation');
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (sourceContext.propagationEventsCount >= config.assess.max_propagation_events) {
|
|
102
|
-
logger.debug({
|
|
102
|
+
logger.debug({ data }, 'Maximum number of Propagation events reached. Event not created');
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
if (!name) {
|
|
107
|
-
logger.debug({
|
|
107
|
+
logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid name');
|
|
108
108
|
return null;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (!history.length) {
|
|
112
|
-
logger.debug({
|
|
112
|
+
logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid history');
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
if (
|
|
117
|
-
(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
if (!source || !match(source, ANNOTATION_REGEX)) {
|
|
117
|
+
logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid source');
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!target || !match(target, ANNOTATION_REGEX)) {
|
|
122
|
+
logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid target');
|
|
121
123
|
return null;
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -152,7 +154,7 @@ module.exports = function(core) {
|
|
|
152
154
|
return event;
|
|
153
155
|
};
|
|
154
156
|
|
|
155
|
-
eventFactory.createSinkEvent = function(data) {
|
|
157
|
+
eventFactory.createSinkEvent = function (data) {
|
|
156
158
|
const {
|
|
157
159
|
context,
|
|
158
160
|
name = '',
|
|
@@ -169,7 +171,7 @@ module.exports = function(core) {
|
|
|
169
171
|
|
|
170
172
|
const sourceContext = sources.getStore()?.assess;
|
|
171
173
|
if (!sourceContext) {
|
|
172
|
-
logger.debug('no sourceContext found during sink event creation');
|
|
174
|
+
logger.debug({ data }, 'no sourceContext found during sink event creation');
|
|
173
175
|
return null;
|
|
174
176
|
}
|
|
175
177
|
if (!name) {
|
|
@@ -181,7 +183,7 @@ module.exports = function(core) {
|
|
|
181
183
|
return null;
|
|
182
184
|
}
|
|
183
185
|
if (
|
|
184
|
-
(!source || !source.match(
|
|
186
|
+
(!source || !source.match(ANNOTATION_REGEX))
|
|
185
187
|
) {
|
|
186
188
|
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
187
189
|
return null;
|
|
@@ -214,7 +216,7 @@ module.exports = function(core) {
|
|
|
214
216
|
return event;
|
|
215
217
|
};
|
|
216
218
|
|
|
217
|
-
eventFactory.createSessionEvent = function(data) {
|
|
219
|
+
eventFactory.createSessionEvent = function (data) {
|
|
218
220
|
const {
|
|
219
221
|
context,
|
|
220
222
|
name = '',
|
|
@@ -235,7 +237,7 @@ module.exports = function(core) {
|
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
if (
|
|
238
|
-
(!source || !source.match(
|
|
240
|
+
(!source || !source.match(ANNOTATION_REGEX))
|
|
239
241
|
) {
|
|
240
242
|
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
241
243
|
return null;
|
|
@@ -270,5 +272,52 @@ module.exports = function(core) {
|
|
|
270
272
|
return event;
|
|
271
273
|
};
|
|
272
274
|
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {{
|
|
278
|
+
* context: string,
|
|
279
|
+
* name: string,
|
|
280
|
+
* moduleName: string,
|
|
281
|
+
* methodName: string,
|
|
282
|
+
* object: { value: any, tracked: boolean },
|
|
283
|
+
* args: any[],
|
|
284
|
+
* result: { value: vany, tracked: boolean },
|
|
285
|
+
* source: string,
|
|
286
|
+
* stacktraceOpts: { constructorOpt?: Function},
|
|
287
|
+
* }} data
|
|
288
|
+
* @returns {any}
|
|
289
|
+
*/
|
|
290
|
+
eventFactory.createCryptoAnalysisEvent = function (data) {
|
|
291
|
+
const {
|
|
292
|
+
name = '',
|
|
293
|
+
source,
|
|
294
|
+
stacktraceOpts,
|
|
295
|
+
} = data;
|
|
296
|
+
|
|
297
|
+
if (!name) {
|
|
298
|
+
logger.debug({ data }, 'no sink event name');
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!source || !source.match(ANNOTATION_REGEX)) {
|
|
303
|
+
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let stack;
|
|
308
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
309
|
+
stack = createSnapshot(stacktraceOpts)();
|
|
310
|
+
} else {
|
|
311
|
+
stack = [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
data.stack = stack;
|
|
315
|
+
data.time = Date.now();
|
|
316
|
+
|
|
317
|
+
eventFactory.createdEvents.add(data);
|
|
318
|
+
|
|
319
|
+
return data;
|
|
320
|
+
};
|
|
321
|
+
|
|
273
322
|
return eventFactory;
|
|
274
323
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 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 {
|
|
19
|
+
Rule,
|
|
20
|
+
ResponseScanningRule,
|
|
21
|
+
SessionConfigurationRule,
|
|
22
|
+
Event,
|
|
23
|
+
} = require('@contrast/common');
|
|
24
|
+
|
|
25
|
+
const rulesIds = Object.values({
|
|
26
|
+
...Rule,
|
|
27
|
+
...ResponseScanningRule,
|
|
28
|
+
...SessionConfigurationRule,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {{
|
|
33
|
+
* config: import('@contrast/config').Config,
|
|
34
|
+
* logger: import('@contrast/logger').Logger,
|
|
35
|
+
* messages: import('@contrast/common').Messages,
|
|
36
|
+
* }} core
|
|
37
|
+
* @returns {import('@contrast/common').Installable}
|
|
38
|
+
*/
|
|
39
|
+
module.exports = function assess(core) {
|
|
40
|
+
const { logger, messages } = core;
|
|
41
|
+
|
|
42
|
+
const enabledRules = new Set(rulesIds);
|
|
43
|
+
|
|
44
|
+
messages.on(Event.SERVER_SETTINGS_UPDATE, (msg) => {
|
|
45
|
+
if (!msg.assess) return;
|
|
46
|
+
|
|
47
|
+
for (const ruleId of rulesIds) {
|
|
48
|
+
const enable = msg.assess[ruleId]?.enable;
|
|
49
|
+
if (enable === true) {
|
|
50
|
+
enabledRules.add(ruleId);
|
|
51
|
+
if (ruleId === Rule.NOSQL_INJECTION) enabledRules.add(Rule.NOSQL_INJECTION_MONGO);
|
|
52
|
+
} else if (enable === false) {
|
|
53
|
+
if (ruleId === Rule.NOSQL_INJECTION) enabledRules.delete(Rule.NOSQL_INJECTION_MONGO);
|
|
54
|
+
enabledRules.delete(ruleId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
logger.info({
|
|
58
|
+
enabledRules: Array.from(enabledRules)
|
|
59
|
+
}, 'Assess policy updated');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return core.assess.getPolicy = function getPolicy() {
|
|
63
|
+
// creates copy of local policy for request store
|
|
64
|
+
return {
|
|
65
|
+
enabledRules: new Set(enabledRules),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 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 { InstrumentationType } = require('./constants');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* assess: import('@contrast/assess').Assess,
|
|
23
|
+
* config: import('@contrast/config').Config,
|
|
24
|
+
* scopes: import('@contrast/scopes').Scopes,
|
|
25
|
+
* }} core
|
|
26
|
+
*/
|
|
27
|
+
module.exports = function(core) {
|
|
28
|
+
const {
|
|
29
|
+
config,
|
|
30
|
+
scopes: { sources, instrumentation },
|
|
31
|
+
assess: { ruleScopes },
|
|
32
|
+
} = core;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {import('./constants.js').InstrumentationType} type
|
|
36
|
+
* @returns {import('@contrast/assess').SourceContext|null} the assess store
|
|
37
|
+
*/
|
|
38
|
+
return core.assess.getSourceContext = function getSourceContext(type, ...rest) {
|
|
39
|
+
const ctx = sources.getStore()?.assess;
|
|
40
|
+
|
|
41
|
+
if (!ctx || instrumentation.isLocked()) return null;
|
|
42
|
+
|
|
43
|
+
switch (type) {
|
|
44
|
+
case InstrumentationType.PROPAGATOR: {
|
|
45
|
+
if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case InstrumentationType.SOURCE: {
|
|
49
|
+
if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case InstrumentationType.RULE: {
|
|
53
|
+
const [ruleId] = rest;
|
|
54
|
+
if (!ruleId) break;
|
|
55
|
+
if (!ctx.policy.enabledRules.has(ruleId) || ruleScopes.isLocked(ruleId)) return null;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ctx;
|
|
61
|
+
};
|
|
62
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 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
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
16
|
+
import {
|
|
17
|
+
Rule,
|
|
18
|
+
SessionConfigurationRule,
|
|
19
|
+
} from '@contrast/common';
|
|
20
|
+
|
|
21
|
+
export enum InstrumentationType {
|
|
22
|
+
SOURCE = 'source',
|
|
23
|
+
PROPAGATOR = 'propagator',
|
|
24
|
+
RULE = 'rule',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SourceContext {
|
|
28
|
+
reqData: object,
|
|
29
|
+
responseData: {
|
|
30
|
+
contentType: string,
|
|
31
|
+
},
|
|
32
|
+
policy: {
|
|
33
|
+
enabledRules: Set<string>,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Policy {
|
|
38
|
+
enabledRules: Set<Rule>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RuleScopes {
|
|
42
|
+
run(ruleId: Rule, cb: void): void;
|
|
43
|
+
isLocked(ruleId: Rule): boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SessionRuleState {
|
|
47
|
+
reported: boolean,
|
|
48
|
+
valuesAnalyzed: Set<string>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RuleState {
|
|
52
|
+
[SessionConfigurationRule.HTTPONLY]?: SessionRuleState,
|
|
53
|
+
[SessionConfigurationRule.SECURE_FLAG_MISSING]?: SessionRuleState,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Assess {
|
|
57
|
+
getPolicy(): Policy,
|
|
58
|
+
getSourceContext(instrType?: InstrumentationType, opts?: any): SourceContext,
|
|
59
|
+
makeSourceContext(req: IncomingMessage, res: ServerResponse): SourceContext,
|
|
60
|
+
ruleScopes: RuleScopes,
|
|
61
|
+
ruleState: RuleState,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -16,32 +16,33 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
|
-
const sessionConfiguration = require('./session-configuration');
|
|
20
|
-
const dataflow = require('./dataflow');
|
|
21
|
-
const responseScanning = require('./response-scanning');
|
|
22
|
-
const eventFactory = require('./event-factory');
|
|
23
19
|
|
|
24
20
|
module.exports = function assess(core) {
|
|
25
|
-
const assess = core.assess = {
|
|
21
|
+
const assess = core.assess = {
|
|
22
|
+
install() {
|
|
23
|
+
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
24
|
+
core.logger.debug('assess is disabled, skipping installation');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
core.rewriter.install('assess');
|
|
29
|
+
callChildComponentMethodsSync(core.assess, 'install');
|
|
30
|
+
},
|
|
31
|
+
};
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
require('./rule-scopes')(core);
|
|
34
|
+
require('./get-policy')(core);
|
|
35
|
+
require('./make-source-context')(core);
|
|
36
|
+
require('./get-source-context')(core);
|
|
37
|
+
require('./event-factory')(core);
|
|
38
|
+
require('./crypto-analysis')(core);
|
|
39
|
+
require('./dataflow')(core);
|
|
40
|
+
require('./response-scanning')(core);
|
|
41
|
+
require('./session-configuration')(core);
|
|
31
42
|
|
|
32
43
|
// todo
|
|
33
44
|
// crypto rule implementations
|
|
34
45
|
// static rule implementations in coordination with (CLI) rewriter
|
|
35
46
|
|
|
36
|
-
assess.install = function() {
|
|
37
|
-
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
38
|
-
core.logger.debug('assess is disabled, skipping installation');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
core.rewriter.install('assess');
|
|
43
|
-
callChildComponentMethodsSync(core.assess, 'install');
|
|
44
|
-
};
|
|
45
|
-
|
|
46
47
|
return assess;
|
|
47
48
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 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 { toLowerCase } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* assess: import('@contrast/assess').Assess,
|
|
23
|
+
* logger: import('@contrast/logger').Logger,
|
|
24
|
+
* }} core
|
|
25
|
+
*/
|
|
26
|
+
module.exports = function(core) {
|
|
27
|
+
const {
|
|
28
|
+
assess: { getPolicy },
|
|
29
|
+
logger,
|
|
30
|
+
} = core;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @returns {import('@contrast/assess').SourceContext}
|
|
34
|
+
*/
|
|
35
|
+
return core.assess.makeSourceContext = function (req, res) {
|
|
36
|
+
let contentType, queries, uriPath;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const ix = req.url.indexOf('?');
|
|
40
|
+
if (ix >= 0) {
|
|
41
|
+
uriPath = req.url.slice(0, ix);
|
|
42
|
+
queries = req.url.slice(ix + 1);
|
|
43
|
+
} else {
|
|
44
|
+
uriPath = req.url;
|
|
45
|
+
queries = '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// copy to avoid storing tracked values
|
|
49
|
+
const headers = { ...req.headers };
|
|
50
|
+
if (headers['content-type']) {
|
|
51
|
+
contentType = toLowerCase(headers['content-type']);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
propagationEventsCount: 0,
|
|
56
|
+
sourceEventsCount: 0,
|
|
57
|
+
policy: getPolicy(),
|
|
58
|
+
reqData: {
|
|
59
|
+
ip: req.socket.remoteAddress,
|
|
60
|
+
httpVersion: req.httpVersion,
|
|
61
|
+
method: req.method,
|
|
62
|
+
headers,
|
|
63
|
+
uriPath,
|
|
64
|
+
queries,
|
|
65
|
+
contentType,
|
|
66
|
+
},
|
|
67
|
+
responseData: {},
|
|
68
|
+
ruleState: {},
|
|
69
|
+
};
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error(
|
|
72
|
+
{ err },
|
|
73
|
+
'unable to construct assess store. assess will be disabled for request.'
|
|
74
|
+
);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|