@contrast/assess 1.12.0 → 1.14.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 (42) hide show
  1. package/LICENSE +12 -0
  2. package/lib/dataflow/propagation/index.js +2 -0
  3. package/lib/dataflow/propagation/install/buffer.js +6 -5
  4. package/lib/dataflow/propagation/install/contrast-methods/add.js +3 -0
  5. package/lib/dataflow/propagation/install/joi/any.js +46 -0
  6. package/lib/dataflow/propagation/install/joi/boolean.js +109 -0
  7. package/lib/dataflow/propagation/install/joi/expression.js +99 -0
  8. package/lib/dataflow/propagation/install/joi/index.js +172 -0
  9. package/lib/dataflow/propagation/install/joi/keys.js +140 -0
  10. package/lib/dataflow/propagation/install/joi/number.js +107 -0
  11. package/lib/dataflow/propagation/install/joi/object.js +46 -0
  12. package/lib/dataflow/propagation/install/joi/string-schema.js +233 -0
  13. package/lib/dataflow/propagation/install/joi/utils.js +111 -0
  14. package/lib/dataflow/propagation/install/joi/values.js +154 -0
  15. package/lib/dataflow/propagation/install/path/basename.js +1 -3
  16. package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -3
  17. package/lib/dataflow/propagation/install/path/normalize.js +1 -3
  18. package/lib/dataflow/propagation/install/pug/index.js +2 -2
  19. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +7 -6
  20. package/lib/dataflow/propagation/install/send.js +60 -0
  21. package/lib/dataflow/propagation/install/sequelize.js +4 -4
  22. package/lib/dataflow/propagation/install/string/match-all.js +6 -5
  23. package/lib/dataflow/propagation/install/string/match.js +14 -11
  24. package/lib/dataflow/propagation/install/string/replace.js +6 -5
  25. package/lib/dataflow/propagation/install/string/slice.js +3 -0
  26. package/lib/dataflow/propagation/install/string/split.js +6 -5
  27. package/lib/dataflow/propagation/install/string/substring.js +1 -3
  28. package/lib/dataflow/propagation/install/string/trim.js +2 -0
  29. package/lib/dataflow/propagation/install/url/parse.js +3 -8
  30. package/lib/dataflow/propagation/install/url/searchParams.js +7 -7
  31. package/lib/dataflow/propagation/install/validator/hooks.js +4 -4
  32. package/lib/dataflow/sinks/index.js +3 -3
  33. package/lib/dataflow/sinks/install/eval.js +63 -67
  34. package/lib/dataflow/sinks/install/fs.js +2 -2
  35. package/lib/dataflow/sinks/install/function.js +87 -91
  36. package/lib/dataflow/sinks/install/vm.js +7 -7
  37. package/lib/dataflow/sources/install/body-parser1.js +2 -2
  38. package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
  39. package/lib/dataflow/sources/install/http.js +11 -10
  40. package/lib/dataflow/tracker.js +1 -3
  41. package/lib/response-scanning/install/http.js +3 -2
  42. package/package.json +14 -11
@@ -45,7 +45,7 @@ module.exports = function(core) {
45
45
  context: `'${obj}'.exec('${strInfo.value}')`,
46
46
  history: [strInfo],
47
47
  object: {
48
- value: obj,
48
+ value: 'RegExp',
49
49
  tracked: false,
50
50
  },
51
51
  args: [
@@ -162,11 +162,12 @@ module.exports = function(core) {
162
162
  metadata
163
163
  });
164
164
 
165
- if (event) {
166
- const { extern } = tracker.track(res, event);
167
- if (extern) {
168
- result.groups[key] = extern;
169
- }
165
+ if (!event) return;
166
+
167
+ const { extern } = tracker.track(res, event);
168
+
169
+ if (extern) {
170
+ result.groups[key] = extern;
170
171
  }
171
172
  });
172
173
  }
