@contrast/assess 1.8.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.
- package/lib/dataflow/event-factory.js +17 -13
- package/lib/dataflow/propagation/index.js +3 -0
- package/lib/dataflow/propagation/install/JSON/index.js +1 -0
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +248 -0
- package/lib/dataflow/propagation/install/JSON/parse.js +196 -0
- package/lib/dataflow/propagation/install/JSON/stringify.js +5 -3
- package/lib/dataflow/propagation/install/array-prototype-join.js +21 -14
- package/lib/dataflow/propagation/install/buffer.js +2 -0
- package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -0
- package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -0
- package/lib/dataflow/propagation/install/contrast-methods/number.js +58 -0
- package/lib/dataflow/propagation/install/contrast-methods/string.js +53 -6
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +3 -1
- package/lib/dataflow/propagation/install/decode-uri-component.js +9 -2
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -2
- package/lib/dataflow/propagation/install/encode-uri-component.js +9 -2
- package/lib/dataflow/propagation/install/escape-html.js +13 -5
- package/lib/dataflow/propagation/install/escape.js +9 -2
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +11 -4
- package/lib/dataflow/propagation/install/isnumeric-0.js +59 -0
- package/lib/dataflow/propagation/install/mongoose/index.js +36 -0
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +156 -0
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -0
- package/lib/dataflow/propagation/install/parse-int.js +60 -0
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +9 -2
- package/lib/dataflow/propagation/install/querystring/parse.js +11 -9
- package/lib/dataflow/propagation/install/sequelize.js +6 -3
- package/lib/dataflow/propagation/install/sql-template-strings.js +9 -2
- package/lib/dataflow/propagation/install/string/concat.js +8 -2
- package/lib/dataflow/propagation/install/string/format-methods.js +7 -2
- package/lib/dataflow/propagation/install/string/html-methods.js +15 -5
- package/lib/dataflow/propagation/install/string/match.js +14 -9
- package/lib/dataflow/propagation/install/string/replace.js +22 -14
- package/lib/dataflow/propagation/install/string/slice.js +13 -5
- package/lib/dataflow/propagation/install/string/split.js +15 -11
- package/lib/dataflow/propagation/install/string/substring.js +16 -6
- package/lib/dataflow/propagation/install/string/trim.js +3 -0
- package/lib/dataflow/propagation/install/unescape.js +9 -2
- package/lib/dataflow/propagation/install/url/domain-parsers.js +7 -2
- package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
- package/lib/dataflow/sinks/install/child-process.js +116 -50
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +6 -3
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +7 -4
- package/lib/dataflow/sinks/install/fs.js +44 -12
- package/lib/dataflow/sinks/install/http.js +5 -2
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +7 -4
- package/lib/dataflow/sinks/install/marsdb.js +3 -0
- package/lib/dataflow/sinks/install/mongodb.js +3 -1
- package/lib/dataflow/sinks/install/mssql.js +9 -2
- package/lib/dataflow/sinks/install/mysql.js +9 -4
- package/lib/dataflow/sinks/install/postgres.js +6 -3
- package/lib/dataflow/sinks/install/sequelize.js +7 -5
- package/lib/dataflow/sinks/install/sqlite3.js +7 -3
- package/lib/dataflow/sources/handler.js +2 -1
- package/lib/dataflow/sources/install/http.js +1 -1
- package/lib/dataflow/tag-utils.js +25 -1
- package/lib/dataflow/tracker.js +6 -6
- package/lib/index.js +2 -0
- package/lib/response-scanning/handlers/utils.js +2 -2
- package/lib/session-configuration/index.js +34 -0
- package/lib/session-configuration/install/http.js +79 -0
- package/package.json +2 -2
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const { InputType, signatures } = require('@contrast/common');
|
|
18
|
+
const { InputType, match } = require('@contrast/common');
|
|
20
19
|
const annotationRegExp = /^(A|O|R|P|P\d+)$/;
|
|
21
20
|
|
|
22
21
|
module.exports = function(core) {
|
|
@@ -48,7 +47,7 @@ module.exports = function(core) {
|
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
if (!name) {
|
|
51
|
-
logger.debug({
|
|
50
|
+
logger.debug({ data }, baseMessage, 'invalid name');
|
|
52
51
|
return null;
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -77,6 +76,8 @@ module.exports = function(core) {
|
|
|
77
76
|
eventFactory.createPropagationEvent = function(data) {
|
|
78
77
|
const {
|
|
79
78
|
name = '',
|
|
79
|
+
moduleName,
|
|
80
|
+
methodName,
|
|
80
81
|
history = [],
|
|
81
82
|
object = { value: null, tracked: false },
|
|
82
83
|
args = [],
|
|
@@ -102,10 +103,8 @@ module.exports = function(core) {
|
|
|
102
103
|
return null;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!signature) {
|
|
108
|
-
logger.debug({ name }, 'Propagation event not created: no signature found for this name');
|
|
106
|
+
if (!name) {
|
|
107
|
+
logger.debug({ name }, 'Propagation event not created: invalid name');
|
|
109
108
|
return null;
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -115,8 +114,8 @@ module.exports = function(core) {
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
if (
|
|
118
|
-
(
|
|
119
|
-
(
|
|
117
|
+
(!source || !match(source, annotationRegExp)) ||
|
|
118
|
+
(!target || !match(target, annotationRegExp))
|
|
120
119
|
) {
|
|
121
120
|
logger.debug({ name, source, target }, 'Propagation event not created: %s', 'invalid source/target');
|
|
122
121
|
return null;
|
|
@@ -135,6 +134,8 @@ module.exports = function(core) {
|
|
|
135
134
|
context,
|
|
136
135
|
history,
|
|
137
136
|
name,
|
|
137
|
+
moduleName,
|
|
138
|
+
methodName,
|
|
138
139
|
object,
|
|
139
140
|
removedTags,
|
|
140
141
|
result,
|
|
@@ -155,6 +156,8 @@ module.exports = function(core) {
|
|
|
155
156
|
const {
|
|
156
157
|
context,
|
|
157
158
|
name = '',
|
|
159
|
+
moduleName,
|
|
160
|
+
methodName,
|
|
158
161
|
history = [],
|
|
159
162
|
object = { value: null, tracked: false },
|
|
160
163
|
args = [],
|
|
@@ -169,9 +172,8 @@ module.exports = function(core) {
|
|
|
169
172
|
logger.debug('no sourceContext found during sink event creation');
|
|
170
173
|
return null;
|
|
171
174
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logger.debug({ name }, 'no signature found for sink event name');
|
|
175
|
+
if (!name) {
|
|
176
|
+
logger.debug({ data }, 'no sink event name');
|
|
175
177
|
return null;
|
|
176
178
|
}
|
|
177
179
|
if (!history.length) {
|
|
@@ -179,7 +181,7 @@ module.exports = function(core) {
|
|
|
179
181
|
return null;
|
|
180
182
|
}
|
|
181
183
|
if (
|
|
182
|
-
(
|
|
184
|
+
(!source || !source.match(annotationRegExp))
|
|
183
185
|
) {
|
|
184
186
|
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
185
187
|
return null;
|
|
@@ -197,6 +199,8 @@ module.exports = function(core) {
|
|
|
197
199
|
context,
|
|
198
200
|
history,
|
|
199
201
|
name,
|
|
202
|
+
moduleName,
|
|
203
|
+
methodName,
|
|
200
204
|
object,
|
|
201
205
|
result,
|
|
202
206
|
source,
|
|
@@ -22,6 +22,7 @@ module.exports = function(core) {
|
|
|
22
22
|
|
|
23
23
|
require('./install/contrast-methods')(core);
|
|
24
24
|
require('./install/ejs')(core);
|
|
25
|
+
require('./install/mongoose')(core);
|
|
25
26
|
require('./install/pug')(core);
|
|
26
27
|
require('./install/string')(core);
|
|
27
28
|
require('./install/url')(core);
|
|
@@ -33,7 +34,9 @@ module.exports = function(core) {
|
|
|
33
34
|
require('./install/escape-html')(core);
|
|
34
35
|
require('./install/escape')(core);
|
|
35
36
|
require('./install/handlebars-utils-escape-expression')(core);
|
|
37
|
+
require('./install/isnumeric-0')(core);
|
|
36
38
|
require('./install/mysql-connection-escape')(core);
|
|
39
|
+
require('./install/parse-int')(core);
|
|
37
40
|
require('./install/pug-runtime-escape')(core);
|
|
38
41
|
require('./install/sql-template-strings')(core);
|
|
39
42
|
require('./install/unescape')(core);
|
|
@@ -0,0 +1,248 @@
|
|
|
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
|
+
'use strict';
|
|
16
|
+
const { trim } = require('@contrast/common');
|
|
17
|
+
|
|
18
|
+
function isNumber(value) {
|
|
19
|
+
return !isNaN(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function array(input, index, accumulator) {
|
|
23
|
+
const startIndex = index - 1;
|
|
24
|
+
const valueIndexes = [];
|
|
25
|
+
|
|
26
|
+
while (index < input.length) {
|
|
27
|
+
const cv = input[index];
|
|
28
|
+
if (cv === ']') {
|
|
29
|
+
index += 1;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (trim(cv) === '' || (valueIndexes.length > 0 && cv === ',')) {
|
|
34
|
+
index += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
startIndex,
|
|
40
|
+
endIndex,
|
|
41
|
+
index: nextIndex,
|
|
42
|
+
} = getValueIndexes(input, index, accumulator);
|
|
43
|
+
|
|
44
|
+
valueIndexes.push([startIndex, endIndex]);
|
|
45
|
+
accumulator.push({ key: [null, null], value: [startIndex, endIndex] });
|
|
46
|
+
|
|
47
|
+
index = nextIndex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
startIndex,
|
|
52
|
+
endIndex: index - 1,
|
|
53
|
+
index,
|
|
54
|
+
accumulator,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function string(input, index) {
|
|
59
|
+
const startIndex = index;
|
|
60
|
+
|
|
61
|
+
while (index < input.length) {
|
|
62
|
+
const cv = input[index];
|
|
63
|
+
if (cv === '\\') {
|
|
64
|
+
index += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (input[index] !== '"') {
|
|
69
|
+
index += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (startIndex === index) {
|
|
77
|
+
return { startIndex: null, endIndex: null, index: index + 1 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { startIndex, endIndex: index - 1, index: index + 1 };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function bool(input, index = 0) {
|
|
84
|
+
const trueLength = 'true'.length;
|
|
85
|
+
const falseLength = 'false'.length;
|
|
86
|
+
let endIndex = 0;
|
|
87
|
+
|
|
88
|
+
if (input[index] === 't') {
|
|
89
|
+
endIndex = index + trueLength - 1;
|
|
90
|
+
} else {
|
|
91
|
+
endIndex = index + falseLength - 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { startIndex: index, endIndex, index: endIndex + 1 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function nully(index = 0) {
|
|
98
|
+
const nullLength = 'null'.length;
|
|
99
|
+
const endIndex = index + nullLength - 1;
|
|
100
|
+
return { startIndex: index, endIndex, index: endIndex + 1 };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function number(input, index) {
|
|
104
|
+
const startIndex = index;
|
|
105
|
+
|
|
106
|
+
if (input[startIndex] === '-') {
|
|
107
|
+
index += 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
while (index < input.length) {
|
|
111
|
+
if (isNumber(input[index]) || input[index] === '.') {
|
|
112
|
+
index += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { startIndex, endIndex: index - 1, index };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function object(value, index, accumulator) {
|
|
123
|
+
const startIndex = index - 1;
|
|
124
|
+
const keyIndexes = [];
|
|
125
|
+
const valueIndexes = [];
|
|
126
|
+
|
|
127
|
+
while (index < value.length) {
|
|
128
|
+
const cv = value[index];
|
|
129
|
+
const keyIndexesLength = keyIndexes.length;
|
|
130
|
+
const valueIndexesLength = valueIndexes.length;
|
|
131
|
+
const areKeysEqualToValues = keyIndexesLength - valueIndexesLength === 0;
|
|
132
|
+
|
|
133
|
+
if (cv === '}' && areKeysEqualToValues) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
trim(cv) === '' ||
|
|
139
|
+
(cv === ':' && keyIndexesLength > 0) ||
|
|
140
|
+
(cv === ',' && areKeysEqualToValues)
|
|
141
|
+
) {
|
|
142
|
+
index += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Object key
|
|
147
|
+
if (cv === '"' && areKeysEqualToValues) {
|
|
148
|
+
index += 1;
|
|
149
|
+
const { startIndex, endIndex, index: nextIndex } = string(value, index);
|
|
150
|
+
keyIndexes.push([startIndex, endIndex]);
|
|
151
|
+
index = nextIndex;
|
|
152
|
+
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const {
|
|
157
|
+
startIndex,
|
|
158
|
+
endIndex,
|
|
159
|
+
index: nextIndex,
|
|
160
|
+
} = getValueIndexes(value, index, accumulator);
|
|
161
|
+
|
|
162
|
+
valueIndexes.push([startIndex, endIndex]);
|
|
163
|
+
accumulator.push({
|
|
164
|
+
key: keyIndexes[keyIndexes.length - 1],
|
|
165
|
+
value: [startIndex, endIndex],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
index = nextIndex;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
accumulator,
|
|
173
|
+
startIndex,
|
|
174
|
+
endIndex: index,
|
|
175
|
+
index: index + 1,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getValueIndexes(value, index, accumulator) {
|
|
180
|
+
const cv = value[index];
|
|
181
|
+
|
|
182
|
+
switch (cv) {
|
|
183
|
+
case '{':
|
|
184
|
+
return object(value, index + 1, accumulator);
|
|
185
|
+
case '"':
|
|
186
|
+
return string(value, index + 1);
|
|
187
|
+
case '[':
|
|
188
|
+
return array(value, index + 1, accumulator);
|
|
189
|
+
case 't':
|
|
190
|
+
case 'f':
|
|
191
|
+
return bool(value, index);
|
|
192
|
+
case 'n':
|
|
193
|
+
return nully(index);
|
|
194
|
+
default:
|
|
195
|
+
if (cv === '-' || (cv && cv >= 0 && cv <= 9)) {
|
|
196
|
+
return number(value, index);
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error('bad JSON');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function processInput(input) {
|
|
204
|
+
const firstElement = input[0];
|
|
205
|
+
const lastElement = input[input.length - 1];
|
|
206
|
+
const accumulator = [];
|
|
207
|
+
|
|
208
|
+
if (firstElement === '{' && lastElement === '}') {
|
|
209
|
+
return object(input, 1, accumulator);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (firstElement === '[' && lastElement === ']') {
|
|
213
|
+
return array(input, 1, accumulator);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (isNumber(input)) {
|
|
217
|
+
return number(input, 0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (firstElement === '"') {
|
|
221
|
+
return string(input, 1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (input) {
|
|
225
|
+
case 'true':
|
|
226
|
+
case 'false':
|
|
227
|
+
return bool(input);
|
|
228
|
+
case 'null':
|
|
229
|
+
return nully();
|
|
230
|
+
default:
|
|
231
|
+
throw new Error('bad JSON');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function wrapEndResult({ accumulator, startIndex, endIndex }) {
|
|
236
|
+
accumulator = accumulator || [];
|
|
237
|
+
accumulator.push({ key: '', value: [startIndex, endIndex] });
|
|
238
|
+
|
|
239
|
+
return accumulator;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getKeyValueIndexes(input) {
|
|
243
|
+
return wrapEndResult(processInput(input));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
getKeyValueIndexes,
|
|
248
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
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 { isString, inspect } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../../common');
|
|
20
|
+
const { getKeyValueIndexes } = require('./parse-fn');
|
|
21
|
+
const {
|
|
22
|
+
createOverlappingTags
|
|
23
|
+
} = require('../../../tag-utils');
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
When we return a string as a result of a reviver call
|
|
27
|
+
we loose tracking. We keep the values in a stack and when
|
|
28
|
+
the reviver gets called with an object - we apply the values
|
|
29
|
+
*/
|
|
30
|
+
const applyValuesToObject = (object, stack) => {
|
|
31
|
+
let i = 0;
|
|
32
|
+
|
|
33
|
+
if (Array.isArray(object)) {
|
|
34
|
+
i = object.length;
|
|
35
|
+
} else {
|
|
36
|
+
i = Object.keys(object).length;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (i > 0) {
|
|
40
|
+
const { key, value } = stack.pop();
|
|
41
|
+
if (object[key]) {
|
|
42
|
+
object[key] = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
i--;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = function (core) {
|
|
50
|
+
const {
|
|
51
|
+
logger,
|
|
52
|
+
scopes: { sources, instrumentation },
|
|
53
|
+
patcher,
|
|
54
|
+
assess: {
|
|
55
|
+
dataflow: {
|
|
56
|
+
tracker,
|
|
57
|
+
eventFactory: { createPropagationEvent },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
} = core;
|
|
61
|
+
|
|
62
|
+
function createEvent(data, value, newTags, reviver, strInfo) {
|
|
63
|
+
const method = 'JSON.parse';
|
|
64
|
+
return createPropagationEvent({
|
|
65
|
+
context: method,
|
|
66
|
+
name: method,
|
|
67
|
+
history: [strInfo],
|
|
68
|
+
moduleName: 'JSON',
|
|
69
|
+
methodName: 'parse',
|
|
70
|
+
object: {
|
|
71
|
+
value: inspect(data.obj),
|
|
72
|
+
tracked: false,
|
|
73
|
+
},
|
|
74
|
+
args: [
|
|
75
|
+
{
|
|
76
|
+
value: data.args[0],
|
|
77
|
+
tracked: true,
|
|
78
|
+
},
|
|
79
|
+
reviver && {
|
|
80
|
+
value: reviver.toString(),
|
|
81
|
+
tracked: false,
|
|
82
|
+
},
|
|
83
|
+
].filter(Boolean),
|
|
84
|
+
result: {
|
|
85
|
+
value,
|
|
86
|
+
tracked: true,
|
|
87
|
+
},
|
|
88
|
+
value,
|
|
89
|
+
tags: newTags,
|
|
90
|
+
stacktraceOpts: {
|
|
91
|
+
constructorOpt: data.hooked,
|
|
92
|
+
prependFrames: [data.orig],
|
|
93
|
+
},
|
|
94
|
+
source: 'P0',
|
|
95
|
+
target: 'R',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getNewTags(strInfo, startIndex, endIndex) {
|
|
100
|
+
const overlappingRanges = createOverlappingTags(
|
|
101
|
+
strInfo.tags,
|
|
102
|
+
startIndex,
|
|
103
|
+
endIndex
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const tags = {};
|
|
107
|
+
const startingOffset = startIndex;
|
|
108
|
+
const normalizedEndIndex = endIndex - startingOffset;
|
|
109
|
+
|
|
110
|
+
Object.entries(overlappingRanges).forEach(([tag, ranges]) => {
|
|
111
|
+
tags[tag] = [];
|
|
112
|
+
|
|
113
|
+
ranges.forEach(([start, end]) => {
|
|
114
|
+
const transferredStartIndex = start - startingOffset;
|
|
115
|
+
const transferredEndIndex = end - startingOffset;
|
|
116
|
+
|
|
117
|
+
tags[tag].push(transferredStartIndex < 0 ? 0 : transferredStartIndex);
|
|
118
|
+
tags[tag].push(
|
|
119
|
+
transferredEndIndex > normalizedEndIndex
|
|
120
|
+
? normalizedEndIndex
|
|
121
|
+
: transferredEndIndex
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return tags;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (core.assess.dataflow.propagation.jsonInstrumentation.parse = {
|
|
130
|
+
install() {
|
|
131
|
+
patcher.patch(JSON, 'parse', {
|
|
132
|
+
name: 'JSON.prototype.parse',
|
|
133
|
+
patchType,
|
|
134
|
+
pre(data) {
|
|
135
|
+
if (!sources.getStore()?.assess || instrumentation.isLocked()) return;
|
|
136
|
+
const input = data.args[0];
|
|
137
|
+
|
|
138
|
+
const strInfo = tracker.getData(input);
|
|
139
|
+
if (!strInfo) return;
|
|
140
|
+
|
|
141
|
+
const reviver = data.args[1];
|
|
142
|
+
const stack = [];
|
|
143
|
+
let keyValueIndexes;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
keyValueIndexes = getKeyValueIndexes(input);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.warn({ error, string: input }, 'JSON.parse() propagation failed');
|
|
149
|
+
keyValueIndexes = [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Error found, continue without instrumentation
|
|
153
|
+
if (keyValueIndexes.length === 0) return;
|
|
154
|
+
|
|
155
|
+
let i = 0;
|
|
156
|
+
function contrastParseReviver(key, value) {
|
|
157
|
+
const { value: [startIndex, endIndex] } = keyValueIndexes[i];
|
|
158
|
+
|
|
159
|
+
if (!isString(value)) {
|
|
160
|
+
return reviver ? reviver(key, value) : value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const newTags = getNewTags(strInfo, startIndex, endIndex);
|
|
164
|
+
|
|
165
|
+
const event = createEvent(data, value, newTags, reviver, strInfo);
|
|
166
|
+
if (!event || Object.keys(newTags).length === 0) {
|
|
167
|
+
return reviver ? reviver(key, value) : value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { extern } = tracker.track(value, event);
|
|
171
|
+
|
|
172
|
+
if (reviver) return reviver(key, extern);
|
|
173
|
+
|
|
174
|
+
return extern;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
data.args[1] = function (key, value) {
|
|
178
|
+
if (typeof value === 'object') {
|
|
179
|
+
applyValuesToObject(value, stack);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const result = contrastParseReviver(key, value);
|
|
183
|
+
stack.push({ key, value: result });
|
|
184
|
+
|
|
185
|
+
i += 1;
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
uninstall() {
|
|
193
|
+
JSON.parse = patcher.unwrap(JSON.parse);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
};
|
|
@@ -81,8 +81,8 @@ module.exports = function(core) {
|
|
|
81
81
|
} = core;
|
|
82
82
|
|
|
83
83
|
function getUntrustedSpaceProps(space) {
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// it can't be a problem if it's not a string, if the string is all spaces, or
|
|
85
|
+
// if the string is zero length.
|
|
86
86
|
if (!isString(space) || match(space, /^\s+$/) || !space) {
|
|
87
87
|
return null;
|
|
88
88
|
}
|
|
@@ -239,8 +239,10 @@ module.exports = function(core) {
|
|
|
239
239
|
|
|
240
240
|
const method = 'JSON.stringify';
|
|
241
241
|
const event = createPropagationEvent({
|
|
242
|
-
context: method
|
|
242
|
+
context: `${method}(${ret})`,
|
|
243
243
|
name: method,
|
|
244
|
+
moduleName: 'JSON',
|
|
245
|
+
methodName: 'stringify',
|
|
244
246
|
history: Array.from(metadata.history),
|
|
245
247
|
object: {
|
|
246
248
|
value: inspect(data.obj),
|
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
createAppendTags
|
|
20
20
|
} = require('../../tag-utils');
|
|
21
21
|
const { patchType } = require('../common');
|
|
22
|
-
const { isString } = require('@contrast/common');
|
|
22
|
+
const { isString, join, inspect } = require('@contrast/common');
|
|
23
23
|
|
|
24
24
|
module.exports = function(core) {
|
|
25
25
|
const {
|
|
@@ -63,36 +63,43 @@ module.exports = function(core) {
|
|
|
63
63
|
|
|
64
64
|
return core.assess.dataflow.propagation.arrayJoin = {
|
|
65
65
|
install() {
|
|
66
|
-
const
|
|
66
|
+
const name = 'Array.prototype.join';
|
|
67
|
+
|
|
67
68
|
patcher.patch(Array.prototype, 'join', {
|
|
68
|
-
name
|
|
69
|
+
name,
|
|
69
70
|
patchType,
|
|
70
71
|
post(data) {
|
|
71
|
-
const { args, obj, result, hooked, orig } = data;
|
|
72
|
+
const { args: origArgs, obj, result, hooked, orig } = data;
|
|
72
73
|
if (!result || !sources.getStore()?.assess || instrumentation.isLocked()) return;
|
|
73
74
|
|
|
74
75
|
const resultInfo = tracker.getData(result);
|
|
75
|
-
const delimiter =
|
|
76
|
+
const delimiter = origArgs[0] === undefined ? ',' : origArgs[0];
|
|
76
77
|
const delimiterLength = delimiter.length;
|
|
77
78
|
const delimiterInfo = tracker.getData(delimiter);
|
|
78
79
|
const initHistory = delimiterInfo ? new Set([delimiterInfo]) : new Set();
|
|
79
80
|
const { newTags, newHistory: history } = accumulateTags(obj, {}, 0, initHistory, delimiterLength, delimiterInfo?.tags);
|
|
81
|
+
const object = {
|
|
82
|
+
value: obj && join(obj),
|
|
83
|
+
tracked: false
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const args = [{
|
|
87
|
+
value: delimiterInfo ? delimiterInfo.value : delimiter,
|
|
88
|
+
tracked: !!delimiterInfo
|
|
89
|
+
}];
|
|
80
90
|
|
|
81
91
|
if (history.size) {
|
|
82
92
|
const event = createPropagationEvent({
|
|
83
|
-
name
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
name,
|
|
94
|
+
moduleName: 'Array',
|
|
95
|
+
methodName: 'prototype.join',
|
|
96
|
+
context: `${object.value}.join('${inspect(args[0].value) || ''})`,
|
|
97
|
+
object,
|
|
88
98
|
result: {
|
|
89
99
|
value: resultInfo ? resultInfo.value : result,
|
|
90
100
|
tracked: true
|
|
91
101
|
},
|
|
92
|
-
args
|
|
93
|
-
value: delimiterInfo ? delimiterInfo.value : delimiter,
|
|
94
|
-
tracked: !!delimiterInfo
|
|
95
|
-
}],
|
|
102
|
+
args,
|
|
96
103
|
tags: newTags,
|
|
97
104
|
history: Array.from(history),
|
|
98
105
|
source: delimiterInfo ? (history.size > 1 ? 'A' : 'P') : 'O',
|
|
@@ -46,6 +46,8 @@ module.exports = function(core) {
|
|
|
46
46
|
|
|
47
47
|
const event = eventFactory.createPropagationEvent({
|
|
48
48
|
args: data.args.map((a) => ({ tracked: false, value: a })),
|
|
49
|
+
moduleName: 'Buffer',
|
|
50
|
+
methodName: 'prototype.toString',
|
|
49
51
|
context: 'buffer.toString()',
|
|
50
52
|
object: { tracked: true, value: 'Buffer' },
|
|
51
53
|
history: [bufferInfo],
|