@contrast/protect 1.25.1 → 1.26.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/common-handler.js +3 -3
- package/lib/error-handlers/install/express4.js +2 -2
- package/lib/error-handlers/install/fastify.js +4 -4
- package/lib/error-handlers/install/hapi.js +1 -1
- package/lib/error-handlers/install/koa2.js +2 -2
- package/lib/get-source-context.js +4 -10
- package/lib/input-analysis/install/busboy1.js +1 -1
- package/lib/input-analysis/install/cookie-parser1.js +1 -1
- package/lib/input-analysis/install/express4.js +2 -2
- package/lib/input-analysis/install/fastify.js +2 -2
- package/lib/input-analysis/install/formidable1.js +1 -1
- package/lib/input-analysis/install/hapi.js +2 -2
- package/lib/input-analysis/install/koa-body5.js +1 -1
- package/lib/input-analysis/install/koa-bodyparser4.js +1 -1
- package/lib/input-analysis/install/koa2.js +3 -3
- package/lib/input-analysis/install/multer1.js +1 -1
- package/lib/input-analysis/install/qs6.js +1 -1
- package/lib/input-analysis/install/universal-cookie4.js +1 -1
- package/lib/input-tracing/handlers/index.js +12 -6
- package/lib/input-tracing/install/child-process.js +2 -2
- package/lib/input-tracing/install/eval.js +2 -2
- package/lib/input-tracing/install/fs.js +1 -1
- package/lib/input-tracing/install/function.js +2 -2
- package/lib/input-tracing/install/marsdb.js +3 -3
- package/lib/input-tracing/install/mongodb.js +181 -186
- package/package.json +3 -3
|
@@ -17,18 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
const { isSecurityException } = require('../security-exception');
|
|
19
19
|
|
|
20
|
-
module.exports = function(core) {
|
|
20
|
+
module.exports = function (core) {
|
|
21
21
|
const {
|
|
22
22
|
logger,
|
|
23
23
|
protect: { getSourceContext },
|
|
24
24
|
} = core;
|
|
25
25
|
|
|
26
|
-
return core.protect.errorHandlers.commonHandler = function(err) {
|
|
26
|
+
return core.protect.errorHandlers.commonHandler = function (err) {
|
|
27
27
|
if (!isSecurityException(err)) {
|
|
28
28
|
throw err;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const sourceContext = getSourceContext(
|
|
31
|
+
const sourceContext = getSourceContext();
|
|
32
32
|
if (!sourceContext) {
|
|
33
33
|
logger.info('SecurityException caught by Contrast but Protect store is unavailable for req handling');
|
|
34
34
|
return;
|
|
@@ -19,7 +19,7 @@ const SecurityException = require('../../security-exception');
|
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
module.exports = function(core) {
|
|
22
|
+
module.exports = function (core) {
|
|
23
23
|
const {
|
|
24
24
|
logger,
|
|
25
25
|
depHooks,
|
|
@@ -107,7 +107,7 @@ module.exports = function(core) {
|
|
|
107
107
|
const ret = orig();
|
|
108
108
|
if (ret && ret.catch) {
|
|
109
109
|
return ret.catch((err) => {
|
|
110
|
-
const sourceContext = protect.getSourceContext(
|
|
110
|
+
const sourceContext = protect.getSourceContext();
|
|
111
111
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
112
112
|
|
|
113
113
|
if (isSecurityException && sourceContext) {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const { isSecurityException } = require('../../security-exception');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
module.exports = function(core) {
|
|
21
|
+
module.exports = function (core) {
|
|
22
22
|
const {
|
|
23
23
|
depHooks,
|
|
24
24
|
patcher,
|
|
@@ -53,11 +53,11 @@ module.exports = function(core) {
|
|
|
53
53
|
* @param {object} request fastify request
|
|
54
54
|
* @param {object} reply fastify repoly
|
|
55
55
|
*/
|
|
56
|
-
fastifyErrorHandler.handler = function(err, request, reply) {
|
|
56
|
+
fastifyErrorHandler.handler = function (err, request, reply) {
|
|
57
57
|
const normalHandler = fastifyErrorHandler._userHandler || fastifyErrorHandler.defaultErrorHandler;
|
|
58
58
|
|
|
59
59
|
if (isSecurityException(err)) {
|
|
60
|
-
const sourceContext = protect.getSourceContext(
|
|
60
|
+
const sourceContext = protect.getSourceContext();
|
|
61
61
|
|
|
62
62
|
if (!sourceContext) {
|
|
63
63
|
normalHandler.call(this, err, request, reply);
|
|
@@ -73,7 +73,7 @@ module.exports = function(core) {
|
|
|
73
73
|
/**
|
|
74
74
|
* Instruments fastify in order to add our custom error handler.
|
|
75
75
|
*/
|
|
76
|
-
fastifyErrorHandler.install = function() {
|
|
76
|
+
fastifyErrorHandler.install = function () {
|
|
77
77
|
depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => patcher.patch(fastify, {
|
|
78
78
|
name: 'fastify',
|
|
79
79
|
patchType,
|
|
@@ -35,7 +35,7 @@ module.exports = function (core) {
|
|
|
35
35
|
patchType,
|
|
36
36
|
post(data) {
|
|
37
37
|
const [err] = data.args;
|
|
38
|
-
const sourceContext = protect.getSourceContext(
|
|
38
|
+
const sourceContext = protect.getSourceContext();
|
|
39
39
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
40
40
|
|
|
41
41
|
if (isSecurityException && sourceContext && err.output.statusCode !== 403) {
|
|
@@ -19,7 +19,7 @@ const SecurityException = require('../../security-exception');
|
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
module.exports = function(core) {
|
|
22
|
+
module.exports = function (core) {
|
|
23
23
|
const {
|
|
24
24
|
logger,
|
|
25
25
|
depHooks,
|
|
@@ -41,7 +41,7 @@ module.exports = function(core) {
|
|
|
41
41
|
patchType,
|
|
42
42
|
around(orig, data) {
|
|
43
43
|
const [err] = data.args;
|
|
44
|
-
const sourceContext = protect.getSourceContext(
|
|
44
|
+
const sourceContext = protect.getSourceContext();
|
|
45
45
|
const isSecurityException = SecurityException.isSecurityException(err);
|
|
46
46
|
|
|
47
47
|
if (isSecurityException && sourceContext) {
|
|
@@ -15,18 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
module.exports = function (core) {
|
|
19
|
-
const { scopes: { sources }
|
|
18
|
+
module.exports = function init(core) {
|
|
19
|
+
const { scopes: { sources } } = core;
|
|
20
20
|
|
|
21
|
-
function getSourceContext(
|
|
21
|
+
function getSourceContext() {
|
|
22
22
|
const sourceContext = sources.getStore()?.protect;
|
|
23
|
-
|
|
24
|
-
if (!sourceContext) {
|
|
25
|
-
logger.debug('source context not available in %s', callPoint);
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return sourceContext.allowed ? null : sourceContext;
|
|
23
|
+
return sourceContext?.allowed ? null : sourceContext;
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
core.protect.getSourceContext = getSourceContext;
|
|
@@ -32,7 +32,7 @@ module.exports = (core) => {
|
|
|
32
32
|
name: 'busboy.prototype.emit',
|
|
33
33
|
patchType,
|
|
34
34
|
pre({ args }) {
|
|
35
|
-
const sourceContext = protect.getSourceContext(
|
|
35
|
+
const sourceContext = protect.getSourceContext();
|
|
36
36
|
|
|
37
37
|
if (sourceContext) {
|
|
38
38
|
const [eventName, , , fileName] = args;
|
|
@@ -40,7 +40,7 @@ module.exports = (core) => {
|
|
|
40
40
|
const [req, , origNext] = data.args;
|
|
41
41
|
|
|
42
42
|
function contrastNext(origErr) {
|
|
43
|
-
const sourceContext = protect.getSourceContext(
|
|
43
|
+
const sourceContext = protect.getSourceContext();
|
|
44
44
|
|
|
45
45
|
let securityException;
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ module.exports = (core) => {
|
|
|
49
49
|
const [req, , origNext] = data.args;
|
|
50
50
|
|
|
51
51
|
function contrastNext(origErr) {
|
|
52
|
-
const sourceContext = protect.getSourceContext(
|
|
52
|
+
const sourceContext = protect.getSourceContext();
|
|
53
53
|
let securityException;
|
|
54
54
|
|
|
55
55
|
// It is possible for the query to be already parsed by `qs`
|
|
@@ -93,7 +93,7 @@ module.exports = (core) => {
|
|
|
93
93
|
// we can exit early if
|
|
94
94
|
// the layer doesn't match the request or
|
|
95
95
|
// the layer doesn't recognize any parameters
|
|
96
|
-
if (!data.result || !layer.keys || layer.keys.length === 0) {
|
|
96
|
+
if (!data.result || !layer.keys || layer.keys.length === 0 || layer.route.path === '*') {
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -40,9 +40,9 @@ module.exports = (core) => {
|
|
|
40
40
|
name: 'fastify.build',
|
|
41
41
|
patchType,
|
|
42
42
|
post({ result: server }) {
|
|
43
|
-
server.addHook('preValidation', function(request, reply, done) {
|
|
43
|
+
server.addHook('preValidation', function (request, reply, done) {
|
|
44
44
|
let securityException;
|
|
45
|
-
const sourceContext = protect.getSourceContext(
|
|
45
|
+
const sourceContext = protect.getSourceContext();
|
|
46
46
|
|
|
47
47
|
if (sourceContext) {
|
|
48
48
|
try {
|
|
@@ -32,7 +32,7 @@ module.exports = (core) => {
|
|
|
32
32
|
name: 'Formidable.IncomingForm.prototype.parse',
|
|
33
33
|
patchType,
|
|
34
34
|
pre(data) {
|
|
35
|
-
const sourceContext = protect.getSourceContext(
|
|
35
|
+
const sourceContext = protect.getSourceContext();
|
|
36
36
|
|
|
37
37
|
if (sourceContext) {
|
|
38
38
|
const origCb = data.args[1];
|
|
@@ -66,7 +66,7 @@ module.exports = (core) => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
server.ext('onPreHandler', function onPreHandler(req, h) {
|
|
69
|
-
const sourceContext = protect.getSourceContext(
|
|
69
|
+
const sourceContext = protect.getSourceContext();
|
|
70
70
|
|
|
71
71
|
if (sourceContext) {
|
|
72
72
|
try {
|
|
@@ -107,7 +107,7 @@ module.exports = (core) => {
|
|
|
107
107
|
name: 'Pez.Dispenser.prototype.emit',
|
|
108
108
|
patchType,
|
|
109
109
|
pre: ({ args }) => {
|
|
110
|
-
const sourceContext = protect.getSourceContext(
|
|
110
|
+
const sourceContext = protect.getSourceContext();
|
|
111
111
|
|
|
112
112
|
if (sourceContext) {
|
|
113
113
|
const [eventName, part] = args;
|
|
@@ -38,7 +38,7 @@ module.exports = (core) => {
|
|
|
38
38
|
const [ctx, origNext] = data.args;
|
|
39
39
|
|
|
40
40
|
async function contrastNext(origErr) {
|
|
41
|
-
const sourceContext = protect.getSourceContext(
|
|
41
|
+
const sourceContext = protect.getSourceContext();
|
|
42
42
|
|
|
43
43
|
if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
|
|
44
44
|
sourceContext.parsedBody = ctx.request.body;
|
|
@@ -38,7 +38,7 @@ module.exports = (core) => {
|
|
|
38
38
|
const [ctx, origNext] = data.args;
|
|
39
39
|
|
|
40
40
|
async function contrastNext(origErr) {
|
|
41
|
-
const sourceContext = protect.getSourceContext(
|
|
41
|
+
const sourceContext = protect.getSourceContext();
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
if (sourceContext && ctx.request.body && Object.keys(ctx.request.body).length) {
|
|
@@ -37,7 +37,7 @@ module.exports = (core) => {
|
|
|
37
37
|
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
38
38
|
function contrastStartMiddleware(ctx, next) {
|
|
39
39
|
if (ctx.query && Object.keys(ctx.query).length) {
|
|
40
|
-
const sourceContext = protect.getSourceContext(
|
|
40
|
+
const sourceContext = protect.getSourceContext();
|
|
41
41
|
|
|
42
42
|
if (sourceContext && !('parsedQuery' in sourceContext)) {
|
|
43
43
|
sourceContext.parsedQuery = ctx.query;
|
|
@@ -57,7 +57,7 @@ module.exports = (core) => {
|
|
|
57
57
|
// if not already inserted, insert the initial middleware.
|
|
58
58
|
if (
|
|
59
59
|
app.middleware &&
|
|
60
|
-
|
|
60
|
+
(!app.middleware[0] || !app.middleware[0]._isContrastStartMiddleware)
|
|
61
61
|
) {
|
|
62
62
|
app.middleware.splice(0, 0, contrastStartMiddleware);
|
|
63
63
|
}
|
|
@@ -100,7 +100,7 @@ module.exports = (core) => {
|
|
|
100
100
|
const [ctx, origNext] = data.args;
|
|
101
101
|
|
|
102
102
|
async function contrastNext(origErr) {
|
|
103
|
-
const sourceContext = protect.getSourceContext(
|
|
103
|
+
const sourceContext = protect.getSourceContext();
|
|
104
104
|
|
|
105
105
|
if (sourceContext && ctx.cookie) {
|
|
106
106
|
sourceContext.parsedCookies = ctx.cookie;
|
|
@@ -42,7 +42,7 @@ module.exports = (core) => {
|
|
|
42
42
|
|
|
43
43
|
// We are getting the sourceContext here because in the time of calling
|
|
44
44
|
// the contrastNext() method the context is lost
|
|
45
|
-
const sourceContext = protect.getSourceContext(
|
|
45
|
+
const sourceContext = protect.getSourceContext();
|
|
46
46
|
|
|
47
47
|
if (!sourceContext) return;
|
|
48
48
|
|
|
@@ -33,7 +33,7 @@ module.exports = (core) => {
|
|
|
33
33
|
patchType,
|
|
34
34
|
post({ args, result }) {
|
|
35
35
|
if (result && Object.keys(result).length) {
|
|
36
|
-
const sourceContext = protect.getSourceContext(
|
|
36
|
+
const sourceContext = protect.getSourceContext();
|
|
37
37
|
|
|
38
38
|
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
39
39
|
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
@@ -32,7 +32,7 @@ module.exports = (core) => {
|
|
|
32
32
|
patchType,
|
|
33
33
|
post({ result }) {
|
|
34
34
|
if (result && Object.keys(result).length) {
|
|
35
|
-
const sourceContext = protect.getSourceContext(
|
|
35
|
+
const sourceContext = protect.getSourceContext();
|
|
36
36
|
|
|
37
37
|
if (sourceContext) {
|
|
38
38
|
sourceContext.parsedCookies = result;
|
|
@@ -130,7 +130,7 @@ module.exports = function(core) {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
inputTracing.nosqlInjectionMongo = function
|
|
133
|
+
inputTracing.nosqlInjectionMongo = function(sourceContext, sinkContext) {
|
|
134
134
|
const ruleId = NOSQL_INJECTION_MONGO;
|
|
135
135
|
const nosqlInjectionMongoResults =
|
|
136
136
|
getResultsByRuleId(ruleId, sourceContext) || [];
|
|
@@ -155,7 +155,7 @@ module.exports = function(core) {
|
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
if (expansionResults) {
|
|
158
|
+
if (expansionResults.length) {
|
|
159
159
|
let expansionFindings = null;
|
|
160
160
|
for (const result of expansionResults) {
|
|
161
161
|
expansionFindings = handleObjectValue(result, sinkContext.value);
|
|
@@ -172,14 +172,14 @@ module.exports = function(core) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
if (stringInjectionResults) {
|
|
175
|
+
if (stringInjectionResults.length) {
|
|
176
176
|
let stringFindings = null;
|
|
177
177
|
|
|
178
178
|
for (const result of stringInjectionResults) {
|
|
179
179
|
if (typeof sinkContext.value === 'object') {
|
|
180
180
|
traverseKeysAndValues(
|
|
181
181
|
sinkContext.value,
|
|
182
|
-
function
|
|
182
|
+
function(_path, type, value, obj) {
|
|
183
183
|
if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
|
|
184
184
|
|
|
185
185
|
if (!stringFindings && type == 'Key' && value == '$accumulator') {
|
|
@@ -221,10 +221,16 @@ module.exports = function(core) {
|
|
|
221
221
|
const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
|
|
222
222
|
|
|
223
223
|
const nosqlInjectionResults = sourceContext.resultsMap[ruleId];
|
|
224
|
+
const isAlreadyPresentInNosqlresults = result.idsList &&
|
|
225
|
+
result.idsList.some(
|
|
226
|
+
(el) =>
|
|
227
|
+
el === agentLibIDListTypes.MONGO_SLEEP ||
|
|
228
|
+
el === agentLibIDListTypes.TRUE_CLAUSE_1
|
|
229
|
+
);
|
|
224
230
|
if (Array.isArray(nosqlInjectionResults)) {
|
|
225
|
-
nosqlInjectionResults.push(nosqlInjectionResult);
|
|
231
|
+
!isAlreadyPresentInNosqlresults && nosqlInjectionResults.push(nosqlInjectionResult);
|
|
226
232
|
} else {
|
|
227
|
-
sourceContext.resultsMap[ruleId] = [nosqlInjectionResult];
|
|
233
|
+
!isAlreadyPresentInNosqlresults && (sourceContext.resultsMap[ruleId] = [nosqlInjectionResult]);
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
handleFindings(
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const { isString } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
module.exports = function(core) {
|
|
21
|
+
module.exports = function (core) {
|
|
22
22
|
const {
|
|
23
23
|
scopes: { instrumentation },
|
|
24
24
|
patcher,
|
|
@@ -29,7 +29,7 @@ module.exports = function(core) {
|
|
|
29
29
|
function pre({ args, name, hooked, orig }) {
|
|
30
30
|
if (instrumentation.isLocked()) return;
|
|
31
31
|
|
|
32
|
-
const sourceContext = getSourceContext(
|
|
32
|
+
const sourceContext = getSourceContext();
|
|
33
33
|
const value = args[0];
|
|
34
34
|
|
|
35
35
|
if (!sourceContext || !value || !isString(value)) return;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const { isString } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
module.exports = function(core) {
|
|
21
|
+
module.exports = function (core) {
|
|
22
22
|
const {
|
|
23
23
|
logger,
|
|
24
24
|
scopes: { instrumentation },
|
|
@@ -39,7 +39,7 @@ module.exports = function(core) {
|
|
|
39
39
|
pre: ({ args, hooked, orig }) => {
|
|
40
40
|
if (instrumentation.isLocked()) return;
|
|
41
41
|
|
|
42
|
-
const sourceContext = protect.getSourceContext(
|
|
42
|
+
const sourceContext = protect.getSourceContext();
|
|
43
43
|
const value = args[0];
|
|
44
44
|
|
|
45
45
|
if (!sourceContext || !value || !isString(value)) return;
|
|
@@ -34,7 +34,7 @@ module.exports = function init(core) {
|
|
|
34
34
|
if (core.scopes.instrumentation.isLocked()) return;
|
|
35
35
|
|
|
36
36
|
// obtain the Protect sourceContext
|
|
37
|
-
const sourceContext = core.protect.getSourceContext(
|
|
37
|
+
const sourceContext = core.protect.getSourceContext();
|
|
38
38
|
|
|
39
39
|
if (!sourceContext) return;
|
|
40
40
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const { isString } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
module.exports = function(core) {
|
|
21
|
+
module.exports = function (core) {
|
|
22
22
|
const {
|
|
23
23
|
logger,
|
|
24
24
|
scopes: { instrumentation },
|
|
@@ -40,7 +40,7 @@ module.exports = function(core) {
|
|
|
40
40
|
pre: ({ args, hooked, orig }) => {
|
|
41
41
|
if (instrumentation.isLocked()) return;
|
|
42
42
|
|
|
43
|
-
const sourceContext = protect.getSourceContext(
|
|
43
|
+
const sourceContext = protect.getSourceContext();
|
|
44
44
|
const fnBody = args[args.length - 1];
|
|
45
45
|
|
|
46
46
|
if (!sourceContext || !fnBody || !isString(fnBody)) return;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
const { patchType } = require('../constants');
|
|
18
18
|
|
|
19
|
-
module.exports = function
|
|
19
|
+
module.exports = function(core) {
|
|
20
20
|
const {
|
|
21
21
|
depHooks,
|
|
22
22
|
patcher,
|
|
@@ -32,11 +32,11 @@ module.exports = function (core) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (Object.prototype.toString.call(query) === '[object String]') {
|
|
35
|
-
return query
|
|
35
|
+
return query;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (query['$where']) {
|
|
39
|
-
return query['$where']
|
|
39
|
+
return query['$where'];
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return query;
|
|
@@ -14,224 +14,219 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
|
-
const
|
|
18
|
-
const semver = require('semver');
|
|
17
|
+
const { isNonEmptyObject, isString, traverseValues, traverseKeys } = require('@contrast/common');
|
|
19
18
|
const { patchType } = require('../constants');
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
const collectionMethods = [
|
|
21
|
+
'find',
|
|
22
|
+
'findOne',
|
|
23
|
+
'findAndModify',
|
|
24
|
+
'findOneAndDelete',
|
|
25
|
+
'findOneAndReplace',
|
|
26
|
+
'findOneAndUpdate',
|
|
27
|
+
'remove',
|
|
28
|
+
'replaceOne',
|
|
29
|
+
'update',
|
|
30
|
+
'updateOne',
|
|
31
|
+
'updateMany',
|
|
32
|
+
'deleteOne',
|
|
33
|
+
'deleteMany',
|
|
34
|
+
'aggregate',
|
|
35
|
+
'mapReduce',
|
|
36
|
+
'group'
|
|
37
|
+
];
|
|
38
|
+
const dbMethods = [
|
|
39
|
+
'command',
|
|
40
|
+
'eval',
|
|
41
|
+
'aggregate'
|
|
42
|
+
];
|
|
43
|
+
// const methodsWithNestedCalls = ['findOne', 'eval', 'group'];
|
|
44
|
+
|
|
45
|
+
module.exports = function(core) {
|
|
22
46
|
const {
|
|
47
|
+
logger,
|
|
48
|
+
patcher,
|
|
49
|
+
depHooks,
|
|
50
|
+
scopes: { instrumentation },
|
|
23
51
|
protect: { getSourceContext, inputTracing },
|
|
24
|
-
instrumentation: { instrument }
|
|
25
52
|
} = core;
|
|
26
53
|
|
|
27
|
-
|
|
28
|
-
const query = semver.gte(version, '3.3.0')
|
|
29
|
-
? args[0]?.cmd?.query || args[1]?.query
|
|
30
|
-
: args[1]?.query;
|
|
54
|
+
const instr = inputTracing.mongodbInstrumentation = {};
|
|
31
55
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
56
|
+
instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(value, _argIdx, sourceContext, metadata) {
|
|
57
|
+
if (!isString(value) && !isNonEmptyObject(value)) return;
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
const { name, hooked, orig } = metadata;
|
|
60
|
+
const sinkContext = {
|
|
61
|
+
name,
|
|
62
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
63
|
+
};
|
|
39
64
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
65
|
+
if (name === 'mongodb.Db.prototype.command' && value?.filter) {
|
|
66
|
+
value = value.filter;
|
|
42
67
|
}
|
|
43
68
|
|
|
44
|
-
|
|
45
|
-
}
|
|
69
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value });
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
instr.getAggregateVulnerabilityInfo = function getAggregateVulnerabilityInfo(aggregation, _argIdx, sourceContext, metadata) {
|
|
73
|
+
if (!isNonEmptyObject(aggregation)) return;
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
75
|
+
const { name, hooked, orig } = metadata;
|
|
76
|
+
const sinkContext = {
|
|
77
|
+
name,
|
|
78
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
traverseValues(aggregation, (path, _type, value) => {
|
|
82
|
+
const accumulatorFuncProps = [
|
|
83
|
+
'init',
|
|
84
|
+
'merge',
|
|
85
|
+
'accumulate',
|
|
86
|
+
'finalize'
|
|
87
|
+
];
|
|
88
|
+
const lastIdx = path.length - 1;
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
(path[lastIdx - 1] === '$function' && path[lastIdx] === 'body') ||
|
|
92
|
+
(path[lastIdx - 1] === '$accumulator' && accumulatorFuncProps.includes(path[lastIdx]))
|
|
93
|
+
) {
|
|
94
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
instr.getMapReduceVulnerabilityInfo = function getMapReduceVulnerabilityInfo(argToCheck, argIdx, sourceContext, metadata) {
|
|
100
|
+
const { name, hooked, orig } = metadata;
|
|
101
|
+
const sinkContext = {
|
|
102
|
+
name,
|
|
103
|
+
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (argIdx !== 2 && isString(argToCheck)) {
|
|
107
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
|
|
49
108
|
return;
|
|
50
109
|
}
|
|
51
|
-
if (op.q.$where) {
|
|
52
|
-
return op.q.$where;
|
|
53
|
-
}
|
|
54
|
-
return op.q;
|
|
55
|
-
}
|
|
56
110
|
|
|
57
|
-
|
|
58
|
-
const sourceContext = getSourceContext(name);
|
|
111
|
+
if (!isNonEmptyObject(argToCheck)) return;
|
|
59
112
|
|
|
60
|
-
|
|
113
|
+
traverseKeys(argToCheck, (_path, _type, key) => {
|
|
114
|
+
const vulnerableProps = [
|
|
115
|
+
'query',
|
|
116
|
+
'finalize',
|
|
117
|
+
];
|
|
118
|
+
if (vulnerableProps.includes(key)) {
|
|
119
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck[key] });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
61
123
|
|
|
124
|
+
instr.getGroupVulnerabilityInfo = function getGroupVulnerabilityInfo(argToCheck, argIdx, sourceContext, metadata) {
|
|
125
|
+
const { name, hooked, orig } = metadata;
|
|
62
126
|
const sinkContext = {
|
|
63
127
|
name,
|
|
64
|
-
value,
|
|
65
128
|
stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
|
|
66
129
|
};
|
|
67
|
-
inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
|
|
68
|
-
}
|
|
69
130
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
131
|
+
if (argIdx !== 1 && isString(argToCheck)) {
|
|
132
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
74
135
|
|
|
75
|
-
|
|
76
|
-
const value = args[0]?.filter;
|
|
77
|
-
preHook(value, ctx);
|
|
78
|
-
}
|
|
136
|
+
if (!isNonEmptyObject(argToCheck)) return;
|
|
79
137
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
preHook(value, ctx);
|
|
83
|
-
}
|
|
138
|
+
inputTracing.nosqlInjectionMongo(sourceContext, { ...sinkContext, value: argToCheck });
|
|
139
|
+
};
|
|
84
140
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
141
|
+
function createAroundHook(getInfoMethod, vulnerableArgIdxs) {
|
|
142
|
+
const argsIdxsToCheck = vulnerableArgIdxs || [0];
|
|
143
|
+
return function(next, data) {
|
|
144
|
+
const { args, name, hooked, orig } = data;
|
|
145
|
+
const sourceCtx = getSourceContext(name);
|
|
146
|
+
|
|
147
|
+
if (instrumentation.isLocked() || !sourceCtx) {
|
|
148
|
+
return next();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const argIdx of argsIdxsToCheck) {
|
|
152
|
+
getInfoMethod(args[argIdx], argIdx, sourceCtx, { name, hooked, orig });
|
|
153
|
+
}
|
|
89
154
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
return next();
|
|
156
|
+
// return methodsWithNestedCalls.includes(method) ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
|
|
157
|
+
};
|
|
93
158
|
}
|
|
94
159
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
instr.install = function() {
|
|
161
|
+
depHooks.resolve({ name: 'mongodb' }, (mongodb, version) => {
|
|
162
|
+
patchCollection(mongodb, version);
|
|
163
|
+
patchDatabase(mongodb, version);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function patchCollection(mongodb, version) {
|
|
168
|
+
const proto = mongodb.Collection.prototype;
|
|
169
|
+
for (const method of collectionMethods) {
|
|
170
|
+
const name = `mongodb.Collection.prototype.${method}`;
|
|
171
|
+
|
|
172
|
+
if (!proto[method]) {
|
|
173
|
+
logger.trace({ name, version }, 'method not found - skipping instrumentation');
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (method === 'aggregate') {
|
|
178
|
+
patcher.patch(proto, method, {
|
|
179
|
+
name,
|
|
180
|
+
patchType,
|
|
181
|
+
around: createAroundHook(instr.getAggregateVulnerabilityInfo)
|
|
182
|
+
});
|
|
183
|
+
} else if (method === 'mapReduce') {
|
|
184
|
+
patcher.patch(proto, method, {
|
|
185
|
+
name,
|
|
186
|
+
patchType,
|
|
187
|
+
around: createAroundHook(instr.getMapReduceVulnerabilityInfo, [0, 1, 2])
|
|
188
|
+
});
|
|
189
|
+
} else if (method === 'group') {
|
|
190
|
+
patcher.patch(proto, method, {
|
|
191
|
+
name,
|
|
192
|
+
patchType,
|
|
193
|
+
around: createAroundHook(instr.getGroupVulnerabilityInfo, [0, 1, 3])
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
patcher.patch(proto, method, {
|
|
197
|
+
name,
|
|
198
|
+
patchType,
|
|
199
|
+
around: createAroundHook(instr.getQueryVulnerabilityInfo)
|
|
200
|
+
});
|
|
101
201
|
}
|
|
102
202
|
}
|
|
103
203
|
}
|
|
104
204
|
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
],
|
|
130
|
-
patchType,
|
|
131
|
-
preHookFn: CollectionVal
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'Db.prototype',
|
|
135
|
-
methods: ['command'],
|
|
136
|
-
patchType,
|
|
137
|
-
preHookFn: dbCommandVal
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
moduleName,
|
|
143
|
-
version: '>=4.0.0',
|
|
144
|
-
file: 'lib/cursor/find_cursor',
|
|
145
|
-
patchObjects: [
|
|
146
|
-
{
|
|
147
|
-
methods: ['FindCursor'],
|
|
148
|
-
patchType,
|
|
149
|
-
preHookFn: v4CursorVal
|
|
150
|
-
}
|
|
151
|
-
]
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
moduleName,
|
|
155
|
-
version: '<4.0.0',
|
|
156
|
-
patchObjects: [
|
|
157
|
-
{
|
|
158
|
-
name: 'CoreServer.prototype',
|
|
159
|
-
methods: ['cursor'],
|
|
160
|
-
patchType,
|
|
161
|
-
preHookFn: v3CursorVal
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: 'Db.prototype',
|
|
165
|
-
methods: ['command'],
|
|
166
|
-
patchType,
|
|
167
|
-
preHookFn: dbCommandVal
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
name: 'Db.prototype',
|
|
171
|
-
methods: ['eval'],
|
|
172
|
-
patchType,
|
|
173
|
-
preHookFn: firstArgVal
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: 'Collection.prototype',
|
|
177
|
-
methods: ['find'],
|
|
178
|
-
patchType,
|
|
179
|
-
preHookFn: firstArgVal
|
|
180
|
-
},
|
|
181
|
-
]
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
moduleName,
|
|
185
|
-
file: 'lib/topologies/topology_base.js',
|
|
186
|
-
version: '<4.0.0 >=3.0.0',
|
|
187
|
-
patchObjects: [
|
|
188
|
-
{
|
|
189
|
-
name: 'TopologyBase.prototype',
|
|
190
|
-
methods: ['update', 'remove'],
|
|
191
|
-
patchType,
|
|
192
|
-
preHookFn: v3TopologyVal
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'TopologyBase.prototype',
|
|
196
|
-
methods: ['command'],
|
|
197
|
-
patchType,
|
|
198
|
-
preHookFn: v3CursorVal
|
|
199
|
-
}
|
|
200
|
-
]
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
moduleName,
|
|
204
|
-
file: 'lib/topologies/native_topology.js',
|
|
205
|
-
version: '<4.0.0 >=3.0.0',
|
|
206
|
-
patchObjects: [
|
|
207
|
-
{
|
|
208
|
-
name: 'NativeTopology.prototype',
|
|
209
|
-
patchName: 'prototype',
|
|
210
|
-
methods: ['update', 'remove'],
|
|
211
|
-
patchType,
|
|
212
|
-
preHookFn: v3TopologyVal
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: 'NativeTopology.prototype',
|
|
216
|
-
patchName: 'prototype',
|
|
217
|
-
methods: ['command'],
|
|
218
|
-
patchType,
|
|
219
|
-
preHookFn: v3CursorVal
|
|
220
|
-
}
|
|
221
|
-
]
|
|
222
|
-
},
|
|
223
|
-
].forEach(({ moduleName, file, version, patchObjects }) => {
|
|
224
|
-
instrument({
|
|
225
|
-
moduleName,
|
|
226
|
-
file,
|
|
227
|
-
version,
|
|
228
|
-
patchObjects
|
|
229
|
-
});
|
|
230
|
-
});
|
|
205
|
+
function patchDatabase(mongodb, version) {
|
|
206
|
+
const proto = mongodb.Db.prototype;
|
|
207
|
+
for (const method of dbMethods) {
|
|
208
|
+
const name = `mongodb.Db.prototype.${method}`;
|
|
209
|
+
|
|
210
|
+
if (!proto[method]) {
|
|
211
|
+
logger.trace({ name, version }, 'method not found - skipping instrumentation');
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (method === 'aggregate') {
|
|
216
|
+
patcher.patch(proto, method, {
|
|
217
|
+
name,
|
|
218
|
+
patchType,
|
|
219
|
+
around: createAroundHook(instr.getAggregateVulnerabilityInfo)
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
patcher.patch(proto, method, {
|
|
223
|
+
name,
|
|
224
|
+
patchType,
|
|
225
|
+
around: createAroundHook(instr.getQueryVulnerabilityInfo)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
231
229
|
}
|
|
232
|
-
const mongodbInstr = (core.protect.inputTracing.mongodbInstrumentation = {
|
|
233
|
-
install,
|
|
234
|
-
});
|
|
235
230
|
|
|
236
|
-
return
|
|
231
|
+
return instr;
|
|
237
232
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Protect support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@contrast/agent-lib": "^7.0.1",
|
|
21
21
|
"@contrast/common": "1.15.1",
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/esm-hooks": "1.
|
|
22
|
+
"@contrast/core": "1.24.0",
|
|
23
|
+
"@contrast/esm-hooks": "1.20.0",
|
|
24
24
|
"@contrast/scopes": "1.4.0",
|
|
25
25
|
"ipaddr.js": "^2.0.1",
|
|
26
26
|
"semver": "^7.3.7"
|