@contrast/assess 1.28.0 → 1.29.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 (81) hide show
  1. package/lib/crypto-analysis/install/crypto.js +3 -3
  2. package/lib/dataflow/propagation/install/JSON/parse-fn.js +5 -5
  3. package/lib/dataflow/propagation/install/JSON/parse.js +3 -3
  4. package/lib/dataflow/propagation/install/JSON/stringify.js +24 -17
  5. package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
  6. package/lib/dataflow/propagation/install/buffer.js +60 -2
  7. package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -3
  8. package/lib/dataflow/propagation/install/ejs/template.js +3 -3
  9. package/lib/dataflow/propagation/install/joi/boolean.js +1 -1
  10. package/lib/dataflow/propagation/install/joi/expression.js +1 -1
  11. package/lib/dataflow/propagation/install/joi/index.js +1 -1
  12. package/lib/dataflow/propagation/install/joi/keys.js +5 -4
  13. package/lib/dataflow/propagation/install/joi/number.js +1 -1
  14. package/lib/dataflow/propagation/install/joi/string-schema.js +3 -2
  15. package/lib/dataflow/propagation/install/joi/utils.js +9 -5
  16. package/lib/dataflow/propagation/install/joi/values.js +4 -3
  17. package/lib/dataflow/propagation/install/mongoose/schema-map.js +2 -2
  18. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +2 -2
  19. package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -2
  20. package/lib/dataflow/propagation/install/path/basename.js +2 -2
  21. package/lib/dataflow/propagation/install/path/common.js +5 -5
  22. package/lib/dataflow/propagation/install/path/format.js +7 -4
  23. package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -2
  24. package/lib/dataflow/propagation/install/path/parse.js +4 -5
  25. package/lib/dataflow/propagation/install/querystring/escape.js +1 -1
  26. package/lib/dataflow/propagation/install/querystring/parse.js +8 -8
  27. package/lib/dataflow/propagation/install/querystring/stringify.js +1 -1
  28. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
  29. package/lib/dataflow/propagation/install/send.js +2 -2
  30. package/lib/dataflow/propagation/install/string/concat.js +19 -19
  31. package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
  32. package/lib/dataflow/propagation/install/string/index.js +4 -3
  33. package/lib/dataflow/propagation/install/string/match-all.js +3 -9
  34. package/lib/dataflow/propagation/install/string/match.js +6 -5
  35. package/lib/dataflow/propagation/install/string/replace.js +23 -17
  36. package/lib/dataflow/propagation/install/string/slice.js +5 -5
  37. package/lib/dataflow/propagation/install/string/split.js +13 -11
  38. package/lib/dataflow/propagation/install/string/substring.js +6 -5
  39. package/lib/dataflow/propagation/install/url/parse.js +1 -1
  40. package/lib/dataflow/propagation/install/url/searchParams.js +2 -1
  41. package/lib/dataflow/propagation/install/url/url.js +1 -1
  42. package/lib/dataflow/sinks/index.js +1 -0
  43. package/lib/dataflow/sinks/install/child-process.js +4 -4
  44. package/lib/dataflow/sinks/install/express/reflected-xss.js +7 -5
  45. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +1 -2
  46. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +1 -3
  47. package/lib/dataflow/sinks/install/fs.js +3 -3
  48. package/lib/dataflow/sinks/install/function.js +3 -3
  49. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +1 -2
  50. package/lib/dataflow/sinks/install/http/request.js +6 -5
  51. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
  52. package/lib/dataflow/sinks/install/libxmljs.js +1 -1
  53. package/lib/dataflow/sinks/install/marsdb.js +1 -2
  54. package/lib/dataflow/sinks/install/mongodb.js +1 -1
  55. package/lib/dataflow/sinks/install/mysql.js +1 -1
  56. package/lib/dataflow/sinks/install/postgres.js +1 -3
  57. package/lib/dataflow/sinks/install/restify.js +208 -0
  58. package/lib/dataflow/sinks/install/sequelize.js +1 -2
  59. package/lib/dataflow/sinks/install/vm.js +5 -5
  60. package/lib/dataflow/sources/handler.js +2 -2
  61. package/lib/dataflow/sources/index.js +1 -0
  62. package/lib/dataflow/sources/install/http.js +4 -4
  63. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +85 -0
  64. package/lib/dataflow/sources/install/restify/index.js +32 -0
  65. package/lib/dataflow/sources/install/restify/jsonBodyParser.js +109 -0
  66. package/lib/dataflow/sources/install/restify/router.js +77 -0
  67. package/lib/dataflow/tag-utils.js +20 -4
  68. package/lib/dataflow/tracker.js +1 -0
  69. package/lib/event-factory.js +3 -3
  70. package/lib/get-policy.js +2 -2
  71. package/lib/index.d.ts +18 -0
  72. package/lib/index.js +13 -0
  73. package/lib/make-source-context.js +2 -2
  74. package/lib/response-scanning/handlers/index.js +10 -10
  75. package/lib/response-scanning/handlers/utils.js +19 -12
  76. package/lib/response-scanning/install/http.js +9 -59
  77. package/lib/session-configuration/install/express-session.js +3 -5
  78. package/lib/session-configuration/install/fastify-cookie.js +3 -3
  79. package/lib/session-configuration/install/hapi.js +1 -3
  80. package/lib/session-configuration/install/koa.js +1 -1
  81. package/package.json +4 -4
