@contrast/assess 1.1.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 (111) hide show
  1. package/coverage/lcov-report/base.css +224 -0
  2. package/coverage/lcov-report/block-navigation.js +87 -0
  3. package/coverage/lcov-report/favicon.png +0 -0
  4. package/coverage/lcov-report/index.html +266 -0
  5. package/coverage/lcov-report/lib/dataflow/common.js.html +154 -0
  6. package/coverage/lcov-report/lib/dataflow/event-factory.js.html +598 -0
  7. package/coverage/lcov-report/lib/dataflow/index.html +191 -0
  8. package/coverage/lcov-report/lib/dataflow/index.js.html +190 -0
  9. package/coverage/lcov-report/lib/dataflow/propagation/common.js.html +145 -0
  10. package/coverage/lcov-report/lib/dataflow/propagation/index.html +131 -0
  11. package/coverage/lcov-report/lib/dataflow/propagation/index.js.html +190 -0
  12. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.html +131 -0
  13. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.js.html +184 -0
  14. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/plus.js.html +397 -0
  15. package/coverage/lcov-report/lib/dataflow/propagation/install/string/concat.js.html +478 -0
  16. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.html +176 -0
  17. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.js.html +202 -0
  18. package/coverage/lcov-report/lib/dataflow/propagation/install/string/replace.js.html +496 -0
  19. package/coverage/lcov-report/lib/dataflow/propagation/install/string/substring.js.html +415 -0
  20. package/coverage/lcov-report/lib/dataflow/propagation/install/string/trim.js.html +403 -0
  21. package/coverage/lcov-report/lib/dataflow/signatures.js.html +5923 -0
  22. package/coverage/lcov-report/lib/dataflow/sinks/common.js.html +145 -0
  23. package/coverage/lcov-report/lib/dataflow/sinks/index.html +131 -0
  24. package/coverage/lcov-report/lib/dataflow/sinks/index.js.html +211 -0
  25. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.html +146 -0
  26. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.js.html +172 -0
  27. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js.html +319 -0
  28. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/xss.js.html +721 -0
  29. package/coverage/lcov-report/lib/dataflow/sinks/install/http.js.html +340 -0
  30. package/coverage/lcov-report/lib/dataflow/sinks/install/index.html +116 -0
  31. package/coverage/lcov-report/lib/dataflow/sources/common.js.html +145 -0
  32. package/coverage/lcov-report/lib/dataflow/sources/index.html +131 -0
  33. package/coverage/lcov-report/lib/dataflow/sources/index.js.html +226 -0
  34. package/coverage/lcov-report/lib/dataflow/sources/install/fastify.js.html +379 -0
  35. package/coverage/lcov-report/lib/dataflow/sources/install/http.js.html +502 -0
  36. package/coverage/lcov-report/lib/dataflow/sources/install/index.html +146 -0
  37. package/coverage/lcov-report/lib/dataflow/sources/install/qs.js.html +322 -0
  38. package/coverage/lcov-report/lib/dataflow/tag-utils.js.html +418 -0
  39. package/coverage/lcov-report/lib/dataflow/tracker.js.html +466 -0
  40. package/coverage/lcov-report/lib/dataflow/utils/index.html +116 -0
  41. package/coverage/lcov-report/lib/dataflow/utils/is-vulnerable.js.html +424 -0
  42. package/coverage/lcov-report/lib/index.html +116 -0
  43. package/coverage/lcov-report/lib/index.js.html +193 -0
  44. package/coverage/lcov-report/prettify.css +1 -0
  45. package/coverage/lcov-report/prettify.js +2 -0
  46. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  47. package/coverage/lcov-report/sorter.js +196 -0
  48. package/coverage/lcov.info +4856 -0
  49. package/coverage/tmp/coverage-9548-1675168551025-0.json +1 -0
  50. package/coverage/tmp/coverage-9551-1675168550963-0.json +1 -0
  51. package/coverage/tmp/coverage-9552-1675168550969-0.json +1 -0
  52. package/coverage/tmp/coverage-9553-1675168550970-0.json +1 -0
  53. package/coverage/tmp/coverage-9554-1675168550962-0.json +1 -0
  54. package/coverage/tmp/coverage-9555-1675168550965-0.json +1 -0
  55. package/coverage/tmp/coverage-9556-1675168550964-0.json +1 -0
  56. package/coverage/tmp/coverage-9557-1675168550969-0.json +1 -0
  57. package/coverage/tmp/coverage-9558-1675168550964-0.json +1 -0
  58. package/coverage/tmp/coverage-9559-1675168550971-0.json +1 -0
  59. package/lib/dataflow/event-factory.js +256 -0
  60. package/lib/dataflow/index.js +35 -0
  61. package/lib/dataflow/propagation/common.js +26 -0
  62. package/lib/dataflow/propagation/index.js +50 -0
  63. package/lib/dataflow/propagation/install/array-prototype-join.js +125 -0
  64. package/lib/dataflow/propagation/install/contrast-methods/add.js +104 -0
  65. package/lib/dataflow/propagation/install/contrast-methods/index.js +34 -0
  66. package/lib/dataflow/propagation/install/contrast-methods/tag.js +102 -0
  67. package/lib/dataflow/propagation/install/decode-uri-component.js +88 -0
  68. package/lib/dataflow/propagation/install/ejs/escape-xml.js +89 -0
  69. package/lib/dataflow/propagation/install/ejs/index.js +30 -0
  70. package/lib/dataflow/propagation/install/encode-uri-component.js +87 -0
  71. package/lib/dataflow/propagation/install/escape-html.js +89 -0
  72. package/lib/dataflow/propagation/install/escape.js +89 -0
  73. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +89 -0
  74. package/lib/dataflow/propagation/install/mysql-connection-escape.js +111 -0
  75. package/lib/dataflow/propagation/install/pug/index.js +64 -0
  76. package/lib/dataflow/propagation/install/pug-runtime-escape.js +89 -0
  77. package/lib/dataflow/propagation/install/sql-template-strings.js +103 -0
  78. package/lib/dataflow/propagation/install/string/concat.js +108 -0
  79. package/lib/dataflow/propagation/install/string/format-methods.js +83 -0
  80. package/lib/dataflow/propagation/install/string/html-methods.js +163 -0
  81. package/lib/dataflow/propagation/install/string/index.js +38 -0
  82. package/lib/dataflow/propagation/install/string/replace.js +197 -0
  83. package/lib/dataflow/propagation/install/string/substring.js +117 -0
  84. package/lib/dataflow/propagation/install/string/trim.js +115 -0
  85. package/lib/dataflow/propagation/install/unescape.js +90 -0
  86. package/lib/dataflow/propagation/install/url/domain-parsers.js +89 -0
  87. package/lib/dataflow/propagation/install/url/index.js +33 -0
  88. package/lib/dataflow/propagation/install/validator/hooks.js +172 -0
  89. package/lib/dataflow/propagation/install/validator/index.js +28 -0
  90. package/lib/dataflow/propagation/install/validator/methods.js +82 -0
  91. package/lib/dataflow/signatures.js +2022 -0
  92. package/lib/dataflow/sinks/common.js +20 -0
  93. package/lib/dataflow/sinks/index.js +44 -0
  94. package/lib/dataflow/sinks/install/fastify/index.js +30 -0
  95. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +107 -0
  96. package/lib/dataflow/sinks/install/http.js +119 -0
  97. package/lib/dataflow/sinks/install/postgres/index.js +129 -0
  98. package/lib/dataflow/sources/common.js +20 -0
  99. package/lib/dataflow/sources/handler.js +114 -0
  100. package/lib/dataflow/sources/index.js +35 -0
  101. package/lib/dataflow/sources/install/fastify/cookie.js +61 -0
  102. package/lib/dataflow/sources/install/fastify/fastify.js +89 -0
  103. package/lib/dataflow/sources/install/fastify/index.js +31 -0
  104. package/lib/dataflow/sources/install/http.js +181 -0
  105. package/lib/dataflow/sources/install/qs.js +88 -0
  106. package/lib/dataflow/tag-utils.js +122 -0
  107. package/lib/dataflow/tracker.js +127 -0
  108. package/lib/dataflow/utils/is-safe-content-type.js +30 -0
  109. package/lib/dataflow/utils/is-vulnerable.js +113 -0
  110. package/lib/index.js +36 -0
  111. package/package.json +21 -0
