@contrast/assess 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dataflow/event-factory.js +17 -13
- package/lib/dataflow/propagation/index.js +4 -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/path/basename.js +124 -0
- package/lib/dataflow/propagation/install/path/common.js +176 -0
- package/lib/dataflow/propagation/install/path/index.js +32 -0
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
- package/lib/dataflow/propagation/install/path/normalize.js +123 -0
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +9 -2
- package/lib/dataflow/propagation/install/querystring/parse.js +12 -10
- 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 +16 -11
- package/lib/dataflow/propagation/install/string/replace.js +23 -15
- package/lib/dataflow/propagation/install/string/slice.js +14 -6
- package/lib/dataflow/propagation/install/string/split.js +16 -12
- package/lib/dataflow/propagation/install/string/substring.js +18 -8
- package/lib/dataflow/propagation/install/string/trim.js +4 -1
- 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/url/index.js +1 -0
- package/lib/dataflow/propagation/install/url/url.js +228 -0
- package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
- package/lib/dataflow/sinks/index.js +8 -4
- package/lib/dataflow/sinks/install/child-process.js +116 -50
- package/lib/dataflow/sinks/install/eval.js +138 -0
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +7 -4
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +9 -5
- package/lib/dataflow/sinks/install/fs.js +45 -13
- package/lib/dataflow/sinks/install/function.js +160 -0
- package/lib/dataflow/sinks/install/http/index.js +31 -0
- package/lib/dataflow/sinks/install/http/request.js +152 -0
- package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +7 -4
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +8 -5
- package/lib/dataflow/sinks/install/marsdb.js +3 -0
- package/lib/dataflow/sinks/install/mongodb.js +7 -24
- package/lib/dataflow/sinks/install/mssql.js +49 -29
- 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/sinks/install/vm.js +276 -0
- package/lib/dataflow/sources/handler.js +2 -1
- package/lib/dataflow/sources/install/http.js +1 -1
- package/lib/dataflow/tag-utils.js +95 -2
- 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
|
@@ -80,7 +80,7 @@ module.exports = function(core) {
|
|
|
80
80
|
pre(data) {
|
|
81
81
|
const [name = '', value] = data.args;
|
|
82
82
|
if (toLowerCase(name) === 'content-type' && value) {
|
|
83
|
-
|
|
83
|
+
scopes.sources.getStore().assess.responseData.contentType = value;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
});
|
|
@@ -44,7 +44,7 @@ function atomicAppend(firstTagRanges, secondTagRanges, offset) {
|
|
|
44
44
|
|
|
45
45
|
function atomicSubset(tags, subsetStart, len) {
|
|
46
46
|
const ret = [];
|
|
47
|
-
const subsetStop = subsetStart + len;
|
|
47
|
+
const subsetStop = subsetStart + len - 1;
|
|
48
48
|
|
|
49
49
|
for (let idx = 0; idx < tags.length - 1; idx += 2) {
|
|
50
50
|
const tagStart = tags[idx];
|
|
@@ -119,6 +119,42 @@ function atomicMerge(firstTagRanges, secondTagRanges) {
|
|
|
119
119
|
return finalMergedRanges;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function atomicExclude(tags, exclusionRange) {
|
|
123
|
+
const ret = [];
|
|
124
|
+
const [exclusionStart, exclusionStop] = exclusionRange;
|
|
125
|
+
|
|
126
|
+
for (let idx = 0; idx < tags.length - 1; idx += 2) {
|
|
127
|
+
const tagStart = tags[idx];
|
|
128
|
+
const tagStop = tags[idx + 1];
|
|
129
|
+
|
|
130
|
+
if (tagStop < exclusionStart) {
|
|
131
|
+
ret.push(tagStart, tagStop);
|
|
132
|
+
// exlusion is below - continue to check next range
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (tagStart > exclusionStop) {
|
|
137
|
+
ret.push(...tags.slice(idx));
|
|
138
|
+
// all other ranges are above exclusion so we can stop
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (exclusionStart <= tagStart && exclusionStop < tagStop) {
|
|
143
|
+
ret.push(exclusionStop + 1, tagStop);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (exclusionStart > tagStart) {
|
|
147
|
+
ret.push(tagStart, exclusionStart - 1);
|
|
148
|
+
|
|
149
|
+
if (exclusionStop < tagStop) {
|
|
150
|
+
ret.push(exclusionStop + 1, tagStop);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return ret;
|
|
156
|
+
}
|
|
157
|
+
|
|
122
158
|
function createAppendTags(firstTags, secondTags, offset) {
|
|
123
159
|
const ret = Object.create(null);
|
|
124
160
|
const firstTagsObject = ensureObject(firstTags);
|
|
@@ -134,6 +170,29 @@ function createAppendTags(firstTags, secondTags, offset) {
|
|
|
134
170
|
return Object.keys(ret).length ? ret : null;
|
|
135
171
|
}
|
|
136
172
|
|
|
173
|
+
function createOverlappingTags(tags, startIndex, endIndex) {
|
|
174
|
+
const overlappingTags = {};
|
|
175
|
+
|
|
176
|
+
Object.entries(tags).forEach(([tag, tagRanges]) => {
|
|
177
|
+
const overlappingRanges = [];
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < tagRanges.length; i += 2) {
|
|
180
|
+
const start = tagRanges[i];
|
|
181
|
+
const end = tagRanges[i + 1];
|
|
182
|
+
|
|
183
|
+
if (end >= startIndex && start <= endIndex) {
|
|
184
|
+
overlappingRanges.push([start, end]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (overlappingRanges.length > 0) {
|
|
189
|
+
overlappingTags[tag] = overlappingRanges;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return overlappingTags;
|
|
194
|
+
}
|
|
195
|
+
|
|
137
196
|
/**
|
|
138
197
|
* assumes:
|
|
139
198
|
* - no mutation of arguments
|
|
@@ -181,9 +240,43 @@ function createMergedTags(firstTags, secondTags) {
|
|
|
181
240
|
return Object.keys(ret).length ? ret : null;
|
|
182
241
|
}
|
|
183
242
|
|
|
243
|
+
function createTagsWithExclusion(tags, exclusionRange) {
|
|
244
|
+
if (!exclusionRange.length) return;
|
|
245
|
+
|
|
246
|
+
const ret = Object.create(null);
|
|
247
|
+
const tagsObject = ensureObject(tags);
|
|
248
|
+
|
|
249
|
+
for (const tagName of Object.keys(tagsObject)) {
|
|
250
|
+
const newTagRanges = atomicExclude(ensureTagsImmutable(tagsObject, tagName), exclusionRange);
|
|
251
|
+
|
|
252
|
+
newTagRanges.length && (ret[tagName] = newTagRanges);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return Object.keys(ret).length ? ret : null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function createAdjustedQueryTags(path, tags, value, argString) {
|
|
259
|
+
let idx = -1;
|
|
260
|
+
for (const str of [...path, value]) {
|
|
261
|
+
// This is the case where the argument is an array
|
|
262
|
+
if (str === 0) continue;
|
|
263
|
+
|
|
264
|
+
idx = argString.indexOf(str, idx);
|
|
265
|
+
if (idx == -1) {
|
|
266
|
+
idx = -1;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return idx > 0 ? createAppendTags([], tags, idx) : [...tags];
|
|
272
|
+
}
|
|
273
|
+
|
|
184
274
|
module.exports = {
|
|
185
275
|
createSubsetTags,
|
|
186
276
|
createAppendTags,
|
|
187
277
|
createFullLengthCopyTags,
|
|
188
|
-
createMergedTags
|
|
278
|
+
createMergedTags,
|
|
279
|
+
createTagsWithExclusion,
|
|
280
|
+
createAdjustedQueryTags,
|
|
281
|
+
createOverlappingTags
|
|
189
282
|
};
|
package/lib/dataflow/tracker.js
CHANGED
|
@@ -34,7 +34,8 @@ module.exports = function tracker(core) {
|
|
|
34
34
|
|
|
35
35
|
function getData(value) {
|
|
36
36
|
if (typeof value === 'string') {
|
|
37
|
-
|
|
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.
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
?
|
|
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.
|
|
3
|
+
"version": "1.10.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.
|
|
18
|
+
"@contrast/common": "1.13.0",
|
|
19
19
|
"parseurl": "^1.3.3"
|
|
20
20
|
}
|
|
21
21
|
}
|