@@ -0,0 +1,60 @@
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
+ 'use strict';
16
+
17
+ const { patchType } = require('../common');
18
+ const { slice } = require('@contrast/common');
19
+
20
+ module.exports = function (core) {
21
+ const {
22
+ scopes: { sources, instrumentation },
23
+ depHooks,
24
+ patcher
25
+ } = core;
26
+
27
+ const send = {};
28
+ core.assess.dataflow.propagation.send = send;
29
+
30
+ function patchSendModule(sendModuleExport) {
31
+ return patcher.patch(sendModuleExport, {
32
+ name: 'send',
33
+ patchType,
34
+ post(data) {
35
+ patcher.patch(data.result, 'sendFile', {
36
+ name: 'send.sendFile',
37
+ patchType,
38
+ pre(data) {
39
+ const { args } = data;
40
+
41
+ if (!sources.getStore()?.assess || instrumentation.isLocked()) {
42
+ return;
43
+ }
44
+
45
+ const untrackedPath = slice(` ${args[0]}`, 1);
46
+ args[0] = untrackedPath;
47
+ },
48
+ });
49
+ },
50
+ });
51
+ }
52
+
53
+ send.install = function () {
54
+ depHooks.resolve({ name: 'send' }, (sendModule) =>
55
+ patchSendModule(sendModule)
56
+ );
57
+ };
58
+
59
+ return send;
60
+ };
@@ -32,7 +32,7 @@ module.exports = function(core) {
32
32
  },
33
33
  } = core;
34
34
 
35
- function getFormatPostions(str) {
35
+ function getFormatPositions(str) {
36
36
  const positions = [];
37
37
  let index = -1;
38
38
 
@@ -50,7 +50,7 @@ module.exports = function(core) {
50
50
  return Array.from(matches, (match) => ({ [match[1]]: match.index }));
51
51
  }
52
52
 
53
- return (core.assess.dataflow.propagation.sequelizeInstrumentation = {
53
+ return core.assess.dataflow.propagation.sequelizeInstrumentation = {
54
54
  install() {
55
55
  depHooks.resolve(
56
56
  { name: 'sequelize', file: 'lib/sql-string.js' },
@@ -133,7 +133,7 @@ module.exports = function(core) {
133
133
  return;
134
134
  }
135
135
 
136
- const positions = getFormatPostions(data.args[0]);
136
+ const positions = getFormatPositions(data.args[0]);
137
137
  const firstArgInfo = tracker.getData(data.args[0]);
138
138
 
139
139
  if (!positions.length) {
@@ -307,5 +307,5 @@ module.exports = function(core) {
307
307
  }
308
308
  );
309
309
  },
310
- });
310
+ };
311
311
  };
@@ -195,11 +195,12 @@ module.exports = function(core) {
195
195
  });
196
196
  }
197
197
 
198
- if (event) {
199
- const { extern } = tracker.track(res, event);
200
- if (extern) {
201
- resValue.groups[key] = extern;
202
- }
198
+ if (!event) return;
199
+
200
+ const { extern } = tracker.track(res, event);
201
+
202
+ if (extern) {
203
+ resValue.groups[key] = extern;
203
204
  }
204
205
  });
205
206
  }
@@ -30,6 +30,7 @@ module.exports = function(core) {
30
30
  },
31
31
  },
32
32
  } = core;
33
+
33
34
  const name = 'String.prototype.match';
34
35
 
35
36
  function getPropagationEvent(data, res, objInfo, start) {
@@ -49,7 +50,7 @@ module.exports = function(core) {
49
50
  moduleName: 'String',
50
51
  methodName: 'prototype.match',
51
52
  context: `'${objInfo.value}'.match(${args[0].value})`,
52
- history: [objInfo],
53
+ history: [{ ...objInfo }],
53
54
  object: {
54
55
  value: objInfo.value,
55
56
  tracked: true,
@@ -131,11 +132,12 @@ module.exports = function(core) {
131
132
  event = getPropagationEvent(data, res, objInfo, start);
132
133
  }
133
134
 
134
- if (event) {
135
- const { extern } = tracker.track(res, event);
136
- if (extern) {
137
- data.result[i] = extern;
138
- }
135
+ if (!event) continue;
136
+
137
+ const { extern } = tracker.track(res, event);
138
+
139
+ if (extern) {
140
+ data.result[i] = extern;
139
141
  }
140
142
  }
141
143
  if (hasCaptureGroups && result.groups) {
@@ -153,11 +155,12 @@ module.exports = function(core) {
153
155
  event = getPropagationEvent(data, res, objInfo, start);
154
156
  }
155
157
 
156
- if (event) {
157
- const { extern } = tracker.track(res, event);
158
- if (extern) {
159
- data.result.groups[key] = extern;
160
- }
158
+ if (!event) return;
159
+
160
+ const { extern } = tracker.track(res, event);
161
+
162
+ if (extern) {
163
+ data.result.groups[key] = extern;
161
164
  }
162
165
  });
163
166
  }
@@ -199,11 +199,12 @@ module.exports = function(core) {
199
199
  target: 'R',
200
200
  });
201
201
 
202
- if (event) {
203
- const { extern } = tracker.track(result, event);
204
- if (extern) {
205
- data.result = extern;
206
- }
202
+ if (!event) return;
203
+
204
+ const { extern } = tracker.track(result, event);
205
+
206
+ if (extern) {
207
+ data.result = extern;
207
208
  }
208
209
  }
209
210
  });