@@ -0,0 +1,163 @@
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
+ createAppendTags
20
+ } = require('../../../tag-utils');
21
+ const { patchType } = require('../../common');
22
+ const htmlTagsLengths = {
23
+ anchor: 11,
24
+ big: 5,
25
+ blink: 7,
26
+ italics: 3,
27
+ small: 7,
28
+ strike: 8,
29
+ sub: 5,
30
+ fixed: 4
31
+ };
32
+
33
+ module.exports = function(core) {
34
+ const {
35
+ scopes: { sources, instrumentation },
36
+ patcher,
37
+ assess: {
38
+ dataflow: { tracker, eventFactory: { createPropagationEvent } }
39
+ }
40
+ } = core;
41
+ function adjustTags(method, objTags, argLength, argTags = null) {
42
+ let offset;
43
+ if (method === 'anchor') {
44
+ let newArgTags = {};
45
+ offset = htmlTagsLengths[method] + argLength;
46
+ if (argTags) {
47
+ newArgTags = createAppendTags({}, argTags, 9);
48
+ }
49
+
50
+ return createAppendTags(newArgTags, objTags, offset);
51
+ }
52
+
53
+ offset = htmlTagsLengths[method];
54
+
55
+ return createAppendTags({}, objTags, offset);
56
+ }
57
+
58
+ return core.assess.dataflow.propagation.stringInstrumentation.htmlMethods = {
59
+ install() {
60
+ patcher.patch(String.prototype, 'anchor', {
61
+ name: 'String.prototype.anchor',
62
+ patchType,
63
+ post(data) {
64
+ const { args, obj, result, hooked, orig } = data;
65
+ if (!result || !sources.getStore()?.assess || instrumentation.isLocked()) return;
66
+
67
+ const objInfo = tracker.getData(obj);
68
+ const history = objInfo ? new Set([objInfo]) : new Set();
69
+
70
+ const arg = args[0]?.toString();
71
+ const argInfo = arg && tracker.getData(arg);
72
+ if (argInfo) {
73
+ history.add(argInfo);
74
+ }
75
+
76
+ if (history.size) {
77
+ const event = createPropagationEvent({
78
+ name: 'String.prototype.anchor',
79
+ object: {
80
+ value: objInfo?.value || String(obj),
81
+ isTracked: !!objInfo
82
+ },
83
+ result: {
84
+ value: result,
85
+ isTracked: true
86
+ },
87
+ args: [
88
+ { value: arg, isTracked: !!argInfo }
89
+ ],
90
+ tags: adjustTags('anchor', objInfo?.tags, `${arg}`.length, argInfo?.tags),
91
+ history: Array.from(history),
92
+ source: objInfo ? (history.size > 1 ? 'A' : 'O') : 'P',
93
+ target: 'R',
94
+ stacktraceOpts: {
95
+ constructorOpt: hooked,
96
+ prependFrames: [orig]
97
+ },
98
+ });
99
+
100
+ if (!event) return;
101
+
102
+ const { extern } = tracker.track(result, event);
103
+
104
+ if (extern) {
105
+ data.result = extern;
106
+ }
107
+ }
108
+ },
109
+ });
110
+
111
+ ['big', 'blink', 'italics', 'small', 'strike', 'sub', 'fixed'].forEach((method) => {
112
+ patcher.patch(String.prototype, method, {
113
+ name: `String.prototype.${method}`,
114
+ patchType,
115
+ post(data) {
116
+ const { obj, result, hooked, orig } = data;
117
+ if (!result || !sources.getStore()?.assess || instrumentation.isLocked()) return;
118
+
119
+ const objInfo = tracker.getData(obj);
120
+
121
+ if (!objInfo) return;
122
+
123
+ const history = [objInfo];
124
+
125
+ const event = createPropagationEvent({
126
+ name: `String.prototype.${method}`,
127
+ object: {
128
+ value: objInfo.value,
129
+ isTracked: true
130
+ },
131
+ result: {
132
+ value: result,
133
+ isTracked: true
134
+ },
135
+ args: [],
136
+ tags: adjustTags(method, objInfo.tags),
137
+ history,
138
+ source: 'O',
139
+ target: 'R',
140
+ stacktraceOpts: {
141
+ constructorOpt: hooked,
142
+ prependFrames: [orig]
143
+ },
144
+ });
145
+
146
+ if (!event) return;
147
+
148
+ const { extern } = tracker.track(result, event);
149
+
150
+ if (extern) {
151
+ data.result = extern;
152
+ }
153
+ }
154
+ });
155
+ });
156
+ },
157
+ uninstall() {
158
+ ['anchor', 'big', 'blink', 'italics', 'small', 'strike', 'sub', 'fixed'].forEach((method) => {
159
+ String.prototype[method] = patcher.unwrap(String.prototype[method]);
160
+ });
161
+ },
162
+ };
163
+ };
@@ -0,0 +1,38 @@
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 } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const stringInstrumentation = core.assess.dataflow.propagation.stringInstrumentation = {
22
+ install() {
23
+ callChildComponentMethodsSync(stringInstrumentation, 'install');
24
+ },
25
+ uninstall() {
26
+ callChildComponentMethodsSync(stringInstrumentation, 'uninstall');
27
+ }
28
+ };
29
+
30
+ require('./concat')(core);
31
+ require('./replace')(core);
32
+ require('./substring')(core);
33
+ require('./trim')(core);
34
+ require('./html-methods')(core);
35
+ require('./format-methods')(core);
36
+
37
+ return stringInstrumentation;
38
+ };
@@ -0,0 +1,197 @@
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('../../common');
19
+ const { createSubsetTags, createAppendTags } = require('../../../tag-utils');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ patcher,
24
+ assess: {
25
+ dataflow: { tracker, eventFactory: { createPropagationEvent } }
26
+ },
27
+ scopes: { sources, instrumentation }
28
+ } = core;
29
+
30
+ function parseArgs(args) {
31
+ // [match, p1, p2, ..., matchIdx, str, ?groups]
32
+ const match = args[0];
33
+ const len = args.length;
34
+ const lastEl = args[len - 1];
35
+ const hasNamedGroup = typeof lastEl === 'object';
36
+ const captureGroups = args.slice(1, hasNamedGroup ? -3 : -2);
37
+ const matchIdx = args[hasNamedGroup ? len - 3 : len - 2];
38
+ const str = hasNamedGroup ? args[len - 2] : lastEl;
39
+ const namedGroups = hasNamedGroup ? lastEl : null;
40
+ return { match, captureGroups, matchIdx, str, namedGroups };
41
+ }
42
+ function replaceSpecialCharacters(replacement, { captureGroups, match, str, namedGroups }) {
43
+ const origReplace = patcher.unwrap(String.prototype.replace);
44
+ let ret = replacement;
45
+ [
46
+ {
47
+ regex: /\$\$/g,
48
+ replace: '$'
49
+ },
50
+ {
51
+ regex: /\$&/g,
52
+ replace: match
53
+ },
54
+ {
55
+ regex: /\$`/g,
56
+ replace: str.substring(0, str.indexOf(match))
57
+ },
58
+ {
59
+ regex: /\$'/g,
60
+ replace: str.substring(str.indexOf(match) + match.length, str.length)
61
+ }
62
+ ].forEach(({ regex, replace }) => {
63
+ if (ret && ret.match(regex)) {
64
+ // If the match string is tracked, we can actually use the patched replace
65
+ // to keep track of its tag ranges
66
+ if (tracker.getData(replace)) {
67
+ ret = ret.replace(regex, replace);
68
+ } else {
69
+ ret = origReplace.call(ret, regex, replace);
70
+ }
71
+ }
72
+ });
73
+
74
+ const numberedGroupMatches = replacement.match(/\$[1-9][0-9]|\$[1-9]/g);
75
+ if (numberedGroupMatches) {
76
+ numberedGroupMatches.forEach((numberedGroup) => {
77
+ const group = Number(numberedGroup.substring(1));
78
+ ret = origReplace.call(ret, numberedGroup, captureGroups[group - 1]);
79
+ });
80
+ }
81
+
82
+ if (namedGroups) {
83
+ for (const name in namedGroups) {
84
+ ret = origReplace.call(ret, `$${name}`, namedGroups[name]);
85
+ }
86
+ }
87
+
88
+ return ret;
89
+ }
90
+ function getReplacementInfo(data, replacerArgs, parsedArgs) {
91
+ let replacement = data._replacementType === 'function' ?
92
+ data._replacement.call(global, ...replacerArgs) :
93
+ data._replacement;
94
+
95
+ replacement = replaceSpecialCharacters(String(replacement), parsedArgs);
96
+
97
+ data._replacementInfo = tracker.getData(replacement);
98
+ if (data._replacement) {
99
+ data._history.add(data._replacementInfo);
100
+ }
101
+ return { replacement, replacementInfo: data._replacementInfo };
102
+ }
103
+
104
+ function getReplacer(data) {
105
+ return function replacer(...args) {
106
+ const parsedArgs = parseArgs(args);
107
+ const { match, matchIdx, str } = parsedArgs;
108
+
109
+ const { _accumOffset, _accumTags } = data;
110
+ const { replacement, replacementInfo } = getReplacementInfo(data, args, parsedArgs);
111
+
112
+ const preTags = createSubsetTags(_accumTags, 0, _accumOffset + matchIdx - 1);
113
+ const postTags = createSubsetTags(_accumTags, _accumOffset + matchIdx + match.length, str.length - matchIdx - match.length);
114
+ data._accumOffset += (replacement.length - match.length);
115
+ if (preTags || postTags || replacementInfo) {
116
+ data._accumTags = createAppendTags(
117
+ createAppendTags(preTags, replacementInfo?.tags, _accumOffset + matchIdx),
118
+ postTags,
119
+ data._accumOffset + matchIdx + match.length
120
+ );
121
+ } else {
122
+ data._accumTags = {};
123
+ }
124
+ return replacement;
125
+ };
126
+ }
127
+
128
+ return core.assess.dataflow.propagation.stringInstrumentation.replace = {
129
+ install() {
130
+ patcher.patch(String.prototype, 'replace', {
131
+ name: 'String.prototype.replace',
132
+ patchType,
133
+ pre(data) {
134
+ if (!sources.getStore()?.assess || instrumentation.isLocked()) return;
135
+
136
+ // setup state
137
+ data._objInfo = tracker.getData(data.obj);
138
+ data._replacement = data.args[1];
139
+ data._replacementType = typeof data._replacement;
140
+ data._history = data._objInfo ? new Set([data._objInfo]) : new Set();
141
+ data._accumTags = data._objInfo?.tags || {};
142
+ data._accumOffset = 0;
143
+
144
+ data.args[1] = getReplacer(data);
145
+ },
146
+ post(data) {
147
+ if (
148
+ !sources.getStore()?.assess ||
149
+ instrumentation.isLocked() ||
150
+ !data.result ||
151
+ !data._accumTags?.untrusted
152
+ ) return;
153
+
154
+ const { _replacementInfo, obj, args, result, hooked, orig } = data;
155
+
156
+ const event = createPropagationEvent({
157
+ name: 'String.prototype.replace',
158
+ history: Array.from(data._history),
159
+ object: {
160
+ value: obj,
161
+ isTracked: !!data._objInfo
162
+ },
163
+ args: [{
164
+ value: args[0].toString(),
165
+ isTracked: !!tracker.getData(args[0])
166
+ },
167
+ {
168
+ value: data._replacement,
169
+ isTracked: !!_replacementInfo
170
+ }],
171
+ result: {
172
+ value: result,
173
+ isTracked: true
174
+ },
175
+ tags: data._accumTags,
176
+ stacktraceOpts: {
177
+ constructorOpt: hooked,
178
+ prependFrames: [orig]
179
+ },
180
+ source: data._objInfo ? (data._history.size > 1 ? 'A' : 'O') : 'P',
181
+ target: 'R',
182
+ });
183
+
184
+ if (event) {
185
+ const { extern } = tracker.track(result, event);
186
+ if (extern) {
187
+ data.result = extern;
188
+ }
189
+ }
190
+ }
191
+ });
192
+ },
193
+ uninstall() {
194
+ String.prototype.replace = patcher.unwrap(String.prototype.replace);
195
+ }
196
+ };
197
+ };
@@ -0,0 +1,117 @@
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 { createSubsetTags } = require('../../../tag-utils');
19
+ const { patchType } = require('../../common');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ scopes: { sources, instrumentation },
24
+ patcher,
25
+ assess: {
26
+ dataflow: { tracker, eventFactory: { createPropagationEvent } }
27
+ }
28
+ } = core;
29
+
30
+ function calculateSubsetRangeForSubstr({ args, result }) {
31
+ const startIdx = args[0] || 0;
32
+ const subsetLen = args[1] === undefined ? result.length - startIdx - 1 : args[1] - 1;
33
+
34
+ return {
35
+ startIdx,
36
+ subsetLen
37
+ };
38
+ }
39
+
40
+ function calculateSubsetRangeForSubstring({ obj, args }) {
41
+ const hasSingleArg = args[1] === undefined;
42
+ const startIdx = hasSingleArg ? args[0] : Math.min(...args);
43
+ const subsetLen = (hasSingleArg ? obj.length - args[0] : Math.abs(args[1] - args[0])) - 1;
44
+
45
+ return {
46
+ startIdx,
47
+ subsetLen
48
+ };
49
+ }
50
+
51
+ const calculateSubsetRange = {
52
+ substr: calculateSubsetRangeForSubstr,
53
+ substring: calculateSubsetRangeForSubstring
54
+ };
55
+
56
+ return core.assess.dataflow.propagation.stringInstrumentation.substring = {
57
+ install() {
58
+ ['substr', 'substring'].forEach((method) => {
59
+ patcher.patch(String.prototype, method, {
60
+ name: `String.prototype.${method}`,
61
+ patchType,
62
+ post(data) {
63
+ const { obj, args, result, name, hooked, orig } = data;
64
+ if (!result || !sources.getStore() || instrumentation.isLocked()) return;
65
+
66
+ const objInfo = tracker.getData(obj);
67
+ if (!objInfo) return;
68
+
69
+ const rInfo = tracker.getData(result);
70
+
71
+ if (rInfo) {
72
+ // this may happen w/ trackedStr.substring(0) => trackedStr
73
+ return;
74
+ }
75
+
76
+ const { startIdx, subsetLen } = calculateSubsetRange[method](data);
77
+ const tags = createSubsetTags(objInfo.tags, startIdx, subsetLen);
78
+
79
+ if (!tags) return;
80
+
81
+ const event = createPropagationEvent({
82
+ name,
83
+ history: [objInfo],
84
+ object: {
85
+ value: obj,
86
+ isTracked: true,
87
+ },
88
+ args: args.map((arg) => ({
89
+ value: arg.toString(),
90
+ isTracked: false
91
+ })),
92
+ result: {
93
+ value: result,
94
+ isTracked: true
95
+ },
96
+ tags,
97
+ stacktraceOpts: {
98
+ constructorOpt: hooked,
99
+ prependFrames: [orig]
100
+ }
101
+ });
102
+ const { extern } = tracker.track(result, event);
103
+
104
+ if (extern) {
105
+ data.result = extern;
106
+ }
107
+ },
108
+ });
109
+ });
110
+ },
111
+ uninstall() {
112
+ String.prototype.substr = patcher.unwrap(String.prototype.substr);
113
+ String.prototype.substring = patcher.unwrap(String.prototype.substring);
114
+ },
115
+ };
116
+ };
117
+
@@ -0,0 +1,115 @@
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
+ createSubsetTags,
20
+ } = require('../../../tag-utils');
21
+ const { patchType } = require('../../common');
22
+
23
+ module.exports = function(core) {
24
+ const {
25
+ scopes: { sources, instrumentation },
26
+ patcher,
27
+ assess: {
28
+ dataflow: { tracker, eventFactory: { createPropagationEvent } }
29
+ }
30
+ } = core;
31
+
32
+ function createPostHook(methodName, presetStart) {
33
+ return function(data) {
34
+ const { obj, result, hooked, orig } = data;
35
+
36
+ if (!result?.length || !sources.getStore()?.assess || instrumentation.isLocked()) {
37
+ return;
38
+ }
39
+ const rInfo = tracker.getData(result);
40
+ if (rInfo) {
41
+ // this may happen w/ 'trackedStr'.trim() => 'trackedStr'
42
+ return;
43
+ }
44
+
45
+ const objInfo = tracker.getData(obj);
46
+
47
+ if (!objInfo) {
48
+ return;
49
+ }
50
+
51
+ const history = [objInfo];
52
+ const start = presetStart || obj.indexOf(result);
53
+ const newTags = {};
54
+ const objTags = objInfo.tags || {};
55
+ Object.assign(newTags, createSubsetTags(objTags, start, result.length - 1));
56
+
57
+ if (!newTags.untrusted) {
58
+ return;
59
+ }
60
+
61
+ const event = createPropagationEvent({
62
+ name: `String.prototype.${methodName}`,
63
+ history,
64
+ object: {
65
+ value: obj,
66
+ isTracked: true
67
+ },
68
+ result: {
69
+ value: result,
70
+ isTracked: true
71
+ },
72
+ tags: newTags,
73
+ stacktraceOpts: {
74
+ constructorOpt: hooked,
75
+ prependFrames: [orig]
76
+ },
77
+ source: 'O',
78
+ target: 'R'
79
+ });
80
+
81
+ const { extern } = tracker.track(result, event);
82
+
83
+ if (extern) {
84
+ data.result = extern;
85
+ }
86
+ };
87
+ }
88
+
89
+ return core.assess.dataflow.propagation.stringInstrumentation.trim = {
90
+ install() {
91
+ patcher.patch(String.prototype, 'trim', {
92
+ name: 'String.prototype.trim',
93
+ patchType,
94
+ post: createPostHook('trim'),
95
+ });
96
+
97
+ patcher.patch(String.prototype, 'trimStart', {
98
+ name: 'String.prototype.trimStart',
99
+ patchType,
100
+ post: createPostHook('trimStart')
101
+ });
102
+
103
+ patcher.patch(String.prototype, 'trimEnd', {
104
+ name: 'String.prototype.trimEnd',
105
+ patchType,
106
+ post: createPostHook('trimEnd', 0),
107
+ });
108
+ },
109
+ uninstall() {
110
+ String.prototype.trim = patcher.unwrap(String.prototype.trim);
111
+ String.prototype.trimStart = patcher.unwrap(String.prototype.trimStart);
112
+ String.prototype.trimEnd = patcher.unwrap(String.prototype.trimEnd);
113
+ },
114
+ };
115
+ };