@contrast/assess 1.7.0 → 1.9.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 (68) hide show
  1. package/lib/dataflow/event-factory.js +17 -13
  2. package/lib/dataflow/propagation/index.js +5 -0
  3. package/lib/dataflow/propagation/install/JSON/index.js +34 -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 +292 -0
  7. package/lib/dataflow/propagation/install/array-prototype-join.js +21 -14
  8. package/lib/dataflow/propagation/install/buffer.js +81 -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/pug-runtime-escape.js +9 -2
  26. package/lib/dataflow/propagation/install/querystring/parse.js +11 -9
  27. package/lib/dataflow/propagation/install/sequelize.js +6 -3
  28. package/lib/dataflow/propagation/install/sql-template-strings.js +9 -2
  29. package/lib/dataflow/propagation/install/string/concat.js +8 -2
  30. package/lib/dataflow/propagation/install/string/format-methods.js +7 -2
  31. package/lib/dataflow/propagation/install/string/html-methods.js +15 -5
  32. package/lib/dataflow/propagation/install/string/match.js +14 -9
  33. package/lib/dataflow/propagation/install/string/replace.js +22 -14
  34. package/lib/dataflow/propagation/install/string/slice.js +13 -5
  35. package/lib/dataflow/propagation/install/string/split.js +15 -11
  36. package/lib/dataflow/propagation/install/string/substring.js +16 -6
  37. package/lib/dataflow/propagation/install/string/trim.js +3 -0
  38. package/lib/dataflow/propagation/install/unescape.js +9 -2
  39. package/lib/dataflow/propagation/install/url/domain-parsers.js +7 -2
  40. package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
  41. package/lib/dataflow/sinks/install/child-process.js +116 -50
  42. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +6 -3
  43. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +7 -4
  44. package/lib/dataflow/sinks/install/fs.js +44 -12
  45. package/lib/dataflow/sinks/install/http.js +5 -2
  46. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +7 -4
  47. package/lib/dataflow/sinks/install/marsdb.js +3 -0
  48. package/lib/dataflow/sinks/install/mongodb.js +249 -149
  49. package/lib/dataflow/sinks/install/mssql.js +9 -2
  50. package/lib/dataflow/sinks/install/mysql.js +9 -4
  51. package/lib/dataflow/sinks/install/postgres.js +6 -3
  52. package/lib/dataflow/sinks/install/sequelize.js +7 -5
  53. package/lib/dataflow/sinks/install/sqlite3.js +7 -3
  54. package/lib/dataflow/sources/handler.js +141 -26
  55. package/lib/dataflow/sources/index.js +2 -7
  56. package/lib/dataflow/sources/install/body-parser1.js +19 -6
  57. package/lib/dataflow/sources/install/express/index.js +4 -1
  58. package/lib/dataflow/sources/install/express/params.js +81 -0
  59. package/lib/dataflow/sources/install/express/parsedUrl.js +87 -0
  60. package/lib/dataflow/sources/install/http.js +33 -19
  61. package/lib/dataflow/sources/install/querystring.js +75 -0
  62. package/lib/dataflow/tag-utils.js +92 -1
  63. package/lib/dataflow/tracker.js +6 -6
  64. package/lib/index.js +2 -0
  65. package/lib/response-scanning/handlers/utils.js +2 -2
  66. package/lib/session-configuration/index.js +34 -0
  67. package/lib/session-configuration/install/http.js +79 -0
  68. package/package.json +2 -2
@@ -68,6 +68,57 @@ function atomicSubset(tags, subsetStart, len) {
68
68
  return ret;
69
69
  }
70
70
 
