@contrast/protect 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/error-handlers/constants.js +15 -0
  2. package/lib/error-handlers/index.js +17 -0
  3. package/lib/error-handlers/install/express4.js +89 -0
  4. package/lib/error-handlers/install/fastify3.js +17 -4
  5. package/lib/error-handlers/install/koa2.js +16 -2
  6. package/lib/esm-loader.mjs +15 -0
  7. package/lib/get-source-context.js +33 -0
  8. package/lib/hardening/constants.js +20 -0
  9. package/lib/hardening/handlers.js +65 -0
  10. package/lib/hardening/index.js +29 -0
  11. package/lib/hardening/install/node-serialize0.js +59 -0
  12. package/lib/index.d.ts +127 -19
  13. package/lib/index.js +19 -0
  14. package/lib/input-analysis/constants.js +20 -0
  15. package/lib/input-analysis/handlers.js +201 -16
  16. package/lib/input-analysis/index.js +40 -3
  17. package/lib/input-analysis/install/body-parser1.js +122 -0
  18. package/lib/input-analysis/install/cookie-parser1.js +80 -0
  19. package/lib/input-analysis/install/express4.js +103 -0
  20. package/lib/input-analysis/install/fastify3.js +51 -24
  21. package/lib/input-analysis/install/formidable1.js +72 -0
  22. package/lib/input-analysis/install/http.js +30 -4
  23. package/lib/input-analysis/install/koa-body5.js +63 -0
  24. package/lib/input-analysis/install/koa-bodyparser4.js +64 -0
  25. package/lib/input-analysis/install/koa2.js +38 -48
  26. package/lib/input-analysis/install/multer1.js +88 -0
  27. package/lib/input-analysis/install/qs6.js +57 -0
  28. package/lib/input-analysis/install/universal-cookie4.js +52 -0
  29. package/lib/input-analysis/ip-analysis.js +76 -0
  30. package/lib/input-analysis/virtual-patches.js +109 -0
  31. package/lib/input-tracing/constants.js +15 -0
  32. package/lib/input-tracing/handlers/index.js +225 -66
  33. package/lib/input-tracing/index.js +25 -2
  34. package/lib/input-tracing/install/child-process.js +28 -7
  35. package/lib/input-tracing/install/eval.js +60 -0
  36. package/lib/input-tracing/install/fs.js +21 -4
  37. package/lib/input-tracing/install/http.js +63 -0
  38. package/lib/input-tracing/install/mongodb.js +233 -0
  39. package/lib/input-tracing/install/mysql.js +21 -4
  40. package/lib/input-tracing/install/postgres.js +20 -4
  41. package/lib/input-tracing/install/sequelize.js +22 -5
  42. package/lib/input-tracing/install/sqlite3.js +21 -4
  43. package/lib/input-tracing/install/vm.js +132 -0
  44. package/lib/make-response-blocker.js +15 -0
  45. package/lib/make-source-context.js +22 -1
  46. package/lib/security-exception.js +15 -0
  47. package/lib/semantic-analysis/handlers.js +160 -0
  48. package/lib/semantic-analysis/index.js +38 -0
  49. package/lib/throw-security-exception.js +17 -6
  50. package/package.json +10 -12
  51. package/lib/cli-rewriter.js +0 -20
  52. package/lib/input-analysis/install/co-body.js +0 -51
  53. package/lib/input-analysis/install/cookie-parser.js +0 -48
  54. package/lib/input-analysis/install/formidable.js +0 -53
  55. package/lib/input-analysis/install/multer.js +0 -52
  56. package/lib/input-analysis/install/qs.js +0 -40
  57. package/lib/input-analysis/install/universal-cookie.js +0 -34
  58. package/lib/input-tracing/handlers/nosql-injection-mongo.js +0 -48
  59. package/lib/utils.js +0 -88
@@ -1,6 +1,22 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
1
16
  'use strict';
2
17
 
3
- const { simpleTraverse } = require('../utils');
18
+ const { BLOCKING_MODES, simpleTraverse } = require('@contrast/common');
19
+ const address = require('ipaddr.js');
4
20
 
5
21
  //
6
22
  // these rules are not implemented by agent-lib, but are being considered for
