@contrast/agent 4.8.0 → 4.10.1
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/bin/VERSION +1 -1
- package/bin/linux/contrast-service +0 -0
- package/bin/mac/contrast-service +0 -0
- package/bin/windows/contrast-service.exe +0 -0
- package/bootstrap.js +12 -2
- package/esm.mjs +33 -0
- package/lib/assess/index.js +2 -0
- package/lib/assess/models/source-event.js +6 -0
- package/lib/assess/policy/rules.json +29 -0
- package/lib/assess/policy/signatures.json +6 -0
- package/lib/assess/propagators/JSON/stringify.js +77 -7
- package/lib/assess/propagators/joi/any.js +48 -0
- package/lib/assess/propagators/joi/index.js +2 -0
- package/lib/assess/propagators/joi/object.js +61 -0
- package/lib/assess/propagators/joi/string-base.js +16 -0
- package/lib/assess/propagators/mongoose/helpers.js +36 -1
- package/lib/assess/propagators/mongoose/index.js +1 -0
- package/lib/assess/propagators/mongoose/map.js +19 -31
- package/lib/assess/propagators/mongoose/mixed.js +71 -0
- package/lib/assess/propagators/mongoose/string.js +8 -0
- package/lib/assess/sinks/rethinkdb-nosql-injection.js +142 -0
- package/lib/assess/sources/event-handler.js +307 -0
- package/lib/assess/sources/index.js +93 -5
- package/lib/assess/spdy/index.js +23 -0
- package/lib/assess/spdy/sinks/index.js +23 -0
- package/lib/assess/spdy/sinks/xss.js +84 -0
- package/lib/assess/technologies/index.js +2 -1
- package/lib/constants.js +2 -1
- package/lib/contrast.js +6 -6
- package/lib/core/arch-components/index.js +1 -0
- package/lib/core/arch-components/mongodb.js +22 -18
- package/lib/core/arch-components/mysql.js +25 -15
- package/lib/core/arch-components/postgres.js +40 -12
- package/lib/core/arch-components/sqlite3.js +3 -5
- package/lib/core/arch-components/util.js +49 -0
- package/lib/core/config/options.js +37 -1
- package/lib/core/exclusions/exclusion.js +2 -5
- package/lib/core/express/index.js +28 -2
- package/lib/core/express/utils.js +8 -3
- package/lib/core/fastify/index.js +2 -1
- package/lib/core/hapi/index.js +2 -1
- package/lib/core/koa/index.js +9 -1
- package/lib/core/rewrite/callees.js +16 -0
- package/lib/core/rewrite/import-declaration.js +71 -0
- package/lib/core/rewrite/index.js +9 -7
- package/lib/core/rewrite/injections.js +5 -1
- package/lib/hooks/frameworks/index.js +2 -0
- package/lib/hooks/frameworks/spdy.js +87 -0
- package/lib/hooks/http.js +11 -0
- package/lib/protect/restify/sources.js +35 -0
- package/lib/protect/rules/nosqli/nosql-injection-rule.js +30 -16
- package/lib/protect/rules/nosqli/nosql-scanner/index.js +1 -1
- package/lib/protect/rules/nosqli/nosql-scanner/rethinkdbscanner.js +26 -0
- package/lib/protect/sinks/index.js +2 -0
- package/lib/protect/sinks/mongodb.js +1 -3
- package/lib/protect/sinks/rethinkdb.js +47 -0
- package/lib/reporter/translations/to-protobuf/dtm/trace-event/index.js +4 -4
- package/lib/util/source-map.js +3 -3
- package/package.json +18 -12
package/bin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.28.
|
|
1
|
+
2.28.9
|
|
Binary file
|
package/bin/mac/contrast-service
CHANGED
|
Binary file
|
|
Binary file
|
package/bootstrap.js
CHANGED
|
@@ -26,7 +26,16 @@ const orig = Module.runMain;
|
|
|
26
26
|
* script from cli
|
|
27
27
|
*/
|
|
28
28
|
Module.runMain = async function(...args) {
|
|
29
|
+
const { isMainThread } = require('worker_threads');
|
|
30
|
+
|
|
29
31
|
try {
|
|
32
|
+
// Skip instrumenting worker threads.
|
|
33
|
+
if (!isMainThread) {
|
|
34
|
+
orig.apply(this, args);
|
|
35
|
+
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
const { enabled } = await loader.init(process.argv);
|
|
31
40
|
if (enabled) {
|
|
32
41
|
await loader.bootstrap(process.argv);
|
|
@@ -37,9 +46,10 @@ Module.runMain = async function(...args) {
|
|
|
37
46
|
orig.apply(this, args);
|
|
38
47
|
loader.logTime(appStartTime, 'application');
|
|
39
48
|
loader.logTime(startTime, 'agent & application');
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
42
51
|
console.error(err);
|
|
52
|
+
// eslint-disable-next-line no-process-exit
|
|
43
53
|
process.exit(-1);
|
|
44
54
|
}
|
|
45
55
|
};
|
package/esm.mjs
CHANGED
|
@@ -22,6 +22,7 @@ if (enabled) {
|
|
|
22
22
|
await loader.bootstrap(process.argv);
|
|
23
23
|
}
|
|
24
24
|
await loader.resetArgs(process.argv[0], process.argv[1]);
|
|
25
|
+
const { readFile } = require('fs').promises;
|
|
25
26
|
|
|
26
27
|
const agent = require(`./lib/agent.js`);
|
|
27
28
|
const logger = require(`./lib/core/logger/index.js`)('contrast:esm-loaders');
|
|
@@ -95,3 +96,35 @@ export async function transformSource(source, context, defaultTransformSource) {
|
|
|
95
96
|
return defaultTransformSource(source, context, defaultTransformSource);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* For Node 16 and above, the 'getFormat', 'getSource' and 'transformSource' have been
|
|
102
|
+
* consolidated into one 'load' hook. The logic is similar to that of transformSource
|
|
103
|
+
* except that the source is not provided and must be either read in from the file provided
|
|
104
|
+
* or accessed from the cache.
|
|
105
|
+
*
|
|
106
|
+
* @see https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#loadurl-context-defaultload
|
|
107
|
+
* @param {string} url
|
|
108
|
+
* @param {{ format: string, url: string }} context
|
|
109
|
+
* @param {Function} defaultLoad
|
|
110
|
+
* @returns {Promise<{ format: string, source: string | SharedArrayBuffer | Uint8Array }>}
|
|
111
|
+
*/
|
|
112
|
+
export async function load(url, context, defaultLoad) {
|
|
113
|
+
const filename = fileURLToPath(url);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const cached = helpers.find(agent, filename);
|
|
117
|
+
const source = cached || await readFile(filename, 'utf8');
|
|
118
|
+
const result = rewriter.rewriteFile(source, filename, { sourceType: 'module' });
|
|
119
|
+
helpers.cacheWithSourceMap(agent, filename, result);
|
|
120
|
+
return { format: context.format, source: result.code };
|
|
121
|
+
|
|
122
|
+
} catch (err) {
|
|
123
|
+
logger.error(
|
|
124
|
+
'Failed to load rewritten code for %s, err: %o, rewritten code %s, loading original code.',
|
|
125
|
+
filename,
|
|
126
|
+
err
|
|
127
|
+
);
|
|
128
|
+
return defaultLoad(url, context, defaultLoad);
|
|
129
|
+
}
|
|
130
|
+
}
|
package/lib/assess/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const KoaFrameworkInstrumentation = require('./koa');
|
|
|
20
20
|
const HapiFrameworkInstrumentation = require('./hapi');
|
|
21
21
|
const Loopback4FrameworkInstrumentation = require('./loopback4');
|
|
22
22
|
const ExpressFrameworkInstrumentation = require('./express');
|
|
23
|
+
const SpdyFrameworkInstrumentation = require('./spdy');
|
|
23
24
|
const assessSources = require('./sources');
|
|
24
25
|
|
|
25
26
|
module.exports = function(agent) {
|
|
@@ -29,5 +30,6 @@ module.exports = function(agent) {
|
|
|
29
30
|
new HapiFrameworkInstrumentation(agent);
|
|
30
31
|
new Loopback4FrameworkInstrumentation(agent);
|
|
31
32
|
new ExpressFrameworkInstrumentation(agent);
|
|
33
|
+
new SpdyFrameworkInstrumentation(agent);
|
|
32
34
|
assessSources.init(agent);
|
|
33
35
|
};
|
|
@@ -32,6 +32,7 @@ module.exports = class SourceEvent extends BaseEvent {
|
|
|
32
32
|
* @param {boolean} opts.isArray true if req.body is an array (from JSON body).
|
|
33
33
|
*/
|
|
34
34
|
constructor({
|
|
35
|
+
code,
|
|
35
36
|
context,
|
|
36
37
|
name,
|
|
37
38
|
signature,
|
|
@@ -43,6 +44,7 @@ module.exports = class SourceEvent extends BaseEvent {
|
|
|
43
44
|
eventSources = [{ sourceType: type.toUpperCase(), sourceName: name }]
|
|
44
45
|
} = {}) {
|
|
45
46
|
super(context, signature, tagRanges);
|
|
47
|
+
this.code = code;
|
|
46
48
|
this.target = target;
|
|
47
49
|
this.eventSources = eventSources;
|
|
48
50
|
this.parents = [];
|
|
@@ -65,6 +67,10 @@ module.exports = class SourceEvent extends BaseEvent {
|
|
|
65
67
|
mapCustomMethods() {
|
|
66
68
|
const type = this.eventSources[0].sourceType.toLowerCase();
|
|
67
69
|
const name = this.eventSources[0].sourceName;
|
|
70
|
+
if (this.code) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
if (this.isRequestObject) {
|
|
69
75
|
this.code = `const ${type} = req.${type}`;
|
|
70
76
|
} else if (this.isArray) {
|
|
@@ -1376,6 +1376,11 @@
|
|
|
1376
1376
|
"type": "http",
|
|
1377
1377
|
"provider": "./sinks/dustjs-linkedin-xss"
|
|
1378
1378
|
},
|
|
1379
|
+
"nosql-injection-rethinkdb": {
|
|
1380
|
+
"enabled": true,
|
|
1381
|
+
"type": "http",
|
|
1382
|
+
"provider": "./sinks/rethinkdb-nosql-injection"
|
|
1383
|
+
},
|
|
1379
1384
|
"reflected-xss": {
|
|
1380
1385
|
"enabled": true,
|
|
1381
1386
|
"type": "hook",
|
|
@@ -1404,6 +1409,30 @@
|
|
|
1404
1409
|
]
|
|
1405
1410
|
}
|
|
1406
1411
|
},
|
|
1412
|
+
"express.response.push": {
|
|
1413
|
+
"type": "dataflow",
|
|
1414
|
+
"enabled": true,
|
|
1415
|
+
"stackTrustedLibs": [],
|
|
1416
|
+
"conditions": {
|
|
1417
|
+
"mode": "or",
|
|
1418
|
+
"args": [
|
|
1419
|
+
{
|
|
1420
|
+
"index": 0,
|
|
1421
|
+
"disallowedTags": [
|
|
1422
|
+
"cookie",
|
|
1423
|
+
"header",
|
|
1424
|
+
"limited-chars",
|
|
1425
|
+
"alphanum-space-hyphen",
|
|
1426
|
+
"html-encoded",
|
|
1427
|
+
"sql-encoded",
|
|
1428
|
+
"url-encoded",
|
|
1429
|
+
"weak-url-encoded"
|
|
1430
|
+
],
|
|
1431
|
+
"requiredTags": ["untrusted"]
|
|
1432
|
+
}
|
|
1433
|
+
]
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1407
1436
|
"swig.compile": {
|
|
1408
1437
|
"type": "dataflow",
|
|
1409
1438
|
"enabled": true,
|
|
@@ -254,6 +254,12 @@
|
|
|
254
254
|
"methodName": "response.send",
|
|
255
255
|
"isModule": true
|
|
256
256
|
},
|
|
257
|
+
"express.response.push": {
|
|
258
|
+
"moduleName": "express",
|
|
259
|
+
"version": ">=4.0.0",
|
|
260
|
+
"methodName": "response.push",
|
|
261
|
+
"isModule": true
|
|
262
|
+
},
|
|
257
263
|
"express.response.set": {
|
|
258
264
|
"moduleName": "express",
|
|
259
265
|
"version": ">=4.0.0",
|
|
@@ -23,10 +23,16 @@ const crypto = require('crypto');
|
|
|
23
23
|
const TagRange = require('../../models/tag-range');
|
|
24
24
|
const tracker = require('../../../tracker.js');
|
|
25
25
|
const { isString } = require('../../../util/is-string');
|
|
26
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
PropagationEvent,
|
|
28
|
+
Signature,
|
|
29
|
+
CallContext,
|
|
30
|
+
SourceEvent
|
|
31
|
+
} = require('../../models');
|
|
27
32
|
const ContrastJSON = require('../../../core/rewrite/injections').get('JSON');
|
|
28
33
|
const Debraner = require('../../membrane/debraner');
|
|
29
34
|
const { PROXY_TARGET } = require('../../../constants');
|
|
35
|
+
const { trackedObjects } = require('../../sources/event-handler');
|
|
30
36
|
|
|
31
37
|
const sig = new Signature({
|
|
32
38
|
moduleName: 'JSON',
|
|
@@ -125,6 +131,7 @@ function patchReplacer(replacer) {
|
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
let patched = false;
|
|
134
|
+
|
|
128
135
|
module.exports.handle = function() {
|
|
129
136
|
if (patched) {
|
|
130
137
|
return;
|
|
@@ -136,7 +143,7 @@ module.exports.handle = function() {
|
|
|
136
143
|
patchType: PATCH_TYPES.ASSESS_PROPAGATOR,
|
|
137
144
|
|
|
138
145
|
pre(data) {
|
|
139
|
-
const [, , space] = data.args;
|
|
146
|
+
const [obj, , space] = data.args;
|
|
140
147
|
let [input, replacer] = data.args;
|
|
141
148
|
replacer = patchReplacer(replacer);
|
|
142
149
|
|
|
@@ -148,11 +155,24 @@ module.exports.handle = function() {
|
|
|
148
155
|
spaceProps: undefined,
|
|
149
156
|
target: undefined,
|
|
150
157
|
membrane: undefined,
|
|
151
|
-
debraner: undefined
|
|
158
|
+
debraner: undefined,
|
|
159
|
+
sourcesMap: {}
|
|
152
160
|
};
|
|
153
161
|
|
|
162
|
+
if (trackedObjects.has(obj)) {
|
|
163
|
+
const { type } = trackedObjects.get(obj);
|
|
164
|
+
data.metadata.sourcesMap[type] = obj;
|
|
165
|
+
}
|
|
166
|
+
|
|
154
167
|
// is the argument behind a membrane? try getting { target, membrane }.
|
|
155
168
|
const braned = input && input[PROXY_TARGET];
|
|
169
|
+
|
|
170
|
+
const isSourceObject = input && trackedObjects.has(input);
|
|
171
|
+
|
|
172
|
+
if (isSourceObject) {
|
|
173
|
+
data.metadata.propagate = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
// next two are used by the replacer function
|
|
157
177
|
let safeKeys;
|
|
158
178
|
if (braned) {
|
|
@@ -183,7 +203,15 @@ module.exports.handle = function() {
|
|
|
183
203
|
* @param {string} key
|
|
184
204
|
* @param {string} value
|
|
185
205
|
*/
|
|
206
|
+
// eslint-disable-next-line complexity
|
|
186
207
|
function contrastReplacer(key, val) {
|
|
208
|
+
if (trackedObjects.has(obj)) {
|
|
209
|
+
const { type } = trackedObjects.get(obj);
|
|
210
|
+
if (!data.metadata.sourcesMap[type]) {
|
|
211
|
+
data.metadata.sourcesMap[type] = obj;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
187
215
|
let isTracked = false;
|
|
188
216
|
const valProperties = tracker.getData(val);
|
|
189
217
|
if (valProperties && valProperties.tagRanges.length) {
|
|
@@ -206,7 +234,7 @@ module.exports.handle = function() {
|
|
|
206
234
|
// make skeletal version of valProperties with only the tagRanges prop.
|
|
207
235
|
data.metadata[id] =
|
|
208
236
|
// eslint-disable-next-line prettier/prettier
|
|
209
|
-
|
|
237
|
+
{ tagRanges: [new TagRange(0, value.length - 1, '')] };
|
|
210
238
|
value = `${canary}${id}~${value}`;
|
|
211
239
|
id++;
|
|
212
240
|
}
|
|
@@ -229,6 +257,7 @@ module.exports.handle = function() {
|
|
|
229
257
|
if (!data.metadata.propagate) {
|
|
230
258
|
return data.result;
|
|
231
259
|
}
|
|
260
|
+
|
|
232
261
|
// restore the proxies to the argument
|
|
233
262
|
const { debraner } = data.metadata;
|
|
234
263
|
if (debraner) {
|
|
@@ -262,6 +291,41 @@ module.exports.handle = function() {
|
|
|
262
291
|
}
|
|
263
292
|
data.result = tracked.str;
|
|
264
293
|
const { props } = tracked;
|
|
294
|
+
|
|
295
|
+
const customSources = [];
|
|
296
|
+
|
|
297
|
+
Object.keys(data.metadata.sourcesMap).forEach((k) => {
|
|
298
|
+
const objData = trackedObjects.get(data.metadata.sourcesMap[k]);
|
|
299
|
+
|
|
300
|
+
const { type, path, stackOpts } = objData;
|
|
301
|
+
|
|
302
|
+
const obj = data.metadata.sourcesMap[type];
|
|
303
|
+
|
|
304
|
+
customSources.push(
|
|
305
|
+
new SourceEvent({
|
|
306
|
+
context: new CallContext({
|
|
307
|
+
obj,
|
|
308
|
+
args: data.args,
|
|
309
|
+
result: data.result,
|
|
310
|
+
stackOpts
|
|
311
|
+
}),
|
|
312
|
+
signature: new Signature({
|
|
313
|
+
moduleName: 'http.req',
|
|
314
|
+
methodName: 'on.end',
|
|
315
|
+
args: undefined,
|
|
316
|
+
return: undefined,
|
|
317
|
+
isModule: false
|
|
318
|
+
}),
|
|
319
|
+
tagRanges,
|
|
320
|
+
source: 'A',
|
|
321
|
+
target: 'R',
|
|
322
|
+
name: path,
|
|
323
|
+
type,
|
|
324
|
+
isRequestObject: true
|
|
325
|
+
})
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
265
329
|
props.tagRanges = tagRanges;
|
|
266
330
|
// restore the original arguments so reporting shows user's signature.
|
|
267
331
|
data.args = metadata.origArgs;
|
|
@@ -270,8 +334,12 @@ module.exports.handle = function() {
|
|
|
270
334
|
// always exist, even if an empty array, for production code.
|
|
271
335
|
if (metadata.sourceEvents && !metadata.sourceEvents.length) {
|
|
272
336
|
// eslint-disable-next-line prettier/prettier
|
|
273
|
-
const { sourceEvents, braned
|
|
274
|
-
|
|
337
|
+
const { sourceEvents, braned } = data.metadata;
|
|
338
|
+
if (braned) {
|
|
339
|
+
sourceEvents.push(
|
|
340
|
+
braned.membrane.makeReqSourceEvent(data.result.length)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
275
343
|
}
|
|
276
344
|
|
|
277
345
|
props.event = new PropagationEvent({
|
|
@@ -280,7 +348,9 @@ module.exports.handle = function() {
|
|
|
280
348
|
tagRanges: props.tagRanges,
|
|
281
349
|
source: 'P',
|
|
282
350
|
target: 'R',
|
|
283
|
-
parents:
|
|
351
|
+
parents: customSources.length
|
|
352
|
+
? customSources
|
|
353
|
+
: data.metadata.sourceEvents
|
|
284
354
|
});
|
|
285
355
|
|
|
286
356
|
return data.result;
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const requireHook = require('../../../hooks/require');
|
|
16
|
+
const patcher = require('../../../hooks/patcher');
|
|
17
|
+
const {
|
|
18
|
+
PATCH_TYPES: { ASSESS_PROPAGATOR }
|
|
19
|
+
} = require('../../../constants');
|
|
20
|
+
const tracker = require('../../../tracker');
|
|
21
|
+
const agent = require('../../../agent');
|
|
22
|
+
|
|
23
|
+
requireHook.resolve(
|
|
24
|
+
{ name: 'joi', file: 'lib/types/any.js', version: '>=17.0.0' },
|
|
25
|
+
(object) => {
|
|
26
|
+
if (
|
|
27
|
+
!agent.config ||
|
|
28
|
+
(agent.config && !agent.config.agent.trust_custom_validators)
|
|
29
|
+
) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
patcher.patch(object._definition.rules.custom, 'validate', {
|
|
34
|
+
name: 'joi.any.custom.validate',
|
|
35
|
+
patchType: ASSESS_PROPAGATOR,
|
|
36
|
+
alwaysRun: true,
|
|
37
|
+
post(data) {
|
|
38
|
+
if (data.result && typeof data.result === 'string') {
|
|
39
|
+
tracker.untrack(data.result);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (data.args[0] && typeof data.args[0] === 'string') {
|
|
43
|
+
tracker.untrack(data.args[0]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
);
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
const requireHook = require('../../../hooks/require');
|
|
16
|
+
const patcher = require('../../../hooks/patcher');
|
|
17
|
+
const {
|
|
18
|
+
PATCH_TYPES: { ASSESS_PROPAGATOR }
|
|
19
|
+
} = require('../../../constants');
|
|
20
|
+
const tracker = require('../../../tracker');
|
|
21
|
+
const agent = require('../../../agent');
|
|
22
|
+
|
|
23
|
+
const traverseObjectAndUntrack = (object, incomingInput, result) => {
|
|
24
|
+
if (object.type === 'object' && object.$_terms.keys) {
|
|
25
|
+
object.$_terms.keys.forEach(({ key, schema }) => {
|
|
26
|
+
traverseObjectAndUntrack(schema, incomingInput[key], result[key]);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
object.type === 'string' &&
|
|
32
|
+
Array.isArray(object.$_terms.externals) &&
|
|
33
|
+
object.$_terms.externals.length
|
|
34
|
+
) {
|
|
35
|
+
tracker.untrack(incomingInput);
|
|
36
|
+
tracker.untrack(result);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
requireHook.resolve(
|
|
41
|
+
{ name: 'joi', file: 'lib/types/object.js', version: '>=17.0.0' },
|
|
42
|
+
(object) => {
|
|
43
|
+
if (
|
|
44
|
+
!agent.config ||
|
|
45
|
+
(agent.config && !agent.config.agent.trust_custom_validators)
|
|
46
|
+
) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
patcher.patch(object.__proto__, 'validateAsync', {
|
|
51
|
+
name: 'joi.object.validateAsync',
|
|
52
|
+
patchType: ASSESS_PROPAGATOR,
|
|
53
|
+
alwaysRun: true,
|
|
54
|
+
post(data) {
|
|
55
|
+
data.result.then((result) =>
|
|
56
|
+
traverseObjectAndUntrack(data.obj, data.args[0], result)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
);
|
|
@@ -24,6 +24,7 @@ const tracker = require('../../../tracker');
|
|
|
24
24
|
const { PropagationEvent, Signature, CallContext } = require('../../models');
|
|
25
25
|
const TagRange = require('../../models/tag-range');
|
|
26
26
|
const tagRangeUtil = require('../../models/tag-range/util');
|
|
27
|
+
const agent = require('../../../agent');
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Patch joi.string.validate so that it tags input with string-type-checked if validated
|
|
@@ -54,6 +55,21 @@ function instrumentJoiString(string) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
if (agent.config.agent.trust_custom_validators) {
|
|
60
|
+
patcher.patch(string.__proto__, 'validateAsync', {
|
|
61
|
+
name: 'joi.string.validateAsync',
|
|
62
|
+
patchType: ASSESS_PROPAGATOR,
|
|
63
|
+
alwaysRun: true,
|
|
64
|
+
post(data) {
|
|
65
|
+
if (!data.obj.$_terms.externals.length) return;
|
|
66
|
+
data.result.then((result) => {
|
|
67
|
+
tracker.untrack(data.args[0]);
|
|
68
|
+
tracker.untrack(result);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
requireHook.resolve(
|
|
@@ -12,9 +12,44 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
12
12
|
engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
+
const TagRange = require('../../models/tag-range');
|
|
16
|
+
const { CallContext, PropagationEvent, Signature } = require('../../models');
|
|
17
|
+
|
|
15
18
|
const hasUserDefinedValidator = (data) =>
|
|
16
19
|
data.obj.validators.some((validator) => validator.type === 'user defined');
|
|
17
20
|
|
|
21
|
+
const tagCustomValidatedValues = (values, data, tracker, tagRangeUtil) => {
|
|
22
|
+
for (const value of values) {
|
|
23
|
+
if (typeof value !== 'string') continue;
|
|
24
|
+
|
|
25
|
+
const trackingData = tracker.track(value);
|
|
26
|
+
|
|
27
|
+
if (!trackingData) return;
|
|
28
|
+
|
|
29
|
+
const { props } = trackingData;
|
|
30
|
+
const stringLength = value.length - 1;
|
|
31
|
+
|
|
32
|
+
props.tagRanges = tagRangeUtil.add(
|
|
33
|
+
props.tagRanges,
|
|
34
|
+
new TagRange(0, stringLength, 'custom-validated-nosql-injection')
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
props.tagRanges = tagRangeUtil.add(
|
|
38
|
+
props.tagRanges,
|
|
39
|
+
new TagRange(0, stringLength, 'string-type-checked')
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
props.event = new PropagationEvent({
|
|
43
|
+
context: new CallContext(data),
|
|
44
|
+
signature: new Signature('mongoose.mixed.doValidateSync'),
|
|
45
|
+
tagRanges: props.tagRanges,
|
|
46
|
+
source: 'P',
|
|
47
|
+
target: 'A',
|
|
48
|
+
parents: [props.event]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
18
52
|
module.exports = {
|
|
19
|
-
hasUserDefinedValidator
|
|
53
|
+
hasUserDefinedValidator,
|
|
54
|
+
tagCustomValidatedValues
|
|
20
55
|
};
|
|
@@ -21,9 +21,11 @@ const tagRangeUtil = require('../../models/tag-range/util');
|
|
|
21
21
|
const {
|
|
22
22
|
PATCH_TYPES: { ASSESS_PROPAGATOR }
|
|
23
23
|
} = require('../../../constants');
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const {
|
|
25
|
+
hasUserDefinedValidator,
|
|
26
|
+
tagCustomValidatedValues
|
|
27
|
+
} = require('./helpers');
|
|
28
|
+
const agent = require('../../../agent');
|
|
27
29
|
|
|
28
30
|
const doValidateSyncPatcher = (SchemaMap) => {
|
|
29
31
|
patcher.patch(SchemaMap.prototype, 'doValidateSync', {
|
|
@@ -31,37 +33,16 @@ const doValidateSyncPatcher = (SchemaMap) => {
|
|
|
31
33
|
name: 'mongoose.map.doValidateSync',
|
|
32
34
|
patchType: ASSESS_PROPAGATOR,
|
|
33
35
|
post(data) {
|
|
34
|
-
if (data.result
|
|
36
|
+
if (data.result) return;
|
|
35
37
|
|
|
36
38
|
if (!hasUserDefinedValidator(data)) return;
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const stringLength = value.length - 1;
|
|
45
|
-
|
|
46
|
-
props.tagRanges = tagRangeUtil.add(
|
|
47
|
-
props.tagRanges,
|
|
48
|
-
new TagRange(0, stringLength, 'custom-validated-nosql-injection')
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
props.tagRanges = tagRangeUtil.add(
|
|
52
|
-
props.tagRanges,
|
|
53
|
-
new TagRange(0, stringLength, 'string-type-checked')
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
props.event = new PropagationEvent({
|
|
57
|
-
context: new CallContext(data),
|
|
58
|
-
signature: new Signature('mongoose.map.doValidateSync'),
|
|
59
|
-
tagRanges: props.tagRanges,
|
|
60
|
-
source: 'P',
|
|
61
|
-
target: 'A',
|
|
62
|
-
parents: [props.event]
|
|
63
|
-
});
|
|
64
|
-
}
|
|
40
|
+
tagCustomValidatedValues(
|
|
41
|
+
data.args[0].values(),
|
|
42
|
+
data,
|
|
43
|
+
tracker,
|
|
44
|
+
tagRangeUtil
|
|
45
|
+
);
|
|
65
46
|
}
|
|
66
47
|
});
|
|
67
48
|
};
|
|
@@ -69,6 +50,13 @@ const doValidateSyncPatcher = (SchemaMap) => {
|
|
|
69
50
|
requireHook.resolve(
|
|
70
51
|
{ name: 'mongoose', file: 'lib/schema/map.js', version: '>=5.0.0' },
|
|
71
52
|
(SchemaMap) => {
|
|
53
|
+
if (
|
|
54
|
+
!agent.config ||
|
|
55
|
+
(agent.config && !agent.config.agent.trust_custom_validators)
|
|
56
|
+
) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
72
60
|
doValidateSyncPatcher(SchemaMap);
|
|
73
61
|
}
|
|
74
62
|
);
|