71
+ function atomicMerge(firstTagRanges, secondTagRanges) {
72
+ const mergedRanges = [];
73
+ let i = 0;
74
+ let j = 0;
75
+
76
+ while (i < firstTagRanges.length && j < secondTagRanges.length) {
77
+ const start1 = firstTagRanges[i];
78
+ const end1 = firstTagRanges[i + 1];
79
+ const start2 = secondTagRanges[j];
80
+ const end2 = secondTagRanges[j + 1];
81
+
82
+ if (end1 < start2) {
83
+ mergedRanges.push(start1, end1);
84
+ i += 2;
85
+ } else if (end2 < start1) {
86
+ mergedRanges.push(start2, end2);
87
+ j += 2;
88
+ } else {
89
+ const mergedStart = Math.min(start1, start2);
90
+ const mergedEnd = Math.max(end1, end2);
91
+ mergedRanges.push(mergedStart, mergedEnd);
92
+ i += 2;
93
+ j += 2;
94
+ }
95
+ }
96
+
97
+ while (i < firstTagRanges.length) {
98
+ mergedRanges.push(firstTagRanges[i], firstTagRanges[i + 1]);
99
+ i += 2;
100
+ }
101
+
102
+ while (j < secondTagRanges.length) {
103
+ mergedRanges.push(secondTagRanges[j], secondTagRanges[j + 1]);
104
+ j += 2;
105
+ }
106
+
107
+ // Merge adjacent ranges
108
+ const finalMergedRanges = [];
109
+ for (let k = 0; k < mergedRanges.length; k += 2) {
110
+ const start = mergedRanges[k];
111
+ let end = mergedRanges[k + 1];
112
+ while (k + 2 < mergedRanges.length && mergedRanges[k + 2] - end === 1) {
113
+ end = mergedRanges[k + 3];
114
+ k += 2;
115
+ }
116
+ finalMergedRanges.push(start, end);
117
+ }
118
+
119
+ return finalMergedRanges;
120
+ }
121
+
71
122
  function createAppendTags(firstTags, secondTags, offset) {
72
123
  const ret = Object.create(null);
73
124
  const firstTagsObject = ensureObject(firstTags);
@@ -83,6 +134,29 @@ function createAppendTags(firstTags, secondTags, offset) {
83
134
  return Object.keys(ret).length ? ret : null;
84
135
  }
85
136
 
137
+ function createOverlappingTags(tags, startIndex, endIndex) {
138
+ const overlappingTags = {};
139
+
140
+ Object.entries(tags).forEach(([tag, tagRanges]) => {
141
+ const overlappingRanges = [];
142
+
143
+ for (let i = 0; i < tagRanges.length; i += 2) {
144
+ const start = tagRanges[i];
145
+ const end = tagRanges[i + 1];
146
+
147
+ if (end >= startIndex && start <= endIndex) {
148
+ overlappingRanges.push([start, end]);
149
+ }
150
+ }
151
+
152
+ if (overlappingRanges.length > 0) {
153
+ overlappingTags[tag] = overlappingRanges;
154
+ }
155
+ });
156
+
157
+ return overlappingTags;
158
+ }
159
+
86
160
  /**
87
161
  * assumes:
88
162
  * - no mutation of arguments
@@ -115,8 +189,25 @@ function createFullLengthCopyTags(tags, resultLength) {
115
189
  return Object.keys(ret).length ? ret : null;
116
190
  }
117
191
 
192
+ function createMergedTags(firstTags, secondTags) {
193
+ const ret = Object.create(null);
194
+ const firstTagsObject = ensureObject(firstTags);
195
+ const secondTagsObject = ensureObject(secondTags);
196
+ const tagNames = new Set([...Object.keys(firstTagsObject), ...Object.keys(secondTagsObject)]);
197
+
198
+ for (const tagName of tagNames) {
199
+ const newTagRanges = atomicMerge(ensureTagsImmutable(firstTagsObject, tagName), ensureTagsImmutable(secondTagsObject, tagName));
200
+
201
+ newTagRanges.length && (ret[tagName] = newTagRanges);
202
+ }
203
+
204
+ return Object.keys(ret).length ? ret : null;
205
+ }
206
+
118
207
  module.exports = {
119
208
  createSubsetTags,
120
209
  createAppendTags,
121
- createFullLengthCopyTags
210
+ createFullLengthCopyTags,
211
+ createMergedTags,
212
+ createOverlappingTags
122
213
  };
@@ -34,7 +34,8 @@ module.exports = function tracker(core) {
34
34
 
35
35
  function getData(value) {
36
36
  if (typeof value === 'string') {
37
- return distringuish.getProperties(value);
37
+ const props = distringuish.getProperties(value);
38
+ return props?.untracked ? null : props;
38
39
  }
39
40
 
40
41
  return objMap.get(value) || null;
@@ -126,11 +127,10 @@ module.exports = function tracker(core) {
126
127
  if (typeof value === 'string') {
127
128
  const props = distringuish.getProperties(value);
128
129
  if (props) {
129
- Object.assign(props, {
130
- history: [],
131
- tags: {},
132
- });
133
- delete props.resultTracked;
130
+ for (const key of Object.keys(props)) {
131
+ delete props[key];
132
+ }
133
+ props.untracked = true;
134
134
  }
135
135
  return distringuish.internalize(value);
136
136
  }
package/lib/index.js CHANGED
@@ -16,6 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  const { callChildComponentMethodsSync } = require('@contrast/common');
19
+ const sessionConfiguration = require('./session-configuration');
19
20
  const dataflow = require('./dataflow');
20
21
  const responseScanning = require('./response-scanning');
21
22
 
@@ -26,6 +27,7 @@ module.exports = function assess(core) {
26
27
 
27
28
  // Does this order matter? Probably not
28
29
  // 1. dataflow
30
+ sessionConfiguration(core);
29
31
  dataflow(core);
30
32
  responseScanning(core);
31
33
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { join, substring, toLowerCase, split, trim } = require('@contrast/common');
18
+ const { join, substring, toLowerCase, split, trim, replace } = require('@contrast/common');
19
19
 
20
20
  //
21
21
  // General HTML utils
@@ -32,7 +32,7 @@ const reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
32
32
 
33
33
  function escapeHtml(string) {
34
34
  return (string && reHasUnescapedHtml.test(string))
35
- ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr])
35
+ ? replace(string, reUnescapedHtml, (chr) => htmlEscapes[chr])
36
36
  : (string || '');
37
37
  }
38
38
 
@@ -0,0 +1,34 @@
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 { callChildComponentMethodsSync, Event } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const { messages } = core;
22
+ const sessionConfiguration = core.assess.sessionConfiguration = {
23
+ reportFindings(_sourceContext, vulnerabilityMetadata) {
24
+ messages.emit(Event.ASSESS_SESSION_CONFIGURATION_FINDING, vulnerabilityMetadata);
25
+ },
26
+ };
27
+ require('./install/http')(core);
28
+
29
+ sessionConfiguration.install = function() {
30
+ callChildComponentMethodsSync(sessionConfiguration, 'install');
31
+ };
32
+
33
+ return sessionConfiguration;
34
+ };
@@ -0,0 +1,79 @@
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 { SessionConfigurationRule, split, toLowerCase } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ depHooks,
23
+ patcher,
24
+ scopes: { sources },
25
+ assess: {
26
+ sessionConfiguration: {
27
+ reportFindings
28
+ }
29
+ }
30
+ } = core;
31
+ const http = core.assess.sessionConfiguration.httpInstrumentation = {};
32
+
33
+ const patchType = 'session-configuration';
34
+
35
+ http.install = function() {
36
+ [
37
+ { name: 'http', responseObj: 'ServerResponse' },
38
+ { name: 'https', responseObj: 'ServerResponse' },
39
+ { name: 'http2', responseObj: 'Http2ServerResponse' }
40
+ ].forEach(({ name, responseObj }) => {
41
+ depHooks.resolve({ name }, (module) => {
42
+ patcher.patch(module[responseObj].prototype, 'setHeader', {
43
+ name: `${name}.${responseObj}.prototype.setHeader`,
44
+ patchType,
45
+ post(data) {
46
+ const sourceContext = sources.getStore()?.assess;
47
+ if (!sourceContext) return;
48
+
49
+ const [key, val] = data.args;
50
+ if (key === 'Set-Cookie') {
51
+ const [cookies] = val;
52
+ const parsedCookies = split(toLowerCase(cookies), '; ');
53
+
54
+ if (!parsedCookies.includes('httponly')) {
55
+ reportFindings(sourceContext, {
56
+ ruleId: SessionConfigurationRule.HTTPONLY,
57
+ props: {
58
+ evidence: cookies
59
+ }
60
+ });
61
+ }
62
+
63
+ if (!parsedCookies.includes('secure')) {
64
+ reportFindings(sourceContext, {
65
+ ruleId: SessionConfigurationRule.SECURE_FLAG_MISSING,
66
+ props: {
67
+ evidence: cookies
68
+ }
69
+ });
70
+ }
71
+ }
72
+ }
73
+ });
74
+ });
75
+ });
76
+ };
77
+
78
+ return http;
79
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@contrast/distringuish": "^4.1.0",
17
17
  "@contrast/scopes": "1.4.0",
18
- "@contrast/common": "1.10.0",
18
+ "@contrast/common": "1.12.0",
19
19
  "parseurl": "^1.3.3"
20
20
  }
21
21
  }