@@ -98,6 +98,9 @@ module.exports = function(core) {
98
98
  prependFrames: [orig]
99
99
  }
100
100
  });
101
+
102
+ if (!event) return;
103
+
101
104
  const { extern } = tracker.track(result, event);
102
105
 
103
106
  if (extern) {
@@ -95,11 +95,12 @@ module.exports = function(core) {
95
95
  target: 'R'
96
96
  });
97
97
 
98
- if (event) {
99
- const { extern } = tracker.track(res, event);
100
- if (extern) {
101
- data.result[i] = extern;
102
- }
98
+ if (!event) continue;
99
+
100
+ const { extern } = tracker.track(res, event);
101
+
102
+ if (extern) {
103
+ data.result[i] = extern;
103
104
  }
104
105
  }
105
106
  }
@@ -108,9 +108,7 @@ module.exports = function(core) {
108
108
  target: 'R',
109
109
  });
110
110
 
111
- if (!event) {
112
- return;
113
- }
111
+ if (!event) return;
114
112
 
115
113
  const { extern } = tracker.track(result, event);
116
114
 
@@ -82,6 +82,8 @@ module.exports = function(core) {
82
82
  target: 'R'
83
83
  });
84
84
 
85
+ if (!event) return;
86
+
85
87
  const { extern } = tracker.track(result, event);
86
88
 
87
89
  if (extern) {
@@ -106,16 +106,11 @@ module.exports = function(core) {
106
106
  prependFrames: [orig]
107
107
  },
108
108
  });
109
- if (!event) return;
110
109
 
111
- if (partInfo) {
112
- Object.assign(partInfo, event);
113
- }
114
- const { extern } = partInfo || tracker.track(part, event);
110
+ if (!event) return;
115
111
 
116
- if (extern) {
117
- result[key] = extern;
118
- }
112
+ Object.assign(partInfo, event);
113
+ result[key] = substr;
119
114
  }
120
115
  } else {
121
116
  traverse(substr, url, key, 0);
@@ -90,10 +90,8 @@ module.exports = function(core) {
90
90
  if (event) Object.assign(paramInfo, event);
91
91
  }
92
92
 
93
- const trackedKey = keyInfo?.extern;
94
- const trackedParam = paramInfo?.extern;
95
- if (trackedKey) result.delete(key);
96
- result.set(trackedKey || key, trackedParam || param);
93
+ if (keyInfo) result.delete(key);
94
+ result.set(key, param);
97
95
  });
98
96
  }
99
97
 
@@ -106,11 +104,13 @@ module.exports = function(core) {
106
104
  if (!event) return;
107
105
 
108
106
  Object.assign(paramInfo, event);
109
- const { extern } = paramInfo || tracker.track(params[key], event);
107
+ let value = params[key];
110
108
 
111
- if (extern) {
112
- result.set(key, extern);
109
+ if (!paramInfo) {
110
+ ({ extern: value } = tracker.track(params[key], event));
113
111
  }
112
+
113
+ result.set(key, value);
114
114
  });
115
115
  }
116
116
 
@@ -132,13 +132,13 @@ module.exports = function(core) {
132
132
  'R'
133
133
  );