@@ -16,7 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  const { patchType } = require('../../common');
19
- const { inspect, isString } = require('@contrast/common');
19
+ const { isString } = require('@contrast/common');
20
20
 
21
21
  module.exports = function(core) {
22
22
  const {
@@ -24,6 +24,7 @@ module.exports = function(core) {
24
24
  patcher,
25
25
  depHooks,
26
26
  assess: {
27
+ inspect, // todo: remove
27
28
  eventFactory: { createPropagationEvent },
28
29
  dataflow: { tracker }
29
30
  }
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { inspect } = require('@contrast/common');
19
18
  const { patchType } = require('../../common');
20
19
 
21
20
  module.exports = function(core) {
@@ -24,6 +23,7 @@ module.exports = function(core) {
24
23
  patcher,
25
24
  depHooks,
26
25
  assess: {
26
+ inspect, // todo: remove
27
27
  eventFactory: { createPropagationEvent },
28
28
  dataflow: { tracker }
29
29
  }
@@ -82,6 +82,7 @@ module.exports = function (core) {
82
82
  require('./install/mysql')(core);
83
83
  require('./install/node-serialize')(core);
84
84
  require('./install/postgres')(core);
85
+ require('./install/restify')(core);
85
86
  require('./install/sequelize')(core);
86
87
  require('./install/sqlite3')(core);
87
88
  require('./install/vm')(core);
@@ -16,10 +16,9 @@
16
16
  'use strict';
17
17
  const {
18
18
  DataflowTag: { UNTRUSTED },
19
- join,
19
+ ArrayPrototypeJoin,
20
20
  Rule: { CMD_INJECTION: ruleId },
21
21
  isString,
22
- inspect,
23
22
  } = require('@contrast/common');
24
23
  const { InstrumentationType: { RULE } } = require('../../../constants');
25
24
  const { patchType } = require('../common');
@@ -35,6 +34,7 @@ module.exports = function(core) {
35
34
  depHooks,
36
35
  patcher,
37
36
  assess: {
37
+ inspect, // todo: remove
38
38
  getSourceContext,
39
39
  eventFactory: { createSinkEvent },
40
40
  dataflow: {
@@ -81,7 +81,7 @@ module.exports = function(core) {
81
81
  name,
82
82
  moduleName: 'child_process',
83
83
  methodName: method,
84
- context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
84
+ context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
85
85
  history: [strInfo],
86
86
  object: {
87
87
  value: 'child_process',
@@ -167,7 +167,7 @@ module.exports = function(core) {
167
167
  name,
168
168
  moduleName: 'child_process',
169
169
  methodName: method,
170
- context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
170
+ context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
171
171
  history: [trackedArgs[vulnerableArgIdx]],
172
172
  object: {
173
173
  value: 'child_process',
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  Rule: { REFLECTED_XSS: ruleId },
21
20
  DataflowTag: {
@@ -52,14 +51,13 @@ module.exports = function(core) {
52
51
  eventFactory: { createSinkEvent },
53
52
  dataflow: {
54
53
  tracker,
55
- sinks: { isVulnerable, reportFindings, reportSafePositive }
54
+ sinks: { isVulnerable, reportFindings, reportSafePositive, isSafeContentType }
56
55
  },
57
56
  ruleScopes,
58
57
  },
59
58
  } = core;
60
59
 
61
60
  const reflectedXss = core.assess.dataflow.sinks.express.reflectedXss = {};
62
- const inspect = patcher.unwrap(util.inspect);
63
61
 
64
62
  const safeTags = [
65
63
  `excluded:${ruleId}`,
@@ -81,7 +79,11 @@ module.exports = function(core) {
81
79
  name,
82
80
  patchType,
83
81
  around: (next, data) => {
84
- if (!getSourceContext(RULE, ruleId)) return next();
82
+ const sourceContext = getSourceContext(RULE, ruleId);
83
+ if (!sourceContext) return next();
84
+
85
+ const { contentType } = sourceContext.responseData;
86
+ if (contentType && isSafeContentType(contentType)) return next();
85
87
 
86
88
  const [str] = data.args;
87
89
 
@@ -96,7 +98,7 @@ module.exports = function(core) {
96
98
  tracked: true,
97
99
  value: strInfo.value,
98
100
  }],
99
- context: `response.${method}(${inspect(strInfo.value)})`,
101
+ context: `response.${method}('${strInfo.value}')`,
100
102
  history: [strInfo],
101
103
  name,
102
104
  moduleName: 'express',
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  Rule: { UNVALIDATED_REDIRECT: ruleId },
21
20
  DataflowTag: {
@@ -46,6 +45,7 @@ module.exports = function(core) {
46
45
  patcher,
47
46
  config,
48
47
  assess: {
48
+ inspect, // todo: remove
49
49
  getSourceContext,
50
50
  eventFactory: { createSinkEvent },
51
51
  dataflow: {
@@ -56,7 +56,6 @@ module.exports = function(core) {
56
56
  } = core;
57
57
 
58
58
  const unvalidatedRedirect = core.assess.dataflow.sinks.express.unvalidatedRedirect = {};
59
- const inspect = patcher.unwrap(util.inspect);
60
59
 
61
60
  const safeTags = [
62
61
  `excluded:${ruleId}`,
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  Rule: { UNVALIDATED_REDIRECT: ruleId },
21
20
  DataflowTag: {
@@ -65,6 +64,7 @@ module.exports = function(core) {
65
64
  depHooks,
66
65
  patcher,
67
66
  assess: {
67
+ inspect, // todo: remove
68
68
  getSourceContext,
69
69
  eventFactory: { createSinkEvent },
70
70
  dataflow: {
@@ -76,8 +76,6 @@ module.exports = function(core) {
76
76
  const unvalidatedRedirect =
77
77
  (core.assess.dataflow.sinks.fastify.unvalidatedRedirect = {});
78
78
 
79
- const inspect = patcher.unwrap(util.inspect);
80
-
81
79
  const safeTags = [
82
80
  `excluded:${ruleId}`,
83
81
  CUSTOM_ENCODED,
@@ -25,9 +25,8 @@ const {
25
25
  },
26
26
  FS_METHODS,
27
27
  Rule: { PATH_TRAVERSAL: ruleId },
28
- inspect,
29
28
  isString,
30
- join,
29
+ ArrayPrototypeJoin,
31
30
  } = require('@contrast/common');
32
31
  const { InstrumentationType: { RULE } } = require('../../../constants');
33
32
 
@@ -36,6 +35,7 @@ module.exports = function(core) {
36
35
  depHooks,
37
36
  patcher,
38
37
  assess: {
38
+ inspect, // todo: remove
39
39
  getSourceContext,
40
40
  eventFactory: { createSinkEvent },
41
41
  dataflow: {
@@ -87,7 +87,7 @@ module.exports = function(core) {
87
87
  name,
88
88
  moduleName,
89
89
  methodName: fullMethodName || methodName,
90
- context: `${name}(${join(
90
+ context: `${name}(${ArrayPrototypeJoin.call(
91
91
  args.map((a) => inspect(a.value)),
92
92
  ', '
93
93
  )})`,
@@ -17,8 +17,7 @@
17
17
 
18
18
  const {
19
19
  isString,
20
- inspect,
21
- join,
20
+ ArrayPrototypeJoin,
22
21
  DataflowTag: {
23
22
  UNTRUSTED,
24
23
  CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
@@ -47,6 +46,7 @@ module.exports = function (core) {
47
46
  logger,
48
47
  patcher,
49
48
  assess: {
49
+ inspect, // todo: remove
50
50
  getSourceContext,
51
51
  eventFactory: { createSinkEvent },
52
52
  dataflow: {
@@ -114,7 +114,7 @@ module.exports = function (core) {
114
114
  });
115
115
  const event = createSinkEvent({
116
116
  name,
117
- context: `${name}(${join(
117
+ context: `${name}(${ArrayPrototypeJoin.call(
118
118
  args.map((a) => a.inspectedValue),
119
119
  ', '
120
120
  )})`,
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  Rule: { UNVALIDATED_REDIRECT: ruleId },
21
20
  DataflowTag: {
@@ -46,6 +45,7 @@ module.exports = function(core) {
46
45
  patcher,
47
46
  config,
48
47
  assess: {
48
+ inspect, // todo: remove
49
49
  getSourceContext,
50
50
  eventFactory: { createSinkEvent },
51
51
  dataflow: {
@@ -56,7 +56,6 @@ module.exports = function(core) {
56
56
  } = core;
57
57
 
58
58
  const unvalidatedRedirect = core.assess.dataflow.sinks.hapi.unvalidatedRedirect = {};
59
- const inspect = patcher.unwrap(util.inspect);
60
59
 
61
60
  const safeTags = [
62
61
  `excluded:${ruleId}`,
@@ -17,7 +17,6 @@
17
17
 
18
18
  const Url = require('url');
19
19
  const {
20
- inspect,
21
20
  isString,
22
21
  DataflowTag: {
23
22
  UNTRUSTED,
@@ -46,6 +45,7 @@ module.exports = function(core) {
46
45
  depHooks,
47
46
  patcher,
48
47
  assess: {
48
+ inspect, // todo: remove
49
49
  getSourceContext,
50
50
  eventFactory: { createSinkEvent },
51
51
  dataflow: {
@@ -104,11 +104,12 @@ module.exports = function(core) {
104
104
  pre(data) {
105
105
  if (!getSourceContext(RULE, ruleId)) return;
106
106
 
107
- const [req] = data.args;
108
- if (!req) return;
107
+ // url <string> |<URL>
108
+ const [urlArg] = data.args;
109
+ if (!urlArg) return;
109
110
 
110
111
  ['host', 'hostname', 'localAddress', 'protocol'].forEach((key) => {
111
- const value = getValueFromReq(req, key);
112
+ const value = getValueFromReq(urlArg, key);
112
113
  if (!value) return;
113
114
 
114
115
  const strInfo = tracker.getData(value);
@@ -116,7 +117,7 @@ module.exports = function(core) {
116
117
 
117
118
  if (containsTrustedLib(strInfo.stack)) return;
118
119
 
119
- const arg0 = isString(req) ? req : inspect(req);
120
+ const arg0 = isString(urlArg) ? urlArg : inspect(urlArg);
120
121
  const idx = arg0.indexOf(value);
121
122
  const urlTags = createAppendTags({}, strInfo.tags, idx);
122
123
 
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  DataflowTag: {
21
20
  UNTRUSTED,
@@ -47,6 +46,7 @@ module.exports = function(core) {
47
46
  patcher,
48
47
  config,
49
48
  assess: {
49
+ inspect, // todo: remove
50
50
  getSourceContext,
51
51
  eventFactory: { createSinkEvent },
52
52
  dataflow: {
@@ -56,7 +56,7 @@ module.exports = function(core) {
56
56
  },
57
57
  } = core;
58
58
 
59
- const inspect = patcher.unwrap(util.inspect);
59
+
60
60
  const safeTags = [
61
61
  `excluded:${ruleId}`,
62
62
  CUSTOM_ENCODED,
@@ -23,7 +23,6 @@ const {
23
23
  ALPHANUM_SPACE_HYPHEN,
24
24
  LIMITED_CHARS,
25
25
  },
26
- inspect
27
26
  } = require('@contrast/common');
28
27
  const { InstrumentationType: { RULE } } = require('../../../constants');
29
28
  const { patchType } = require('../common');
@@ -48,6 +47,7 @@ module.exports = function(core) {
48
47
  depHooks,
49
48
  patcher,
50
49
  assess: {
50
+ inspect, // todo: remove
51
51
  getSourceContext,
52
52
  eventFactory: { createSinkEvent },
53
53
  dataflow: {
@@ -14,7 +14,6 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
- const util = require('util');
18
17
  const {
19
18
  traverseValues,
20
19
  Rule: { NOSQL_INJECTION_MONGO: ruleId },
@@ -51,6 +50,7 @@ module.exports = function (core) {
51
50
  logger,
52
51
  patcher,
53
52
  assess: {
53
+ inspect, // todo: remove
54
54
  getSourceContext,
55
55
  eventFactory: { createSinkEvent },
56
56
  dataflow: {
@@ -61,7 +61,6 @@ module.exports = function (core) {
61
61
  } = core;
62
62
 
63
63
  const instr = core.assess.dataflow.sinks.marsdb = {};
64
- const inspect = patcher.unwrap(util.inspect);
65
64
 
66
65
  function getVulnerabilityInfo(query) {
67
66
  let vulnInfo = null;
@@ -28,7 +28,6 @@ const {
28
28
  isNonEmptyObject,
29
29
  traverseValues,
30
30
  isString,
31
- inspect
32
31
  } = require('@contrast/common');
33
32
  const { InstrumentationType: { RULE } } = require('../../../constants');
34
33
  const utils = require('../../tag-utils');
@@ -83,6 +82,7 @@ module.exports = function (core) {
83
82
  logger,
84
83
  patcher,
85
84
  assess: {
85
+ inspect, // todo: remove
86
86
  getSourceContext,
87
87
  eventFactory: { createSinkEvent },
88
88
  dataflow: {
@@ -28,7 +28,6 @@ const {
28
28
  UNTRUSTED
29
29
  },
30
30
  isString,
31
- inspect,
32
31
  } = require('@contrast/common');
33
32
  const { InstrumentationType: { RULE } } = require('../../../constants');
34
33
 
@@ -54,6 +53,7 @@ module.exports = function(core) {
54
53
  depHooks,
55
54
  patcher,
56
55
  assess: {
56
+ inspect, // todo: remove
57
57
  getSourceContext,
58
58
  eventFactory: { createSinkEvent },
59
59
  dataflow: {
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  DataflowTag: {
21
20
  CUSTOM_VALIDATED,
@@ -43,6 +42,7 @@ module.exports = function(core) {
43
42
  depHooks,
44
43
  patcher,
45
44
  assess: {
45
+ inspect, // todo: remove
46
46
  getSourceContext,
47
47
  eventFactory: { createSinkEvent },
48
48
  dataflow: {
@@ -60,8 +60,6 @@ module.exports = function(core) {
60
60
  CUSTOM_ENCODED,
61
61
  ];
62
62
 
63
- const inspect = patcher.unwrap(util.inspect);
64
-
65
63
  const postgres = core.assess.dataflow.sinks.postgres = {};
66
64
 
67
65
  const preHook = (methodSignature) => (data) => {
@@ -0,0 +1,208 @@
1
+ /*
2
+ * Copyright: 2024 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
+ Rule: { UNVALIDATED_REDIRECT: ruleId },
20
+ DataflowTag: {
21
+ UNTRUSTED,
22
+ CUSTOM_ENCODED,
23
+ CUSTOM_VALIDATED,
24
+ HTML_ENCODED,
25
+ LIMITED_CHARS,
26
+ URL_ENCODED,
27
+ },
28
+ isString,
29
+ ArrayPrototypeJoin,
30
+ } = require('@contrast/common');
31
+ const { InstrumentationType: { RULE } } = require('../../../constants');
32
+ const { createAppendTags } = require('../../tag-utils');
33
+ const { patchType } = require('../common');
34
+
35
+ /**
36
+ * @param {{
37
+ * assess: import('@contrast/assess').Assess,
38
+ * config: import('@contrast/config').Config,
39
+ * logger: import('@contrast/logger').Logger,
40
+ * }} core
41
+ * @returns {import('@contrast/common').Installable}
42
+ */
43
+ module.exports = function(core) {
44
+ const {
45
+ depHooks,
46
+ patcher,
47
+ assess: {
48
+ getSourceContext,
49
+ eventFactory: { createSinkEvent },
50
+ dataflow: {
51
+ tracker,
52
+ sinks: { isVulnerable, reportFindings /*reportSafePositive*/ }
53
+ },
54
+ inspect,
55
+ },
56
+ } = core;
57
+
58
+ const OPTION_FIELDS = ['hostname', 'pathname'];
59
+ const SAFE_TAGS = [
60
+ `excluded:${ruleId}`,
61
+ CUSTOM_ENCODED,
62
+ CUSTOM_VALIDATED,
63
+ HTML_ENCODED,
64
+ LIMITED_CHARS,
65
+ URL_ENCODED,
66
+ ];
67
+
68
+ function createCommonEventData(data) {
69
+ return {
70
+ name: 'restify.Response.redirect',
71
+ moduleName: 'restify',
72
+ methodName: 'Response.redirect',
73
+ object: {
74
+ tracked: false,
75
+ value: 'restify.Response',
76
+ },
77
+ result: {
78
+ tracked: false,
79
+ value: undefined,
80
+ },
81
+ stacktraceOpts: {
82
+ constructorOpt: data.hooked,
83
+ prependFrames: [data.orig]
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Called when tracked values are on options object argument.
90
+ * Will return the args coerced to strings values, and the adjusted ranges.
91
+ * @returns {}
92
+ */
93
+ function getAdjustedValues(origArgs, vulns, vulnArgIdx) {
94
+ let result = '{...';
95
+ let tags;
96
+
97
+ vulns.forEach((vuln, i) => {
98
+ const sep = i == 0 ? '' : ',';
99
+ result += `${sep}'${vuln.path[vuln.path.length - 1]}':'`;
100
+ tags = createAppendTags(tags, vuln.strInfo.tags, result.length);
101
+ result += `${vuln.strInfo.value}'`;
102
+ });
103
+
104
+ result += '...}';
105
+
106
+ const args = origArgs.map((a, i) => i == vulnArgIdx ?
107
+ { tracked: true, value: result } :
108
+ { tracked: false, value: a?.constructor?.name || typeof a });
109
+
110
+ return { args, tags };
111
+ }
112
+
113
+ return core.assess.dataflow.sinks.restify = {
114
+ install() {
115
+ // restify adds functionality to the built-in response via this patch function.
116
+ // once it returns the request, it'll have been decorated with redirect() method.
117
+ depHooks.resolve({ name: 'restify', file: 'lib/response.js' }, (responsePatch) => patcher.patch(responsePatch, {
118
+ name: 'restify.response.patch',
119
+ patchType,
120
+ post(data) {
121
+ patcher.patch(data.args[0].prototype, 'redirect', {
122
+ patchType,
123
+ name: 'restify.Response.redirect',
124
+ pre(data) {
125
+ if (!getSourceContext(RULE, ruleId)) return;
126
+
127
+ let vulnArgIdx;
128
+ let vulnArgIsString = true;
129
+
130
+ if (isString(data.args[0])) {
131
+ vulnArgIdx = 0; // res.redirect(url, next)
132
+ } else if (isString(data.args[1])) {
133
+ vulnArgIdx = 1; // res.redirect(code, url, next)
134
+ } else if (data.args[0] && typeof data.args[0] === 'object') {
135
+ vulnArgIdx = 0; // res.redirect(options, next)
136
+ vulnArgIsString = false;
137
+ } else {
138
+ return; // unknown call signature
139
+ }
140
+
141
+ if (vulnArgIsString) {
142
+ const strInfo = tracker.getData(data.args[vulnArgIdx]);
143
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
144
+ const args = data.args.map((arg, idx) => ({
145
+ tracked: idx === vulnArgIdx,
146
+ value: idx === vulnArgIdx ?
147
+ inspect(strInfo.value) :
148
+ (arg?.constructor?.name ?? typeof strInfo.value)
149
+ }));
150
+
151
+ const sinkEvent = createSinkEvent({
152
+ args,
153
+ context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
154
+ history: [strInfo],
155
+ tags: createAppendTags(null, strInfo.tags, 1), // offset by 1 for formatting as string
156
+ source: `P${vulnArgIdx}`,
157
+ ...createCommonEventData(data),
158
+ });
159
+
160
+ if (sinkEvent) {
161
+ reportFindings({ ruleId, sinkEvent });
162
+ }
163
+ }
164
+ } else {
165
+ const options = data.args[vulnArgIdx];
166
+ const vulns = [];
167
+
168
+ for (const option of OPTION_FIELDS) {
169
+ const strInfo = tracker.getData(options?.[option]);
170
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
171
+ vulns.push({ path: [option], strInfo });
172
+ }
173
+ }
174
+
175
+ if (typeof options.query == 'object') {
176
+ for (const [key, value] of Object.entries(options.query)) {
177
+ const strInfo = tracker.getData(value);
178
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
179
+ vulns.push({ path: ['query', key], strInfo });
180
+ }
181
+ }
182
+ }
183
+
184
+ if (vulns.length) {
185
+ // so events are not duplicated
186
+ const history = Array.from(new Set(vulns.map((v) => v.strInfo)));
187
+ const { tags, args } = getAdjustedValues(data.args, vulns, vulnArgIdx);
188
+ const sinkEvent = createSinkEvent({
189
+ args,
190
+ context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
191
+ history,
192
+ tags,
193
+ source: 'P0',
194
+ ...createCommonEventData(data),
195
+
196
+ });
197
+ if (sinkEvent) {
198
+ reportFindings({ ruleId, sinkEvent });
199
+ }
200
+ }
201
+ }
202
+ }
203
+ });
204
+ }
205
+ }));
206
+ }
207
+ };
208
+ };
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  Rule: { SQL_INJECTION: ruleId },
21
20
  DataflowTag: {
@@ -42,6 +41,7 @@ module.exports = function (core) {
42
41
  patcher,
43
42
  config,
44
43
  assess: {
44
+ inspect, // todo: remove
45
45
  getSourceContext,
46
46
  eventFactory: { createSinkEvent },
47
47
  dataflow: {
@@ -59,7 +59,6 @@ module.exports = function (core) {
59
59
  CUSTOM_ENCODED
60
60
  ];
61
61
  const requiredTag = UNTRUSTED;
62
- const inspect = patcher.unwrap(util.inspect);
63
62
 
64
63
  const sequelize = (core.assess.dataflow.sinks.sequelize = {});
65
64
 
@@ -25,11 +25,10 @@ const {
25
25
  LIMITED_CHARS,
26
26
  },
27
27
  Rule: { UNSAFE_CODE_EXECUTION: ruleId },
28
- inspect,
29
28
  isNonEmptyObject,
30
29
  isString,
31
- join,
32
- split,
30
+ ArrayPrototypeJoin,
31
+ StringPrototypeSplit,
33
32
  traverseValues,
34
33
  } = require('@contrast/common');
35
34
  const { InstrumentationType: { RULE } } = require('../../../constants');
@@ -56,6 +55,7 @@ module.exports = function (core) {
56
55
  depHooks,
57
56
  patcher,
58
57
  assess: {
58
+ inspect, // todo: remove
59
59
  getSourceContext,
60
60
  eventFactory: { createSinkEvent },
61
61
  dataflow: {
@@ -144,7 +144,7 @@ module.exports = function (core) {
144
144
  function around(next, { args: origArgs, hooked, orig, name }) {
145
145
  if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return next();
146
146
 
147
- const methodPath = split(name, '.');
147
+ const methodPath = StringPrototypeSplit.call(name, '.');
148
148
  const method = methodPath[methodPath.length - 1];
149
149
  const idxsToCheck = method === 'runInNewContext' ? [0, 1] : [0];
150
150
  const argsInfo = accumulateArgsInfo(origArgs, idxsToCheck);
@@ -204,7 +204,7 @@ module.exports = function (core) {
204
204
  if (vulnerableArg) {
205
205
  const event = createSinkEvent({
206
206
  name,
207
- context: `${name}(${join(
207
+ context: `${name}(${ArrayPrototypeJoin.call(
208
208
  argsInfo.map((a) => a.ctxValue),
209
209
  ', '
210
210
  )})`,