@contrast/assess 1.17.0 → 1.19.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/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 +151 -0
- package/lib/crypto-analysis/install/math.js +99 -0
- package/lib/dataflow/propagation/index.js +2 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +12 -11
- package/lib/dataflow/propagation/install/JSON/stringify.js +1 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +2 -2
- package/lib/dataflow/propagation/install/ejs/index.js +1 -0
- package/lib/dataflow/propagation/install/ejs/template.js +77 -0
- package/lib/dataflow/propagation/install/sequelize/index.js +31 -0
- package/lib/dataflow/propagation/install/sequelize/query-generator.js +90 -0
- package/lib/dataflow/propagation/install/{sequelize.js → sequelize/sql-string.js} +3 -3
- package/lib/dataflow/propagation/install/util-format.js +126 -0
- package/lib/dataflow/sinks/index.js +1 -0
- package/lib/dataflow/sinks/install/child-process.js +20 -14
- package/lib/dataflow/sinks/install/eval.js +16 -14
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +14 -8
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +12 -5
- package/lib/dataflow/sinks/install/fs.js +7 -7
- package/lib/dataflow/sinks/install/function.js +8 -12
- package/lib/dataflow/sinks/install/http/request.js +16 -8
- package/lib/dataflow/sinks/install/http/server-response.js +11 -2
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +15 -8
- package/lib/dataflow/sinks/install/libxmljs.js +15 -10
- package/lib/dataflow/sinks/install/marsdb.js +13 -8
- package/lib/dataflow/sinks/install/mongodb.js +25 -15
- package/lib/dataflow/sinks/install/mssql.js +20 -9
- package/lib/dataflow/sinks/install/mysql.js +15 -8
- package/lib/dataflow/sinks/install/node-serialize.js +101 -0
- package/lib/dataflow/sinks/install/postgres.js +17 -4
- package/lib/dataflow/sinks/install/sequelize.js +16 -9
- package/lib/dataflow/sinks/install/sqlite3.js +20 -7
- package/lib/dataflow/sinks/install/vm.js +19 -17
- package/lib/dataflow/sources/install/http.js +14 -42
- package/lib/dataflow/sources/install/koa/index.js +1 -0
- package/lib/dataflow/sources/install/koa/koa-multer.js +102 -0
- package/lib/dataflow/sources/install/multer1.js +25 -51
- package/lib/dataflow/sources/install/querystring.js +1 -4
- package/lib/event-factory.js +47 -0
- package/lib/get-policy.js +68 -0
- package/lib/get-source-context.js +62 -0
- package/lib/index.d.ts +50 -0
- package/lib/index.js +20 -19
- package/lib/make-source-context.js +74 -0
- package/lib/response-scanning/handlers/index.js +55 -28
- package/lib/response-scanning/install/http.js +13 -7
- package/lib/rule-scopes.js +48 -0
- package/lib/session-configuration/handlers.js +4 -3
- package/lib/session-configuration/install/express-session.js +8 -2
- package/package.json +2 -2
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
const { patchType, filterSafeTags } = require('../common');
|
|
18
18
|
const {
|
|
19
|
-
isString,
|
|
20
19
|
DataflowTag: {
|
|
21
20
|
UNTRUSTED,
|
|
22
21
|
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
@@ -25,14 +24,17 @@ const {
|
|
|
25
24
|
CUSTOM_VALIDATED,
|
|
26
25
|
LIMITED_CHARS,
|
|
27
26
|
},
|
|
28
|
-
Rule: { UNSAFE_CODE_EXECUTION },
|
|
29
|
-
isNonEmptyObject,
|
|
27
|
+
Rule: { UNSAFE_CODE_EXECUTION: ruleId },
|
|
30
28
|
inspect,
|
|
31
|
-
|
|
29
|
+
isNonEmptyObject,
|
|
30
|
+
isString,
|
|
32
31
|
join,
|
|
33
32
|
split,
|
|
33
|
+
traverseValues,
|
|
34
34
|
} = require('@contrast/common');
|
|
35
|
+
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
35
36
|
const { createAdjustedQueryTags } = require('../../tag-utils');
|
|
37
|
+
|
|
36
38
|
const safeTags = [
|
|
37
39
|
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
38
40
|
CUSTOM_ENCODED,
|
|
@@ -41,13 +43,20 @@ const safeTags = [
|
|
|
41
43
|
LIMITED_CHARS,
|
|
42
44
|
];
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* @param {{
|
|
48
|
+
* assess: import('@contrast/assess').Assess,
|
|
49
|
+
* config: import('@contrast/config').Config,
|
|
50
|
+
* }} core
|
|
51
|
+
* @returns {import('@contrast/common').Installable}
|
|
52
|
+
*/
|
|
44
53
|
module.exports = function (core) {
|
|
45
54
|
const {
|
|
46
55
|
config,
|
|
47
56
|
depHooks,
|
|
48
57
|
patcher,
|
|
49
|
-
scopes: { sources, instrumentation },
|
|
50
58
|
assess: {
|
|
59
|
+
getSourceContext,
|
|
51
60
|
eventFactory: { createSinkEvent },
|
|
52
61
|
dataflow: {
|
|
53
62
|
tracker,
|
|
@@ -133,14 +142,7 @@ module.exports = function (core) {
|
|
|
133
142
|
}
|
|
134
143
|
|
|
135
144
|
function around(next, { args: origArgs, hooked, orig, name }) {
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
!store ||
|
|
139
|
-
instrumentation.isLocked() ||
|
|
140
|
-
isLocked(UNSAFE_CODE_EXECUTION)
|
|
141
|
-
) {
|
|
142
|
-
return next();
|
|
143
|
-
}
|
|
145
|
+
if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return next();
|
|
144
146
|
|
|
145
147
|
const methodPath = split(name, '.');
|
|
146
148
|
const method = methodPath[methodPath.length - 1];
|
|
@@ -189,13 +191,13 @@ module.exports = function (core) {
|
|
|
189
191
|
|
|
190
192
|
reportSafePositive({
|
|
191
193
|
name,
|
|
192
|
-
ruleId
|
|
194
|
+
ruleId,
|
|
193
195
|
safeTags: Array.from(foundSafeTags),
|
|
194
196
|
strInfo,
|
|
195
197
|
});
|
|
196
198
|
|
|
197
199
|
return name === 'vm.runInNewContext'
|
|
198
|
-
? runInActiveSink(
|
|
200
|
+
? runInActiveSink(ruleId, () => next())
|
|
199
201
|
: next();
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -233,14 +235,14 @@ module.exports = function (core) {
|
|
|
233
235
|
|
|
234
236
|
if (event) {
|
|
235
237
|
reportFindings({
|
|
236
|
-
ruleId
|
|
238
|
+
ruleId,
|
|
237
239
|
sinkEvent: event,
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
return name === 'vm.runInNewContext'
|
|
243
|
-
? runInActiveSink(
|
|
245
|
+
? runInActiveSink(ruleId, () => next())
|
|
244
246
|
: next();
|
|
245
247
|
}
|
|
246
248
|
|
|
@@ -17,12 +17,17 @@
|
|
|
17
17
|
const { patchType } = require('../common');
|
|
18
18
|
const { toLowerCase, InputType } = require('@contrast/common');
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* assess: import('@contrast/assess').Assess,
|
|
23
|
+
* }} core
|
|
24
|
+
*/
|
|
20
25
|
module.exports = function(core) {
|
|
21
26
|
const {
|
|
22
|
-
|
|
27
|
+
assess: { dataflow, makeSourceContext },
|
|
23
28
|
instrumentation: { instrument },
|
|
24
|
-
assess: { dataflow },
|
|
25
29
|
patcher,
|
|
30
|
+
scopes,
|
|
26
31
|
} = core;
|
|
27
32
|
|
|
28
33
|
const logger = core.logger.child('contrast:assess');
|
|
@@ -34,19 +39,20 @@ module.exports = function(core) {
|
|
|
34
39
|
function around(next, data) {
|
|
35
40
|
const [type] = data.args;
|
|
36
41
|
|
|
37
|
-
if (type !== 'request')
|
|
38
|
-
return next();
|
|
39
|
-
}
|
|
42
|
+
if (type !== 'request') return next();
|
|
40
43
|
|
|
41
44
|
try {
|
|
42
45
|
const [, req, res] = data.args;
|
|
43
46
|
const store = scopes.sources.getStore();
|
|
44
47
|
|
|
45
48
|
if (!store) {
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
// this would indicate that sources did not install correctly
|
|
50
|
+
throw new Error('async request store not found');
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
store.assess = makeSourceContext(req, res);
|
|
54
|
+
if (!store.assess) return;
|
|
55
|
+
|
|
50
56
|
patcher.patch(res, 'writeHead', {
|
|
51
57
|
name: 'write-head',
|
|
52
58
|
patchType,
|
|
@@ -86,27 +92,7 @@ module.exports = function(core) {
|
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
let uriPath, queries;
|
|
90
|
-
const ix = req.url.indexOf('?');
|
|
91
|
-
|
|
92
|
-
if (ix >= 0) {
|
|
93
|
-
uriPath = req.url.slice(0, ix);
|
|
94
|
-
queries = req.url.slice(ix + 1);
|
|
95
|
-
} else {
|
|
96
|
-
uriPath = req.url;
|
|
97
|
-
queries = '';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const headers = {};
|
|
101
95
|
const sourceName = 'ClientRequest';
|
|
102
|
-
|
|
103
|
-
store.assess = {
|
|
104
|
-
responseData: {},
|
|
105
|
-
sourceEventsCount: 0,
|
|
106
|
-
propagationEventsCount: 0,
|
|
107
|
-
findings: {},
|
|
108
|
-
};
|
|
109
|
-
|
|
110
96
|
const sourceInfo = {
|
|
111
97
|
name: sourceName,
|
|
112
98
|
stacktraceOpts: {
|
|
@@ -141,24 +127,10 @@ module.exports = function(core) {
|
|
|
141
127
|
|
|
142
128
|
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
143
129
|
const header = toLowerCase(req.rawHeaders[i]);
|
|
144
|
-
headers[header] = req.rawHeaders[i + 1];
|
|
145
130
|
req.rawHeaders[i + 1] = req.headers[header];
|
|
146
131
|
}
|
|
147
|
-
|
|
148
|
-
const contentType = headers['content-type'] && toLowerCase(headers['content-type']);
|
|
149
|
-
|
|
150
|
-
store.assess.reqData = {
|
|
151
|
-
ip: req.socket.remoteAddress,
|
|
152
|
-
httpVersion: req.httpVersion,
|
|
153
|
-
method: req.method,
|
|
154
|
-
headers,
|
|
155
|
-
uriPath,
|
|
156
|
-
queries,
|
|
157
|
-
contentType,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
132
|
} catch (err) {
|
|
161
|
-
logger.error({ err }, 'Error during
|
|
133
|
+
logger.error({ err }, 'Error during Assess request handling');
|
|
162
134
|
}
|
|
163
135
|
|
|
164
136
|
setImmediate(() => {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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
|
+
const { patchType } = require('../../common');
|
|
18
|
+
const { InputType } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = (core) => {
|
|
21
|
+
const {
|
|
22
|
+
depHooks,
|
|
23
|
+
logger,
|
|
24
|
+
patcher,
|
|
25
|
+
scopes,
|
|
26
|
+
assess: { dataflow: { sources } },
|
|
27
|
+
} = core;
|
|
28
|
+
|
|
29
|
+
function handler(req, constructorOpt) {
|
|
30
|
+
const sourceContext = scopes.sources.getStore()?.assess;
|
|
31
|
+
if (!sourceContext) return;
|
|
32
|
+
|
|
33
|
+
function handle(context, data, key) {
|
|
34
|
+
try {
|
|
35
|
+
sources.handle({
|
|
36
|
+
context,
|
|
37
|
+
data,
|
|
38
|
+
keys: [key],
|
|
39
|
+
name: 'multer',
|
|
40
|
+
inputType: InputType.BODY,
|
|
41
|
+
sourceContext,
|
|
42
|
+
stacktraceOpts: {
|
|
43
|
+
constructorOpt,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logger.error({ err }, 'error handling Koa multer Assess dataflow %s.%s source', context, key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (req.file) {
|
|
52
|
+
handle('req', req, 'file');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(req.files)) {
|
|
56
|
+
for (let i = 0; i < req.files.length; i++) {
|
|
57
|
+
handle('req.files', req.files[i], i);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (req.body && Object.keys(req.body).length) {
|
|
62
|
+
handle('req', req, 'body');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function install() {
|
|
67
|
+
['koa-multer', '@koa/multer'].forEach((name) => {
|
|
68
|
+
depHooks.resolve(
|
|
69
|
+
{ name }, (_export) => {
|
|
70
|
+
const origMulter = _export;
|
|
71
|
+
return patcher.patch(_export, {
|
|
72
|
+
name,
|
|
73
|
+
patchType,
|
|
74
|
+
post(data) {
|
|
75
|
+
const { args, hooked } = data;
|
|
76
|
+
const instance = origMulter.apply(this, args);
|
|
77
|
+
const origMake = instance._makeMiddleware;
|
|
78
|
+
instance._makeMiddleware = function _makeMiddleware(...args) {
|
|
79
|
+
const origMulterMiddleware = origMake.apply(this, args);
|
|
80
|
+
return function multerMiddleware(req, res, origNext) {
|
|
81
|
+
|
|
82
|
+
const next = function(...args) {
|
|
83
|
+
handler(req, hooked);
|
|
84
|
+
return origNext.apply(this, args);
|
|
85
|
+
};
|
|
86
|
+
return origMulterMiddleware.apply(this, [req, res, next]);
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
data.result = instance;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const koaMulterInstrumentation = sources.koaInstrumentation.koaMulter = {
|
|
98
|
+
install
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return koaMulterInstrumentation;
|
|
102
|
+
};
|
|
@@ -23,36 +23,20 @@ module.exports = (core) => {
|
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
25
25
|
logger,
|
|
26
|
-
assess,
|
|
27
|
-
scopes
|
|
26
|
+
assess: { dataflow: { sources } },
|
|
27
|
+
scopes,
|
|
28
28
|
} = core;
|
|
29
29
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
context: 'req',
|
|
34
|
-
data: req,
|
|
35
|
-
keys: ['file'],
|
|
36
|
-
name: 'multer',
|
|
37
|
-
inputType: InputType.BODY,
|
|
38
|
-
sourceContext,
|
|
39
|
-
stacktraceOpts: {
|
|
40
|
-
constructorOpt,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
} catch (err) {
|
|
44
|
-
logger.error({ err }, 'error handling multer Assess dataflow req.file source');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
30
|
+
function handler(req, constructorOpt) {
|
|
31
|
+
const sourceContext = scopes.sources.getStore()?.assess;
|
|
32
|
+
if (!sourceContext) return;
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
let i;
|
|
50
|
-
for (i = 0; i < req.files.length; i++) {
|
|
34
|
+
function handle(context, data, key) {
|
|
51
35
|
try {
|
|
52
|
-
|
|
53
|
-
context
|
|
54
|
-
data
|
|
55
|
-
keys: [
|
|
36
|
+
sources.handle({
|
|
37
|
+
context,
|
|
38
|
+
data,
|
|
39
|
+
keys: [key],
|
|
56
40
|
name: 'multer',
|
|
57
41
|
inputType: InputType.BODY,
|
|
58
42
|
sourceContext,
|
|
@@ -61,26 +45,22 @@ module.exports = (core) => {
|
|
|
61
45
|
},
|
|
62
46
|
});
|
|
63
47
|
} catch (err) {
|
|
64
|
-
logger.error({ err }, 'error handling multer Assess dataflow
|
|
48
|
+
logger.error({ err }, 'error handling multer Assess dataflow %s.%s source', context, key);
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
|
-
}
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
} catch (err) {
|
|
83
|
-
logger.error({ err }, 'error handling multer Assess dataflow req.body source');
|
|
52
|
+
if (req.file) {
|
|
53
|
+
handle('req', req, 'file');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(req.files)) {
|
|
57
|
+
for (let i = 0; i < req.files.length; i++) {
|
|
58
|
+
handle('req.files', req.files[i], i);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (req.body && Object.keys(req.body).length) {
|
|
63
|
+
handle('req', req, 'body');
|
|
84
64
|
}
|
|
85
65
|
}
|
|
86
66
|
|
|
@@ -97,14 +77,8 @@ module.exports = (core) => {
|
|
|
97
77
|
patchType,
|
|
98
78
|
pre(data) {
|
|
99
79
|
const [req, , next, hooked] = data.args;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!sourceContext) return;
|
|
103
|
-
|
|
104
|
-
data.args[2] = wrap(function (...args) {
|
|
105
|
-
if (req.file) handleFile(sourceContext, hooked, req);
|
|
106
|
-
if (Array.isArray(req.files)) handleFiles(sourceContext, hooked, req);
|
|
107
|
-
if (req.body && Object.keys(req.body).length) handleBody(sourceContext, hooked, req.body);
|
|
80
|
+
data.args[2] = scopes.wrap(function (...args) {
|
|
81
|
+
handler(req, hooked);
|
|
108
82
|
next(...args);
|
|
109
83
|
});
|
|
110
84
|
},
|
|
@@ -32,10 +32,7 @@ module.exports = (core) => {
|
|
|
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
38
|
logger.trace({ name }, 'values already tracked');
|
package/lib/event-factory.js
CHANGED
|
@@ -270,5 +270,52 @@ module.exports = function(core) {
|
|
|
270
270
|
return event;
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @param {{
|
|
276
|
+
* context: string,
|
|
277
|
+
* name: string,
|
|
278
|
+
* moduleName: string,
|
|
279
|
+
* methodName: string,
|
|
280
|
+
* object: { value: any, tracked: boolean },
|
|
281
|
+
* args: any[],
|
|
282
|
+
* result: { value: vany, tracked: boolean },
|
|
283
|
+
* source: string,
|
|
284
|
+
* stacktraceOpts: { constructorOpt?: Function},
|
|
285
|
+
* }} data
|
|
286
|
+
* @returns {any}
|
|
287
|
+
*/
|
|
288
|
+
eventFactory.createCryptoAnalysisEvent = function(data) {
|
|
289
|
+
const {
|
|
290
|
+
name = '',
|
|
291
|
+
source,
|
|
292
|
+
stacktraceOpts,
|
|
293
|
+
} = data;
|
|
294
|
+
|
|
295
|
+
if (!name) {
|
|
296
|
+
logger.debug({ data }, 'no sink event name');
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!source || !source.match(annotationRegExp)) {
|
|
301
|
+
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let stack;
|
|
306
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
307
|
+
stack = createSnapshot(stacktraceOpts)();
|
|
308
|
+
} else {
|
|
309
|
+
stack = [];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
data.stack = stack;
|
|
313
|
+
data.time = Date.now();
|
|
314
|
+
|
|
315
|
+
eventFactory.createdEvents.add(data);
|
|
316
|
+
|
|
317
|
+
return data;
|
|
318
|
+
};
|
|
319
|
+
|
|
273
320
|
return eventFactory;
|
|
274
321
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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: 2023 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,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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 { Rule } from '@contrast/common';
|
|
17
|
+
|
|
18
|
+
export enum InstrumentationType {
|
|
19
|
+
SOURCE = 'source',
|
|
20
|
+
PROPAGATOR = 'propagator',
|
|
21
|
+
RULE = 'rule',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SourceContext {
|
|
25
|
+
reqData: object,
|
|
26
|
+
responseData: {
|
|
27
|
+
contentType: string,
|
|
28
|
+
},
|
|
29
|
+
policy: {
|
|
30
|
+
enabledRules: Set<string>,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Policy {
|
|
35
|
+
enabledRules: Set<Rule>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RuleScopes {
|
|
39
|
+
run(ruleId: Rule, cb: void): void;
|
|
40
|
+
isLocked(ruleId: Rule): boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Assess {
|
|
44
|
+
getPolicy(): Policy,
|
|
45
|
+
getSourceContext(instrType?: InstrumentationType, opts?: any): SourceContext,
|
|
46
|
+
makeSourceContext(req: IncomingMessage, res: ServerResponse): SourceContext,
|
|
47
|
+
ruleScopes: RuleScopes,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
|