@@ -95,8 +111,12 @@ module.exports = function(core) {
95
111
  * @returns {undefined|[String]} undefined to permit else [mode, rule] to block.
96
112
  */
97
113
  inputAnalysis.handleConnect = function handleConnect(sourceContext, connectInputs) {
114
+ if (!sourceContext || sourceContext.allowed) return;
115
+
98
116
  const { rules: { agentLibRules, agentLibRulesMask: mask } } = sourceContext;
99
117
 
118
+ inputAnalysis.handleVirtualPatches(sourceContext, { URLS: connectInputs.rawUrl, HEADERS: connectInputs.headers });
119
+
100
120
  // initialize findings to the basics
101
121
  let block = undefined;
102
122
  if (mask !== 0) {
@@ -115,12 +135,12 @@ module.exports = function(core) {
115
135
  throw new Error('nyi', sourceContext);
116
136
  };
117
137
 
118
-
119
138
  const jsonInputTypes = {
120
- keyType: agentLib.InputType.JsonKey, valueType: agentLib.InputType.JsonValue
139
+ keyType: agentLib.InputType.JsonKey, inputType: agentLib.InputType.JsonValue
121
140
  };
141
+
122
142
  const parameterInputTypes = {
123
- keyType: agentLib.InputType.ParameterKey, valueType: agentLib.InputType.ParameterValue
143
+ keyType: agentLib.InputType.ParameterKey, inputType: agentLib.InputType.ParameterValue
124
144
  };
125
145
 
126
146
  /**
@@ -140,11 +160,22 @@ module.exports = function(core) {
140
160
  * @param {Object} queryParams pojo {key: value, ...} for all query params/search params
141
161
  */
142
162
  inputAnalysis.handleQueryParams = function handleQueryParams(sourceContext, queryParams) {
163
+ if (sourceContext.analyzedQuery) return;
164
+ sourceContext.analyzedQuery = true;
165
+
143
166
  if (typeof queryParams !== 'object') {
144
167
  logger.debug({ queryParams }, 'handleQueryParams() called with non-object');
145
168
  return;
146
169
  }
147
- commonObjectAnalyzer(sourceContext, queryParams, parameterInputTypes);
170
+
171
+
172
+ inputAnalysis.handleVirtualPatches(sourceContext, { PARAMETERS: queryParams });
173
+
174
+ const block = commonObjectAnalyzer(sourceContext, queryParams, parameterInputTypes);
175
+
176
+ if (block) {
177
+ core.protect.throwSecurityException(sourceContext);
178
+ }
148
179
  };
149
180
 
150
181
  /**
@@ -157,11 +188,16 @@ module.exports = function(core) {
157
188
  * @param {Object} urlParams pojo
158
189
  */
159
190
  inputAnalysis.handleUrlParams = function(sourceContext, urlParams) {
191
+ if (sourceContext.analyzedUrlParams) return;
192
+ sourceContext.analyzedUrlParams = true;
193
+
160
194
  if (typeof urlParams !== 'object') {
161
195
  logger.debug({ urlParams }, 'handleUrlParams() called with non-object');
162
196
  return;
163
197
  }
164
198
 
199
+ inputAnalysis.handleVirtualPatches(sourceContext, { PARAMETERS: urlParams });
200
+
165
201
  const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
166
202
  const resultsList = [];
167
203
  const { UrlParameter } = agentLib.InputType;
@@ -199,10 +235,11 @@ module.exports = function(core) {
199
235
  securityException: undefined,
200
236
  resultsList,
201
237
  };
238
+
202
239
  const block = mergeFindings(rules.agentLibRules, sourceContext.findings, urlParamsFindings);
203
240
 
204
241
  if (block) {
205
- sourceContext.block(...block);
242
+ core.protect.throwSecurityException(sourceContext);
206
243
  }
207
244
  };
208
245
 
@@ -215,17 +252,23 @@ module.exports = function(core) {
215
252
  * @param {Object} cookies pojo
216
253
  */
217
254
  inputAnalysis.handleCookies = function(sourceContext, cookies) {
255
+ if (sourceContext.analyzedCookies) return;
256
+ sourceContext.analyzedCookies = true;
257
+
218
258
  const cookiesArr = Object.entries(cookies).reduce((acc, [key, value]) => {
219
259
  acc.push(key, value);
220
260
  return acc;
221
261
  }, []);
262
+
263
+ inputAnalysis.handleVirtualPatches(sourceContext, { HEADERS: cookies });
264
+
222
265
  const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
223
266
  const cookieFindings = agentLib.scoreRequestConnect(mask, { cookies: cookiesArr }, preferWW);
224
267
 
225
268
  const block = mergeFindings(rules.agentLibRules, sourceContext.findings, cookieFindings);
226
269
 
227
270
  if (block) {
228
- sourceContext.block(...block);
271
+ core.protect.throwSecurityException(sourceContext);
229
272
  }
230
273
  };
231
274
 
@@ -239,11 +282,16 @@ module.exports = function(core) {
239
282
  * @param {Object} parsedBody
240
283
  */
241
284
  inputAnalysis.handleParsedBody = function(sourceContext, parsedBody) {
285
+ if (sourceContext.analyzedBody) return;
286
+ sourceContext.analyzedBody = true;
287
+
242
288
  if (typeof parsedBody !== 'object') {
243
289
  logger.debug({ parsedBody }, 'handleParsedBody() called with non-object');
244
290
  return;
245
291
  }
246
292
 
293
+ inputAnalysis.handleVirtualPatches(sourceContext, { PARAMETERS: parsedBody });
294
+
247
295
  let bodyType;
248
296
  let inputTypes;
249
297
  if (sourceContext.reqData.contentType.includes('/json')) {
@@ -253,8 +301,13 @@ module.exports = function(core) {
253
301
  bodyType = 'urlencoded';
254
302
  inputTypes = parameterInputTypes;
255
303
  }
256
- commonObjectAnalyzer(sourceContext, parsedBody, inputTypes);
304
+ const block = commonObjectAnalyzer(sourceContext, parsedBody, inputTypes);
305
+
257
306
  sourceContext.findings.bodyType = bodyType;
307
+
308
+ if (block) {
309
+ core.protect.throwSecurityException(sourceContext);
310
+ }
258
311
  };
259
312
 
260
313
  // was MULTIPART_NAME but maybe we should just call it what it is. it's kind
@@ -263,6 +316,70 @@ module.exports = function(core) {
263
316
  throw new Error('nyi', sourceContext, name);
264
317
  };
265
318
 
319
+ inputAnalysis.handleVirtualPatches = function(sourceContext, requestInput) {
320
+ const ruleId = 'virtual-patch';
321
+
322
+ if (!Object.keys(requestInput).filter(Boolean).length || !sourceContext?.virtualPatchesEvaluators.length) return;
323
+
324
+ for (const vpEvaluators of sourceContext.virtualPatchesEvaluators) {
325
+ for (const key in requestInput) {
326
+ const evaluator = vpEvaluators.get(key);
327
+
328
+ if (evaluator && requestInput[key] && evaluator(requestInput[key])) {
329
+ vpEvaluators.delete(key);
330
+ const { name, uuid } = vpEvaluators.get('metadata');
331
+
332
+ if (vpEvaluators.size === 1 && uuid) {
333
+ if (!sourceContext.findings.serverFeaturesResultsMap[ruleId]) {
334
+ sourceContext.findings.serverFeaturesResultsMap[ruleId] = [];
335
+ }
336
+ sourceContext.findings.serverFeaturesResultsMap[ruleId].push({
337
+ name,
338
+ uuid
339
+ });
340
+ sourceContext.findings.securityException = ['block', ruleId];
341
+ core.protect.throwSecurityException(sourceContext);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ };
347
+
348
+ inputAnalysis.handleIpAllowlist = function(sourceContext, ipAllowlist) {
349
+ if (!sourceContext || !ipAllowlist.length) return;
350
+
351
+ const { ip: reqIp, headers: reqHeaders } = sourceContext.reqData;
352
+
353
+ const match = ipListAnalysis(reqIp, reqHeaders, ipAllowlist);
354
+
355
+ if (match) {
356
+ logger.info(match, 'Found a matching IP to an entry in ipAllow list');
357
+ return true;
358
+ }
359
+ };
360
+
361
+ inputAnalysis.handleIpDenylist = function(sourceContext, ipDenylist) {
362
+ const ruleId = 'ip-denylist';
363
+
364
+ if (!sourceContext || !ipDenylist.length) return;
365
+
366
+ const { ip: reqIp, headers: reqHeaders } = sourceContext.reqData;
367
+
368
+ const match = ipListAnalysis(reqIp, reqHeaders, ipDenylist);
369
+
370
+ if (match) {
371
+ logger.info(match, 'Found a matching IP to an entry in ipDeny list');
372
+ if (!sourceContext.findings.serverFeaturesResultsMap[ruleId]) {
373
+ sourceContext.findings.serverFeaturesResultsMap[ruleId] = [];
374
+ }
375
+
376
+ sourceContext.findings.serverFeaturesResultsMap[ruleId].push({
377
+ ip: match.matchedIp,
378
+ uuid: match.uuid,
379
+ });
380
+ return ['block', 'ip-denylist'];
381
+ }
382
+ };
266
383
 
267
384
  /**
268
385
  * commonObjectAnalyzer() walks an object supplied by the end-user and checks
@@ -277,12 +394,12 @@ module.exports = function(core) {
277
394
  * @param {Object} inputTypes is either jsonInputTypes or parameterInputTypes,
278
395
  * both are defined above. They specify the input types to be used when evaluating
279
396
  * the object.
280
- * @returns undefined
397
+ * @returns {Array | undefined} returns an array with block info if vulnerability was found.
281
398
  */
282
399
  function commonObjectAnalyzer(sourceContext, object, inputTypes) {
283
400
  const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
284
401
  // use inputTypes to set params...
285
- const { keyType, valueType } = inputTypes;
402
+ const { keyType, inputType } = inputTypes;
286
403
  const inputTypeStr = inputTypes === jsonInputTypes ? 'Json' : 'Parameter';
287
404
  const { Where } = agentLib.MongoQueryType;
288
405
  const resultsList = [];
@@ -315,7 +432,7 @@ module.exports = function(core) {
315
432
  mongoQueryType = agentLib.getMongoQueryType(value);
316
433
  }
317
434
  } else {
318
- itemType = valueType;
435
+ itemType = inputType;
319
436
  }
320
437
  let items = agentLib.scoreAtom(mask, value, itemType, preferWW);
321
438
  if (!items && !mongoQueryType) {
@@ -333,7 +450,8 @@ module.exports = function(core) {
333
450
  // mimic it here (where scoreAtom() was used). the actual object/string
334
451
  // to match is stored as `inputToCheck`.
335
452
  const inputType = typeof inputToCheck;
336
- if ((mongoQueryType <= Where && inputType === 'object') || (mongoQueryType >= Where && inputType === 'string')) {
453
+ // query types up to Where, inclusive, accept either string or object values. Where and above accept only string values
454
+ if (mongoQueryType <= Where || inputType === 'string') {
337
455
  // the query-type/input-type combination is valid. add a synthesized item.
338
456
  const item = { ruleId: 'nosql-injection-mongo', score: 10, mongoContext: { inputToCheck } };
339
457
  items.push(item);
@@ -370,9 +488,50 @@ module.exports = function(core) {
370
488
  resultsList,
371
489
  };
372
490
 
373
- const block = mergeFindings(rules.agentLibRules, sourceContext.findings, findings);
374
- if (block) {
375
- sourceContext.block(...block);
491
+ return mergeFindings(rules.agentLibRules, sourceContext.findings, findings);
492
+ }
493
+
494
+ function ipListAnalysis(reqIp, reqHeaders, list) {
495
+ const forwardedIps = [];
496
+
497
+ for (let i = 0; i < reqHeaders.length; i++) {
498
+ if (reqHeaders[i] === 'x-forwarded-for') {
499
+ const ipsFromHeaders = reqHeaders[i + 1]?.split(/[,;]+/);
500
+ forwardedIps.push(...ipsFromHeaders);
501
+ }
502
+ }
503
+
504
+ const ipsToCheck = [reqIp, ...forwardedIps];
505
+ const now = new Date().getTime();
506
+
507
+ /* c8 ignore next 3 */
508
+ if (!ipsToCheck.length) {
509
+ return false;
510
+ }
511
+
512
+ for (const listEntry of list) {
513
+ const { doesExpire, expiresAt } = listEntry;
514
+ for (let i = 0; i < ipsToCheck.length; i++) {
515
+ const currentIp = ipsToCheck[i];
516
+
517
+ // Ignore bad IP values.
518
+ if (!address.isValid(currentIp)) {
519
+ logger.warn(`Unable to parse ${currentIp}.`);
520
+ continue;
521
+ }
522
+ const expired = doesExpire ? expiresAt - now <= 0 : false;
523
+
524
+ if (expired) {
525
+ logger.info(`IP expired: ${listEntry.name}, ${listEntry.ip}`);
526
+ continue;
527
+ }
528
+
529
+ const match = checkIpsMatch(listEntry, currentIp);
530
+
531
+ if (match) {
532
+ return match;
533
+ }
534
+ }
376
535
  }
377
536
  }
378
537
  };
@@ -432,13 +591,38 @@ function normalizeFindings(rules, findings) {
432
591
  // which the block can occur. so at a minimum 'block' should also result in a
433
592
  // block.
434
593
  const { mode } = rules[r.ruleId];
435
- if (r.score >= 90 && ['block', 'block_at_perimeter'].includes(mode)) {
594
+ if (r.score >= 90 && BLOCKING_MODES.includes(mode)) {
436
595
  r.blocked = true;
437
596
  findings.securityException = [mode, r.ruleId];
438
597
  }
439
598
  }
440
599
  }
441
600
 
601
+
602
+ function checkIpsMatch(listEntry, ip) {
603
+ const parsed = address.process(ip);
604
+
605
+ // Check if IP is in CIDR range,
606
+ if (listEntry.cidr) {
607
+ if (parsed.kind() !== listEntry.cidr.kind) {
608
+ return null;
609
+ }
610
+
611
+ if (parsed.match(listEntry.cidr.range)) {
612
+ return { ...listEntry, match: ip };
613
+ } else {
614
+ return null;
615
+ }
616
+ }
617
+
618
+ // or do a direct comparison
619
+ if (parsed.toNormalizedString() === listEntry.normalizedValue) {
620
+ return { ...listEntry, matchedIp: ip };
621
+ }
622
+
623
+ return null;
624
+ }
625
+
442
626
  /**
443
627
  * getValueAtKey() is used to fetch the object (expected) associated
444
628
  * with the path of keys in obj. i say expected because this is only used
@@ -453,6 +637,7 @@ function normalizeFindings(rules, findings) {
453
637
  */
454
638
  function getValueAtKey(obj, path, key) {
455
639
  for (const p of path) {
640
+ /* c8 ignore next 6 */
456
641
  if (!(p in obj)) {
457
642
  return undefined;
458
643
  }
@@ -1,17 +1,54 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
1
16
  'use strict';
2
17
 
3
18
  module.exports = function(core) {
4
19
  const inputAnalysis = core.protect.inputAnalysis = {};
5
20
 
21
+ // inputAnalysis handlers
6
22
  require('./handlers')(core);
23
+
24
+ // http(s) modules instrumentation
7
25
  require('./install/http')(core);
26
+
27
+ // common libraries instrumentation
28
+ require('./install/body-parser1')(core);
29
+ require('./install/cookie-parser1')(core);
30
+ require('./install/formidable1')(core);
31
+ require('./install/koa-body5')(core);
32
+ require('./install/koa-bodyparser4')(core);
33
+ require('./install/multer1')(core);
34
+ require('./install/qs6')(core);
35
+ require('./install/universal-cookie4')(core);
36
+
37
+ // framework specific instrumentation
8
38
  require('./install/fastify3')(core);
9
39
  require('./install/koa2')(core);
40
+ require('./install/express4')(core);
41
+
42
+ // virtual patches
43
+ require('./virtual-patches')(core);
44
+ require('./ip-analysis')(core);
10
45
 
11
46
  inputAnalysis.install = function() {
12
- inputAnalysis.httpInstrumentation.install();
13
- inputAnalysis.fastifyInstrumentation.install();
14
- inputAnalysis.koaInstrumentation.install();
47
+ Object.values(inputAnalysis)
48
+ .filter((property) => property.install)
49
+ .forEach((library) => {
50
+ library.install();
51
+ });
15
52
  };
16
53
 
17
54
  return inputAnalysis;
@@ -0,0 +1,122 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { isSecurityException } = require('../../security-exception');
19
+
20
+ module.exports = (core) => {
21
+ const {
22
+ depHooks,
23
+ logger,
24
+ protect,
25
+ protect: { inputAnalysis },
26
+ } = core;
27
+
28
+ function contrastNext(req, origNext, fnName) {
29
+ return function next(origErr) {
30
+ const sourceContext = protect.getSourceContext(fnName);
31
+ let securityException;
32
+
33
+ if (sourceContext && req.body && Object.keys(req.body).length) {
34
+ sourceContext.parsedBody = req.body;
35
+
36
+ try {
37
+ inputAnalysis.handleParsedBody(sourceContext, req.body);
38
+ } catch (err) {
39
+ if (isSecurityException(err)) {
40
+ securityException = err;
41
+ } else {
42
+ logger.error({ err }, 'Unexpected error during input analysis');
43
+ }
44
+ }
45
+ }
46
+ const error = securityException || origErr;
47
+
48
+ origNext(error);
49
+ };
50
+ }
51
+
52
+ // Patch body parser - `body-parser` used by `express` framework
53
+ function install() {
54
+ depHooks.resolve({ name: 'body-parser' }, (bodyParser) => {
55
+ const origBodyParser = bodyParser;
56
+
57
+ const { json: origJson, raw: origRaw, text: origText, urlencoded: origUrlencoded } = bodyParser;
58
+ const fnArr = [
59
+ {
60
+ key: 'json',
61
+ original: origJson,
62
+ },
63
+ {
64
+ key: 'raw',
65
+ original: origRaw,
66
+ },
67
+ {
68
+ key: 'text',
69
+ original: origText,
70
+ },
71
+ {
72
+ key: 'urlencoded',
73
+ original: origUrlencoded,
74
+ }
75
+ ];
76
+
77
+ bodyParser = function bodyParser(...args) {
78
+ const parser = origBodyParser(...args);
79
+ const hookedParser = function(req, res, next) {
80
+ parser(req, res, contrastNext(req, next, 'bodyParser'));
81
+ };
82
+
83
+ Object.defineProperty(hookedParser, 'name', {
84
+ value: 'bodyParser'
85
+ });
86
+
87
+ return hookedParser;
88
+ };
89
+
90
+ fnArr.forEach((fn) => {
91
+ const fnName = `bodyParser.${fn.key}`;
92
+ function contrastHooked(...args) {
93
+ const parser = fn.original(...args);
94
+ const hookedParser = function (req, res, next) {
95
+ parser(req, res, contrastNext(req, next, fnName));
96
+ };
97
+
98
+ Object.defineProperty(hookedParser, 'name', {
99
+ value: `${fn.key}Parser`
100
+ });
101
+
102
+ return hookedParser;
103
+ }
104
+
105
+ Object.defineProperty(bodyParser, fn.key, {
106
+ configurable: true,
107
+ enumerable: true,
108
+ get: () => contrastHooked,
109
+ });
110
+ });
111
+
112
+ return bodyParser;
113
+ }
114
+ );
115
+ }
116
+
117
+ const bodyParser1Instrumentation = inputAnalysis.bodyParser1Instrumentation = {
118
+ install
119
+ };
120
+
121
+ return bodyParser1Instrumentation;
122
+ };
@@ -0,0 +1,80 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { patchType } = require('../constants');
19
+ const { isSecurityException } = require('../../security-exception');
20
+
21
+ module.exports = (core) => {
22
+ const {
23
+ depHooks,
24
+ patcher,
25
+ logger,
26
+ protect,
27
+ protect: { inputAnalysis },
28
+ } = core;
29
+
30
+ // Patch `cookie-parser` package
31
+ function install() {
32
+ depHooks.resolve({ name: 'cookie-parser' }, (cookieParser) => patcher.patch(cookieParser, {
33
+ name: 'cookie-parser',
34
+ patchType,
35
+ post(data) {
36
+ data.result = patcher.patch(data.result, {
37
+ name: 'cookie-parser',
38
+ patchType,
39
+ pre(data) {
40
+ const [req, , origNext] = data.args;
41
+
42
+ function contrastNext(origErr) {
43
+ const sourceContext = protect.getSourceContext('cookie-parser');
44
+
45
+ let securityException;
46
+
47
+ if (sourceContext
48
+ && ((req.cookies && Object.keys(req.cookies).length) || (req.signedCookies && Object.keys(req.signedCookies).length))) {
49
+ sourceContext.parsedCookies = { ...req.cookies, ...req.signedCookies };
50
+
51
+ try {
52
+ inputAnalysis.handleCookies(sourceContext, sourceContext.parsedCookies);
53
+ } catch (err) {
54
+ if (isSecurityException(err)) {
55
+ securityException = err;
56
+ } else {
57
+ logger.error({ err }, 'Unexpected error during input analysis');
58
+ }
59
+ }
60
+ }
61
+
62
+ const error = securityException || origErr;
63
+
64
+ origNext(error);
65
+ }
66
+
67
+ data.args[2] = contrastNext;
68
+ }
69
+ });
70
+ }
71
+ })
72
+ );
73
+ }
74
+
75
+ const cookieParser1Instrumentation = inputAnalysis.cookieParser1Instrumentation = {
76
+ install
77
+ };
78
+
79
+ return cookieParser1Instrumentation;
80
+ };