@contrast/assess 1.8.0 → 1.10.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 (75) hide show
  1. package/lib/dataflow/event-factory.js +17 -13
  2. package/lib/dataflow/propagation/index.js +4 -0
  3. package/lib/dataflow/propagation/install/JSON/index.js +1 -0
  4. package/lib/dataflow/propagation/install/JSON/parse-fn.js +248 -0
  5. package/lib/dataflow/propagation/install/JSON/parse.js +196 -0
  6. package/lib/dataflow/propagation/install/JSON/stringify.js +5 -3
  7. package/lib/dataflow/propagation/install/array-prototype-join.js +21 -14
  8. package/lib/dataflow/propagation/install/buffer.js +2 -0
  9. package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -0
  10. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/number.js +58 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/string.js +53 -6
  13. package/lib/dataflow/propagation/install/contrast-methods/tag.js +3 -1
  14. package/lib/dataflow/propagation/install/decode-uri-component.js +9 -2
  15. package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -2
  16. package/lib/dataflow/propagation/install/encode-uri-component.js +9 -2
  17. package/lib/dataflow/propagation/install/escape-html.js +13 -5
  18. package/lib/dataflow/propagation/install/escape.js +9 -2
  19. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +11 -4
  20. package/lib/dataflow/propagation/install/isnumeric-0.js +59 -0
  21. package/lib/dataflow/propagation/install/mongoose/index.js +36 -0
  22. package/lib/dataflow/propagation/install/mongoose/schema-string.js +156 -0
  23. package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -0
  24. package/lib/dataflow/propagation/install/parse-int.js +60 -0
  25. package/lib/dataflow/propagation/install/path/basename.js +124 -0
  26. package/lib/dataflow/propagation/install/path/common.js +176 -0
  27. package/lib/dataflow/propagation/install/path/index.js +32 -0
  28. package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
  29. package/lib/dataflow/propagation/install/path/normalize.js +123 -0
  30. package/lib/dataflow/propagation/install/pug-runtime-escape.js +9 -2
  31. package/lib/dataflow/propagation/install/querystring/parse.js +12 -10
  32. package/lib/dataflow/propagation/install/sequelize.js +6 -3
  33. package/lib/dataflow/propagation/install/sql-template-strings.js +9 -2
  34. package/lib/dataflow/propagation/install/string/concat.js +8 -2
  35. package/lib/dataflow/propagation/install/string/format-methods.js +7 -2
  36. package/lib/dataflow/propagation/install/string/html-methods.js +15 -5
  37. package/lib/dataflow/propagation/install/string/match.js +16 -11
  38. package/lib/dataflow/propagation/install/string/replace.js +23 -15
  39. package/lib/dataflow/propagation/install/string/slice.js +14 -6
  40. package/lib/dataflow/propagation/install/string/split.js +16 -12
  41. package/lib/dataflow/propagation/install/string/substring.js +18 -8
  42. package/lib/dataflow/propagation/install/string/trim.js +4 -1
  43. package/lib/dataflow/propagation/install/unescape.js +9 -2
  44. package/lib/dataflow/propagation/install/url/domain-parsers.js +7 -2
  45. package/lib/dataflow/propagation/install/url/index.js +1 -0
  46. package/lib/dataflow/propagation/install/url/url.js +228 -0
  47. package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
  48. package/lib/dataflow/sinks/index.js +8 -4
  49. package/lib/dataflow/sinks/install/child-process.js +116 -50
  50. package/lib/dataflow/sinks/install/eval.js +138 -0
  51. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +7 -4
  52. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +9 -5
  53. package/lib/dataflow/sinks/install/fs.js +45 -13
  54. package/lib/dataflow/sinks/install/function.js +160 -0
  55. package/lib/dataflow/sinks/install/http/index.js +31 -0
  56. package/lib/dataflow/sinks/install/http/request.js +152 -0
  57. package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +7 -4
  58. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +8 -5
  59. package/lib/dataflow/sinks/install/marsdb.js +3 -0
  60. package/lib/dataflow/sinks/install/mongodb.js +7 -24
  61. package/lib/dataflow/sinks/install/mssql.js +49 -29
  62. package/lib/dataflow/sinks/install/mysql.js +9 -4
  63. package/lib/dataflow/sinks/install/postgres.js +6 -3
  64. package/lib/dataflow/sinks/install/sequelize.js +7 -5
  65. package/lib/dataflow/sinks/install/sqlite3.js +7 -3
  66. package/lib/dataflow/sinks/install/vm.js +276 -0
  67. package/lib/dataflow/sources/handler.js +2 -1
  68. package/lib/dataflow/sources/install/http.js +1 -1
  69. package/lib/dataflow/tag-utils.js +95 -2
  70. package/lib/dataflow/tracker.js +6 -6
  71. package/lib/index.js +2 -0
  72. package/lib/response-scanning/handlers/utils.js +2 -2
  73. package/lib/session-configuration/index.js +34 -0
  74. package/lib/session-configuration/install/http.js +79 -0
  75. package/package.json +2 -2