134
134
  let resultTracked = tracker.getData(data.result);
135
- if (!resultTracked) {
136
- resultTracked = tracker.track(data.result, event);
137
- if (resultTracked.extern) data.result = resultTracked.extern;
138
- }
135
+
139
136
  if (event) {
137
+ resultTracked = resultTracked || tracker.track(data.result, event);
140
138
  Object.assign(resultTracked, event);
141
139
  }
140
+
141
+ if (resultTracked.extern) data.result = resultTracked.extern;
142
142
  }
143
143
  }
144
144
  }));
@@ -20,7 +20,7 @@ const { callChildComponentMethodsSync, Event, Rule } = require('@contrast/common
20
20
  const { isVulnerable } = require('../utils/is-vulnerable');
21
21
  const { isSafeContentType } = require('../utils/is-safe-content-type');
22
22
 
23
- module.exports = function(core) {
23
+ module.exports = function (core) {
24
24
  const {
25
25
  logger,
26
26
  messages,
@@ -30,7 +30,7 @@ module.exports = function(core) {
30
30
  const sinkScopes = {
31
31
  [Rule.SQL_INJECTION]: new AsyncLocalStorage(),
32
32
  [Rule.NOSQL_INJECTION_MONGO]: new AsyncLocalStorage(),
33
- ['unsafe-code-execution']: new AsyncLocalStorage()
33
+ [Rule.UNSAFE_CODE_EXECUTION]: new AsyncLocalStorage()
34
34
  };
35
35
  const sinks = core.assess.dataflow.sinks = {
36
36
  isVulnerable,
@@ -83,7 +83,7 @@ module.exports = function(core) {
83
83
  require('./install/sqlite3')(core);
84
84
  require('./install/vm')(core);
85
85
 
86
- sinks.install = function() {
86
+ sinks.install = function () {
87
87
  callChildComponentMethodsSync(core.assess.dataflow.sinks, 'install');
88
88
  };
89
89
 
@@ -25,10 +25,10 @@ const {
25
25
  CUSTOM_VALIDATED,
26
26
  LIMITED_CHARS,
27
27
  },
28
+ Rule: { UNSAFE_CODE_EXECUTION },
28
29
  } = require('@contrast/common');
29
30
  const { patchType, filterSafeTags } = require('../common');
30
31
 
31
- const ruleId = 'unsafe-code-execution';
32
32
  const safeTags = [
33
33
  CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
34
34
  CUSTOM_ENCODED,
@@ -37,7 +37,7 @@ const safeTags = [
37
37
  LIMITED_CHARS,
38
38
  ];
39
39
 
40
- module.exports = function(core) {
40
+ module.exports = function (core) {
41
41
  const {
42
42
  config,
43
43
  logger,
@@ -55,83 +55,79 @@ module.exports = function(core) {
55
55
  core.assess.dataflow.sinks.contrastEval = {
56
56
  install() {
57
57
  if (!global.ContrastMethods?.eval) {
58
- logger.error(
59
- 'Cannot install `eval` instrumentation - Contrast method DNE'
60
- );
58
+ logger.error('Cannot install `eval` instrumentation - Contrast method DNE');
61
59
  return;
62
60
  }
63
61
 
64
- Object.assign(global.ContrastMethods, {
65
- eval: patcher.patch(global.ContrastMethods.eval, {
66
- name: 'global.ContrastMethods.eval',
67
- patchType,
68
- pre({ args: origArgs, hooked, orig, name }) {
69
- const store = sources.getStore()?.assess;
70
- const script = origArgs[0];
71
- if (
72
- !store ||
73
- instrumentation.isLocked() ||
74
- !script ||
75
- !isString(script)
76
- )
77
- return;
62
+ patcher.patch(global.ContrastMethods, 'eval', {
63
+ name: 'global.ContrastMethods.eval',
64
+ patchType,
65
+ pre({ args, orig }) {
66
+ const store = sources.getStore()?.assess;
67
+ const script = args[0];
68
+ if (
69
+ !store ||
70
+ instrumentation.isLocked() ||
71
+ !script ||
72
+ !isString(script)
73
+ ) {
74
+ return;
75
+ }
78
76
 
79
- const strInfo = tracker.getData(script);
77
+ const strInfo = tracker.getData(script);
80
78
 
81
- if (!strInfo) return;
79
+ if (!strInfo) return;
82
80
 
83
- const isArgVulnerable = isVulnerable(
84
- UNTRUSTED,
85
- safeTags,
86
- strInfo.tags
87
- );
81
+ const isArgVulnerable = isVulnerable(
82
+ UNTRUSTED,
83
+ safeTags,
84
+ strInfo.tags
85
+ );
88
86
 
89
- if (!isArgVulnerable && config.assess.safe_positives.enable) {
90
- const foundSafeTags = filterSafeTags(safeTags, strInfo);
91
- const safeStrInfo = {
92
- value: strInfo.value,
93
- tags: strInfo.tags,
94
- };
87
+ if (!isArgVulnerable && config.assess.safe_positives.enable) {
88
+ const foundSafeTags = filterSafeTags(safeTags, strInfo);
89
+ const safeStrInfo = {
90
+ value: strInfo.value,
91
+ tags: strInfo.tags,
92
+ };
95
93
 
96
- reportSafePositive({
97
- name,
98
- ruleId,
99
- safeTags: foundSafeTags,
100
- strInfo: safeStrInfo,
101
- });
102
- }
94
+ reportSafePositive({
95
+ name: 'eval',
96
+ ruleId: UNSAFE_CODE_EXECUTION,
97
+ safeTags: foundSafeTags,
98
+ strInfo: safeStrInfo,
99
+ });
100
+ }
103
101
 
104
- if (isArgVulnerable) {
105
- const event = createSinkEvent({
106
- name,
107
- context: `${name}('${strInfo.value}')`,
108
- history: [strInfo],
109
- object: {
110
- value: 'global.ContrastMethods',
111
- tracked: false,
112
- },
113
- moduleName: 'global.ContrastMethods',
114
- methodName: 'eval',
115
- args: [{ value: strInfo.value, tracked: true }],
116
- tags: strInfo.tags,
117
- source: 'P0',
118
- stacktraceOpts: {
119
- contructorOpt: hooked,
120
- prependFrames: [orig],
121
- },
122
- });
102
+ if (isArgVulnerable) {
103
+ const event = createSinkEvent({
104
+ name: 'eval',
105
+ context: `eval('${strInfo.value}')`,
106
+ history: [strInfo],
107
+ object: {
108
+ value: 'global',
109
+ tracked: false,
110
+ },
111
+ moduleName: 'global',
112
+ methodName: 'eval',
113
+ args: [{ value: strInfo.value, tracked: true }],
114
+ tags: strInfo.tags,
115
+ source: 'P0',
116
+ stacktraceOpts: {
117
+ contructorOpt: orig
118
+ },
119
+ });
123
120
 
124
- if (event) {
125
- reportFindings({
126
- ruleId,
127
- sinkEvent: event,
128
- });
129
- }
121
+ if (event) {
122
+ reportFindings({
123
+ ruleId: UNSAFE_CODE_EXECUTION,
124
+ sinkEvent: event,
125
+ });
130
126
  }
131
- },
132
- }),
127
+ }
128
+ },
133
129
  });
134
- },
130
+ }
135
131
  };
136
132
 
137
133
  return core.assess.dataflow.sinks.contrastEval;
@@ -34,7 +34,7 @@ module.exports = function(core) {
34
34
  const {
35
35
  depHooks,
36
36
  patcher,
37
- scopes: { sources },
37
+ scopes: { instrumentation, sources },
38
38
  assess: {
39
39
  eventFactory: { createSinkEvent },
40
40
  dataflow: {
@@ -62,7 +62,7 @@ module.exports = function(core) {
62
62
  const pre = (name, method, moduleName = 'fs', fullMethodName = '') => (data) => {
63
63
  const { name: methodName, indices } = method;
64
64
  const store = sources.getStore()?.assess;
65
- if (!store) return;
65
+ if (!store || instrumentation.isLocked()) return;
66
66
 
67
67
  const values = getValues(indices, data.args);
68
68
  if (!values.length) return;