@@ -0,0 +1,228 @@
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 { inspect } = require('@contrast/common');
19
+ const { patchType } = require('../../common');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ scopes: { sources, instrumentation },
24
+ patcher,
25
+ depHooks,
26
+ assess: {
27
+ dataflow: { tracker, eventFactory: { createPropagationEvent } }
28
+ }
29
+ } = core;
30
+
31
+ const keys = [
32
+ 'href',
33
+ [
34
+ 'origin',
35
+ 'protocol',
36
+ 'username',
37
+ 'password',
38
+ 'host',
39
+ [
40
+ 'hostname',
41
+ 'port'
42
+ ],
43
+ 'pathname',
44
+ 'search',
45
+ 'searchParams',
46
+ 'hash'
47
+ ]
48
+ ];
49
+
50
+ function getPropagationEvent(strInfo, partInfo, data) {
51
+ const args = data.args.map((v) => {
52
+ const strInfo = tracker.getData(v);
53
+ return {
54
+ value: strInfo ? strInfo.value : v,
55
+ tracked: !!strInfo
56
+ };
57
+ });
58
+
59
+ return createPropagationEvent({
60
+ name: 'url.URL',
61
+ moduleName: 'url',
62
+ methodName: 'URL',
63
+ context: `url.URL('${strInfo.value}')`,
64
+ object: {
65
+ value: 'url',
66
+ tracked: false
67
+ },
68
+ result: {
69
+ value: inspect(data.result),
70
+ tracked: true
71
+ },
72
+ args,
73
+ tags: partInfo.tags,
74
+ history: [partInfo],
75
+ source: 'P',
76
+ target: 'R',
77
+ stacktraceOpts: {
78
+ constructorOpt: data.hooked,
79
+ prependFrames: [data.orig]
80
+ },
81
+ });
82
+ }
83
+
84
+ return core.assess.dataflow.propagation.urlInstrumentation.url = {
85
+ install() {
86
+ depHooks.resolve({ name: 'url' }, (url) => {
87
+ const name = 'url.URL';
88
+ patcher.patch(url, 'URL', {
89
+ name,
90
+ patchType,
91
+ post(data) {
92
+ const { args, result } = data;
93
+ if (!result || !args[0] || !sources.getStore()?.assess || instrumentation.isLocked()) return;
94
+
95
+ const [input, basename] = args;
96
+ let url = input;
97
+ if (basename && url !== result.toString()) {
98
+ const isAbsoluteURL = url.indexOf('://') > 0 || url.indexOf('//') === 0;
99
+ if (isAbsoluteURL) {
100
+ const splitUrl = url.split(new RegExp('(?=//)', 'g'));
101
+ let endOfSlashes = splitUrl.length === 1 ? true : false;
102
+ let newUrl = splitUrl[0] === result.protocol ? splitUrl[0] : basename.split(/(?<=:)/)[0];
103
+ for (let i = 0; i < splitUrl.length; i++) {
104
+ if (endOfSlashes) {
105
+ newUrl = newUrl.concat(splitUrl[i]);
106
+ }
107
+ if (splitUrl[i] === '/' && splitUrl[i + 1] !== '/') {
108
+ endOfSlashes = true;
109
+ }
110
+ }
111
+ url = newUrl;
112
+ } else {
113
+ url = basename.concat(url);
114
+ }
115
+ }
116
+ const strInfo = tracker.getData(url);
117
+ if (!strInfo) return;
118
+
119
+ const searchParamsProxy = new Proxy(data.result.searchParams, {
120
+ set(obj, prop, value) {
121
+ return Reflect.set(obj, prop, value);
122
+ },
123
+ get(obj, prop, receiver) {
124
+ const val = obj[prop];
125
+ if (val instanceof Function) {
126
+ return function (query) {
127
+ if (typeof query === 'string') {
128
+ const trackedProp = Reflect.get(obj, `_contrast_${query}`);
129
+ if (trackedProp) return trackedProp;
130
+ }
131
+ return val.apply(this === receiver ? obj : this, query);
132
+ };
133
+ }
134
+ return Reflect.get(obj, prop, receiver);
135
+ }
136
+ });
137
+
138
+ const proxy = new Proxy(data.result, {
139
+ set(obj, prop, value) {
140
+ return Reflect.set(obj, prop, value);
141
+ },
142
+ get(obj, prop) {
143
+ if (prop === 'searchParams') {
144
+ return searchParamsProxy;
145
+ }
146
+ if (prop === 'toString' || prop === 'toJSON') {
147
+ return function() {
148
+ return Reflect.get(obj, '_contrast_href');
149
+ };
150
+ }
151
+ if (typeof prop === 'string') {
152
+ const trackedProp = Reflect.get(obj, `_contrast_${prop}`);
153
+ if (trackedProp) return trackedProp;
154
+ }
155
+ return Reflect.get(obj, prop);
156
+ }
157
+ });
158
+
159
+ const traverse = function(href, url, keys, idx = 0) {
160
+ let substr = href;
161
+ keys.forEach((key) => {
162
+ if (typeof key === 'string' && key !== 'searchParams') {
163
+ const part = url[key];
164
+ if (part !== 'null') {
165
+
166
+ if (key === 'origin') {
167
+ const [protocol, originWithoutProtocol] = part.split(new RegExp('(?<=//)'));
168
+ const idx1 = href.indexOf(protocol);
169
+ const idx2 = href.indexOf(originWithoutProtocol, idx - 1);
170
+ substr = href.substring(idx1, idx1 + protocol.length).concat(href.substring(idx2, idx2 + originWithoutProtocol.length));
171
+ } else {
172
+ const index = href.indexOf(part, idx - 1);
173
+ substr = href.substring(index, index + part.length);
174
+ idx += part.length;
175
+ }
176
+
177
+ const partInfo = tracker.getData(substr);
178
+ if (!partInfo) return;
179
+
180
+ const event = getPropagationEvent(strInfo, partInfo, data);
181
+ if (!event) return;
182
+
183
+ if (partInfo) {
184
+ Object.assign(partInfo, event);
185
+ }
186
+ const { extern } = partInfo || tracker.track(part, event);
187
+
188
+ if (extern) {
189
+ proxy[`_contrast_${key}`] = extern;
190
+ }
191
+ }
192
+ } else if (key === 'searchParams') {
193
+ const queries = proxy.search.split('&');
194
+ queries.forEach((query) => {
195
+ const startIdx = query.indexOf('?') + 1;
196
+ const endIdx = query.indexOf('=');
197
+ const queryName = query.substring(startIdx, endIdx);
198
+ const queryString = query.substring(endIdx + 1, query.length);
199
+ const queryStringInfo = tracker.getData(queryString);
200
+ if (!queryStringInfo) return;
201
+
202
+ const event = getPropagationEvent(strInfo, queryStringInfo, data);
203
+
204
+ if (!event) return;
205
+
206
+ if (queryStringInfo) {
207
+ Object.assign(queryStringInfo, event);
208
+ }
209
+ const { extern } = queryStringInfo || tracker.track(queryString, event);
210
+
211
+ if (extern) {
212
+ searchParamsProxy[`_contrast_${queryName}`] = extern;
213
+ }
214
+ });
215
+ } else {
216
+ traverse(substr, url, key, 0);
217
+ }
218
+ });
219
+ };
220
+
221
+ traverse(url, result, keys, 0);
222
+ data.result = proxy;
223
+ }
224
+ });
225
+ });
226
+ },
227
+ };
228
+ };
@@ -30,9 +30,12 @@ module.exports = function(core) {
30
30
  function createValidatorPropagationEvent(method, data, trackingData, newTags, target) {
31
31
  return createPropagationEvent({
32
32
  name: `validator.${method}`,
33
+ moduleName: 'validator',
34
+ methodName: method,
35
+ context: `validator.${method}('${trackingData.value}')`,
33
36
  history: [{ ...trackingData }],
34
37
  args: [{
35
- value: data.args[0],
38
+ value: trackingData.value,
36
39
  tracked: true
37
40
  }],
38
41
  result: {
@@ -59,8 +62,9 @@ module.exports = function(core) {
59
62
  { name: 'validator', file: `lib/${validator}` },
60
63
  (index) => {
61
64
  const patchFn = typeof index === 'object' ? index.default : index;
65
+ const name = `validator.${validator}`;
62
66
  return patcher.patch(patchFn, {
63
- name: `validator.${validator}`,
67
+ name,
64
68
  patchType,
65
69
  post(data) {
66
70
  const matches = validator === 'matches';
@@ -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,
@@ -29,7 +29,8 @@ module.exports = function (core) {
29
29
 
30
30
  const sinkScopes = {
31
31
  [Rule.SQL_INJECTION]: new AsyncLocalStorage(),
32
- [Rule.NOSQL_INJECTION_MONGO]: new AsyncLocalStorage()
32
+ [Rule.NOSQL_INJECTION_MONGO]: new AsyncLocalStorage(),
33
+ ['unsafe-code-execution']: new AsyncLocalStorage()
33
34
  };
34
35
  const sinks = core.assess.dataflow.sinks = {
35
36
  isVulnerable,
@@ -65,19 +66,22 @@ module.exports = function (core) {
65
66
  }
66
67
  };
67
68
 
69
+ require('./install/express')(core);
68
70
  require('./install/fastify')(core);
69
71
  require('./install/koa')(core);
70
72
  require('./install/child-process')(core);
73
+ require('./install/eval')(core);
71
74
  require('./install/fs')(core);
75
+ require('./install/function')(core);
72
76
  require('./install/http')(core);
77
+ require('./install/marsdb')(core);
73
78
  require('./install/mongodb')(core);
74
79
  require('./install/mssql')(core);
75
80
  require('./install/mysql')(core);
76
81
  require('./install/postgres')(core);
77
82
  require('./install/sequelize')(core);
78
83
  require('./install/sqlite3')(core);
79
- require('./install/marsdb')(core);
80
- require('./install/express')(core);
84
+ require('./install/vm')(core);
81
85
 
82
86
  sinks.install = function() {
83
87
  callChildComponentMethodsSync(core.assess.dataflow.sinks, 'install');
@@ -15,13 +15,16 @@
15
15
 
16
16
  'use strict';
17
17
  const {
18
- DataflowTag: { UNTRUSTED }
18
+ DataflowTag: { UNTRUSTED },
19
+ join,
20
+ Rule,
21
+ isString,
22
+ inspect,
19
23
  } = require('@contrast/common');
20
24
 
21
25
  const { patchType } = require('../common');
22
- const { Rule, isString, inspect } = require('@contrast/common');
23
26
 
24
- module.exports = function (core) {
27
+ module.exports = function(core) {
25
28
  const {
26
29
  depHooks,
27
30
  patcher,
@@ -37,38 +40,53 @@ module.exports = function (core) {
37
40
 
38
41
  const safeTags = [];
39
42
 
40
- function commandCheck(name, command, secondArg, thirdArg, hooked) {
43
+ function commandCheck(
44
+ name,
45
+ method,
46
+ command,
47
+ secondArg,
48
+ thirdArg,
49
+ hooked,
50
+ orig
51
+ ) {
41
52
  const strInfo = tracker.getData(command);
42
- if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) return {
43
- strInfo: null,
44
- reported: false
45
- };
53
+ if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags))
54
+ return {
55
+ strInfo: null,
56
+ reported: false,
57
+ };
58
+
59
+ const args = [
60
+ {
61
+ value: strInfo.value,
62
+ tracked: true,
63
+ },
64
+ secondArg && {
65
+ value: inspect(secondArg),
66
+ tracked: false,
67
+ },
68
+ thirdArg && {
69
+ value: inspect(thirdArg),
70
+ tracked: false,
71
+ },
72
+ ].filter(Boolean);
46
73
 
47
74
  const event = createSinkEvent({
48
75
  name,
76
+ moduleName: 'child_process',
77
+ methodName: method,
78
+ context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
49
79
  history: [strInfo],
50
80
  object: {
51
81
  value: 'child_process',
52
82
  tracked: false,
53
83
  },
54
- args: [
55
- {
56
- value: strInfo.value,
57
- tracked: true,
58
- },
59
- (secondArg && {
60
- value: inspect(secondArg),
61
- tracked: false
62
- }),
63
- (thirdArg && {
64
- value: inspect(thirdArg),
65
- tracked: false
66
- })
67
- ].filter(Boolean),
84
+ args,
68
85
  tags: strInfo.tags,
69
86
  source: 'P0',
70
87
  stacktraceOpts: {
71
88
  constructorOpt: hooked,
89
+ prependFrames: [orig],
72
90
  },
73
91
  });
74
92
 
@@ -80,24 +98,33 @@ module.exports = function (core) {
80
98
 
81
99
  return {
82
100
  strInfo,
83
- reported: true
101
+ reported: true,
84
102
  };
85
103
  }
86
104
 
87
105
  return {
88
106
  strInfo,
89
- reported: false
107
+ reported: false,
90
108
  };
91
109
  }
92
110
 
93
- function argumentsCheck(name, command, commandInfo, args, options, hooked) {
94
- if (!Array.isArray(args) || !args?.length) return;
111
+ function argumentsCheck(
112
+ name,
113
+ method,
114
+ command,
115
+ commandInfo,
116
+ origArgs,
117
+ options,
118
+ hooked,
119
+ orig
120
+ ) {
121
+ if (!Array.isArray(origArgs) || !origArgs?.length) return;
95
122
 
96
123
  const trackedArgs = [];
97
124
  let vulnerableArgIdx;
98
125
 
99
- for (let i = 0; i < args.length; i++) {
100
- const trackData = tracker.getData(args[i]);
126
+ for (let i = 0; i < origArgs.length; i++) {
127
+ const trackData = tracker.getData(origArgs[i]);
101
128
 
102
129
  trackedArgs.push(trackData);
103
130
 
@@ -116,31 +143,36 @@ module.exports = function (core) {
116
143
 
117
144
  if (vulnerableArgIdx != 0 && !vulnerableArgIdx) return;
118
145
 
146
+ const args = [
147
+ {
148
+ value: commandInfo?.value || command,
149
+ tracked: !!commandInfo,
150
+ },
151
+ {
152
+ value: inspect(origArgs),
153
+ tracked: true,
154
+ },
155
+ {
156
+ value: inspect(options),
157
+ tracked: false,
158
+ },
159
+ ];
119
160
  const event = createSinkEvent({
120
161
  name,
162
+ moduleName: 'child_process',
163
+ methodName: method,
164
+ context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
121
165
  history: [trackedArgs[vulnerableArgIdx]],
122
166
  object: {
123
167
  value: 'child_process',
124
168
  tracked: false,
125
169
  },
126
- args: [
127
- {
128
- value: commandInfo?.value || command,
129
- tracked: !!commandInfo,
130
- },
131
- {
132
- value: inspect(args),
133
- tracked: true
134
- },
135
- {
136
- value: inspect(options),
137
- tracked: false
138
- }
139
- ],
170
+ args,
140
171
  tags: trackedArgs[vulnerableArgIdx].tags,
141
172
  source: 'P1',
142
173
  stacktraceOpts: {
143
174
  contructorOpt: hooked,
175
+ prependFrames: [orig],
144
176
  },
145
177
  });
146
178
 
@@ -154,7 +186,7 @@ module.exports = function (core) {
154
186
 
155
187
  core.assess.dataflow.sinks.cmdInjection = {
156
188
  install() {
157
- depHooks.resolve({ name: 'child_process' }, cp => {
189
+ depHooks.resolve({ name: 'child_process' }, (cp) => {
158
190
  ['spawn', 'spawnSync'].forEach((method) => {
159
191
  const name = `child_process.${method}`;
160
192
  patcher.patch(cp, method, {
@@ -169,12 +201,29 @@ module.exports = function (core) {
169
201
  const cpArgs = Array.isArray(data.args[1]) && data.args[1];
170
202
  const options = cpArgs ? data.args[2] : data.args[1];
171
203
 
172
- const cmdCheck = commandCheck(name, command, cpArgs, options, data.hooked);
204
+ const cmdCheck = commandCheck(
205
+ name,
206
+ method,
207
+ command,
208
+ cpArgs,
209
+ options,
210
+ data.hooked,
211
+ data.orig
212
+ );
173
213
 
174
214
  if (cmdCheck.reported || !options?.shell) return;
175
215
 
176
- argumentsCheck(name, command, cmdCheck.strInfo, cpArgs, options, data.hooked);
177
- }
216
+ argumentsCheck(
217
+ name,
218
+ method,
219
+ command,
220
+ cmdCheck.strInfo,
221
+ cpArgs,
222
+ options,
223
+ data.hooked,
224
+ data.orig
225
+ );
226
+ },
178
227
  });
179
228
  });
180
229
 
@@ -189,8 +238,16 @@ module.exports = function (core) {
189
238
 
190
239
  if (!store || !command || !isString(command)) return;
191
240
 
192
- commandCheck(name, command, secondArg, thirdArg, data.hooked);
193
- }
241
+ commandCheck(
242
+ name,
243
+ method,
244
+ command,
245
+ secondArg,
246
+ thirdArg,
247
+ data.hooked,
248
+ data.orig
249
+ );
250
+ },
194
251
  });
195
252
  });
196
253
 
@@ -212,8 +269,17 @@ module.exports = function (core) {
212
269
 
213
270
  const cmdInfo = tracker.getData(command);
214
271
 
215
- argumentsCheck(name, command, cmdInfo, cpArgs, options, data.hooked);
216
- }
272
+ argumentsCheck(
273
+ name,
274
+ method,
275
+ command,
276
+ cmdInfo,
277
+ cpArgs,
278
+ options,
279
+ data.hooked,
280
+ data.orig
281
+ );
282
+ },
217
283
  });
218
284
  });
219
285
  });
@@ -0,0 +1,138 @@
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 {
19
+ isString,
20
+ DataflowTag: {
21
+ UNTRUSTED,
22
+ CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
23
+ CUSTOM_ENCODED,
24
+ CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
25
+ CUSTOM_VALIDATED,
26
+ LIMITED_CHARS,
27
+ },
28
+ } = require('@contrast/common');
29
+ const { patchType, filterSafeTags } = require('../common');
30
+
31
+ const ruleId = 'unsafe-code-execution';
32
+ const safeTags = [
33
+ CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
34
+ CUSTOM_ENCODED,
35
+ CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
36
+ CUSTOM_VALIDATED,
37
+ LIMITED_CHARS,
38
+ ];
39
+
40
+ module.exports = function(core) {
41
+ const {
42
+ config,
43
+ logger,
44
+ patcher,
45
+ scopes: { sources, instrumentation },
46
+ assess: {
47
+ dataflow: {
48
+ tracker,
49
+ sinks: { isVulnerable, reportFindings, reportSafePositive },
50
+ eventFactory: { createSinkEvent },
51
+ },
52
+ },
53
+ } = core;
54
+
55
+ core.assess.dataflow.sinks.contrastEval = {
56
+ install() {
57
+ if (!global.ContrastMethods?.eval) {
58
+ logger.error(
59
+ 'Cannot install `eval` instrumentation - Contrast method DNE'
60
+ );
61
+ return;
62
+ }
63
+
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;
78
+
79
+ const strInfo = tracker.getData(script);
80
+
81
+ if (!strInfo) return;
82
+
83
+ const isArgVulnerable = isVulnerable(
84
+ UNTRUSTED,
85
+ safeTags,
86
+ strInfo.tags
87
+ );
88
+
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
+ };
95
+
96
+ reportSafePositive({
97
+ name,
98
+ ruleId,
99
+ safeTags: foundSafeTags,
100
+ strInfo: safeStrInfo,
101
+ });
102
+ }
103
+
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
+ });
123
+
124
+ if (event) {
125
+ reportFindings({
126
+ ruleId,
127
+ sinkEvent: event,
128
+ });
129
+ }
130
+ }
131
+ },
132
+ }),
133
+ });
134
+ },
135
+ };
136
+
137
+ return core.assess.dataflow.sinks.contrastEval;
138
+ };