@contrast/assess 1.10.0 → 1.12.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/index.js +1 -2
- package/lib/dataflow/propagation/common.js +1 -1
- package/lib/dataflow/propagation/index.js +2 -1
- package/lib/dataflow/propagation/install/JSON/index.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +3 -5
- package/lib/dataflow/propagation/install/JSON/stringify.js +3 -2
- package/lib/dataflow/propagation/install/array-prototype-join.js +3 -2
- package/lib/dataflow/propagation/install/buffer.js +3 -5
- package/lib/dataflow/propagation/install/contrast-methods/add.js +3 -2
- package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/number.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/string.js +3 -5
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +3 -5
- package/lib/dataflow/propagation/install/decode-uri-component.js +3 -2
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -2
- package/lib/dataflow/propagation/install/ejs/index.js +1 -1
- package/lib/dataflow/propagation/install/encode-uri-component.js +3 -2
- package/lib/dataflow/propagation/install/escape-html.js +3 -2
- package/lib/dataflow/propagation/install/escape.js +3 -2
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +3 -2
- package/lib/dataflow/propagation/install/isnumeric-0.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/common.js +20 -0
- package/lib/dataflow/propagation/install/mongoose/index.js +5 -9
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +149 -0
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +162 -0
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +91 -39
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +3 -2
- package/lib/dataflow/propagation/install/parse-int.js +1 -1
- package/lib/dataflow/propagation/install/path/basename.js +3 -5
- package/lib/dataflow/propagation/install/path/common.js +1 -1
- package/lib/dataflow/propagation/install/path/index.js +1 -1
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +3 -5
- package/lib/dataflow/propagation/install/path/normalize.js +3 -5
- package/lib/dataflow/propagation/install/pug/index.js +1 -1
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -2
- package/lib/dataflow/propagation/install/querystring/index.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +3 -2
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +180 -0
- package/lib/dataflow/propagation/install/sequelize.js +3 -5
- package/lib/dataflow/propagation/install/sql-template-strings.js +3 -2
- package/lib/dataflow/propagation/install/string/concat.js +3 -2
- package/lib/dataflow/propagation/install/string/format-methods.js +3 -2
- package/lib/dataflow/propagation/install/string/html-methods.js +3 -2
- package/lib/dataflow/propagation/install/string/index.js +66 -1
- package/lib/dataflow/propagation/install/string/match-all.js +236 -0
- package/lib/dataflow/propagation/install/string/match.js +83 -37
- package/lib/dataflow/propagation/install/string/replace.js +4 -3
- package/lib/dataflow/propagation/install/string/slice.js +3 -2
- package/lib/dataflow/propagation/install/string/split.js +3 -2
- package/lib/dataflow/propagation/install/string/substring.js +3 -2
- package/lib/dataflow/propagation/install/string/trim.js +3 -2
- package/lib/dataflow/propagation/install/unescape.js +3 -2
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -2
- package/lib/dataflow/propagation/install/url/index.js +3 -1
- package/lib/dataflow/propagation/install/url/parse.js +132 -0
- package/lib/dataflow/propagation/install/url/searchParams.js +140 -0
- package/lib/dataflow/propagation/install/url/url.js +11 -53
- package/lib/dataflow/propagation/install/validator/hooks.js +3 -2
- package/lib/dataflow/propagation/install/validator/index.js +1 -1
- package/lib/dataflow/propagation/install/validator/methods.js +1 -1
- package/lib/dataflow/sinks/common.js +1 -1
- package/lib/dataflow/sinks/index.js +1 -1
- package/lib/dataflow/sinks/install/child-process.js +2 -2
- package/lib/dataflow/sinks/install/eval.js +2 -2
- package/lib/dataflow/sinks/install/express/index.js +1 -1
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +3 -3
- package/lib/dataflow/sinks/install/fastify/index.js +1 -1
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/fs.js +2 -2
- package/lib/dataflow/sinks/install/function.js +2 -2
- package/lib/dataflow/sinks/install/http/index.js +1 -1
- package/lib/dataflow/sinks/install/http/request.js +2 -2
- package/lib/dataflow/sinks/install/http/server-response.js +2 -2
- package/lib/dataflow/sinks/install/koa/index.js +1 -1
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/marsdb.js +2 -2
- package/lib/dataflow/sinks/install/mongodb.js +33 -26
- package/lib/dataflow/sinks/install/mssql.js +2 -2
- package/lib/dataflow/sinks/install/mysql.js +3 -3
- package/lib/dataflow/sinks/install/postgres.js +2 -2
- package/lib/dataflow/sinks/install/sequelize.js +2 -2
- package/lib/dataflow/sinks/install/sqlite3.js +2 -2
- package/lib/dataflow/sinks/install/vm.js +2 -2
- package/lib/dataflow/sources/common.js +1 -1
- package/lib/dataflow/sources/handler.js +3 -3
- package/lib/dataflow/sources/index.js +1 -1
- package/lib/dataflow/sources/install/body-parser1.js +1 -1
- package/lib/dataflow/sources/install/busboy1.js +1 -1
- package/lib/dataflow/sources/install/cookie-parser1.js +1 -1
- package/lib/dataflow/sources/install/express/index.js +1 -1
- package/lib/dataflow/sources/install/express/params.js +1 -1
- package/lib/dataflow/sources/install/express/parsedUrl.js +1 -1
- package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
- package/lib/dataflow/sources/install/fastify/index.js +1 -1
- package/lib/dataflow/sources/install/formidable1.js +1 -1
- package/lib/dataflow/sources/install/http.js +2 -2
- package/lib/dataflow/sources/install/koa/index.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-routers.js +1 -1
- package/lib/dataflow/sources/install/koa/koa2.js +1 -1
- package/lib/dataflow/sources/install/qs6.js +1 -1
- package/lib/dataflow/sources/install/querystring.js +1 -1
- package/lib/dataflow/tag-utils.js +1 -1
- package/lib/dataflow/tracker.js +2 -6
- package/lib/dataflow/utils/is-safe-content-type.js +1 -1
- package/lib/dataflow/utils/is-vulnerable.js +1 -1
- package/lib/{dataflow/event-factory.js → event-factory.js} +58 -2
- package/lib/index.js +4 -2
- package/lib/response-scanning/handlers/index.js +36 -30
- package/lib/response-scanning/handlers/utils.js +1 -1
- package/lib/response-scanning/index.js +1 -1
- package/lib/response-scanning/install/http.js +3 -3
- package/lib/session-configuration/common.js +19 -0
- package/lib/session-configuration/handlers.js +86 -0
- package/lib/session-configuration/index.js +6 -9
- package/lib/session-configuration/install/express-session.js +131 -0
- package/package.json +3 -3
- package/lib/session-configuration/install/http.js +0 -79
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -19,6 +19,7 @@ const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
|
19
19
|
const sessionConfiguration = require('./session-configuration');
|
|
20
20
|
const dataflow = require('./dataflow');
|
|
21
21
|
const responseScanning = require('./response-scanning');
|
|
22
|
+
const eventFactory = require('./event-factory');
|
|
22
23
|
|
|
23
24
|
module.exports = function assess(core) {
|
|
24
25
|
if (!core.config.assess.enable) return;
|
|
@@ -27,9 +28,10 @@ module.exports = function assess(core) {
|
|
|
27
28
|
|
|
28
29
|
// Does this order matter? Probably not
|
|
29
30
|
// 1. dataflow
|
|
30
|
-
|
|
31
|
+
eventFactory(core);
|
|
31
32
|
dataflow(core);
|
|
32
33
|
responseScanning(core);
|
|
34
|
+
sessionConfiguration(core);
|
|
33
35
|
|
|
34
36
|
// crypto
|
|
35
37
|
// static (in coordination with rewriter)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const {
|
|
19
|
+
toLowerCase,
|
|
20
|
+
stringify,
|
|
21
|
+
substring,
|
|
22
|
+
ResponseScanningRule
|
|
23
|
+
} = require('@contrast/common');
|
|
18
24
|
const {
|
|
19
25
|
escapeHtml,
|
|
20
26
|
isHtmlContent,
|
|
@@ -26,7 +32,6 @@ const {
|
|
|
26
32
|
getCspHeaders,
|
|
27
33
|
checkCspSources
|
|
28
34
|
} = require('./utils');
|
|
29
|
-
const { toLowerCase, substring, ResponseScanningRule } = require('@contrast/common');
|
|
30
35
|
|
|
31
36
|
module.exports = function(core) {
|
|
32
37
|
const {
|
|
@@ -106,7 +111,7 @@ module.exports = function(core) {
|
|
|
106
111
|
reportFindings(sourceContext, {
|
|
107
112
|
ruleId: ResponseScanningRule.CACHE_CONTROLS_MISSING,
|
|
108
113
|
vulnerabilityMetadata: {
|
|
109
|
-
data:
|
|
114
|
+
data: stringify(instructions)
|
|
110
115
|
}
|
|
111
116
|
});
|
|
112
117
|
}
|
|
@@ -176,7 +181,10 @@ module.exports = function(core) {
|
|
|
176
181
|
delete vulnerabilityMetadata.referrerSecure;
|
|
177
182
|
delete vulnerabilityMetadata.referrerValue;
|
|
178
183
|
|
|
179
|
-
reportFindings(sourceContext, {
|
|
184
|
+
reportFindings(sourceContext, {
|
|
185
|
+
ruleId: ResponseScanningRule.CSP_HEADER_INSECURE,
|
|
186
|
+
vulnerabilityMetadata: { data: JSON.stringify(vulnerabilityMetadata) }
|
|
187
|
+
});
|
|
180
188
|
}
|
|
181
189
|
};
|
|
182
190
|
|
|
@@ -210,7 +218,27 @@ module.exports = function(core) {
|
|
|
210
218
|
}
|
|
211
219
|
};
|
|
212
220
|
|
|
213
|
-
responseScanning.
|
|
221
|
+
responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, { responseHeaders }) {
|
|
222
|
+
const headerName = 'x-content-type-options';
|
|
223
|
+
let header = responseHeaders[headerName];
|
|
224
|
+
|
|
225
|
+
if (header) {
|
|
226
|
+
header = toLowerCase(header);
|
|
227
|
+
if (header === 'nosniff') {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
reportFindings(sourceContext, {
|
|
233
|
+
ruleId: ResponseScanningRule.XCONTENTTYPE_HEADER_MISSING,
|
|
234
|
+
vulnerabilityMetadata: {
|
|
235
|
+
data: header || ''
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// NODE-3135
|
|
241
|
+
responseScanning.handleXPoweredByHeader = function(sourceContext, { responseHeaders }) {
|
|
214
242
|
const headerName = 'x-powered-by';
|
|
215
243
|
let header = responseHeaders[headerName];
|
|
216
244
|
|
|
@@ -226,7 +254,7 @@ module.exports = function(core) {
|
|
|
226
254
|
];
|
|
227
255
|
|
|
228
256
|
reportFindings(sourceContext, {
|
|
229
|
-
ruleId: ResponseScanningRule.
|
|
257
|
+
ruleId: ResponseScanningRule.X_POWERED_BY_HEADER,
|
|
230
258
|
vulnerabilityMetadata: {
|
|
231
259
|
data: JSON.stringify(instructions)
|
|
232
260
|
}
|
|
@@ -234,37 +262,15 @@ module.exports = function(core) {
|
|
|
234
262
|
}
|
|
235
263
|
};
|
|
236
264
|
|
|
237
|
-
responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, { responseHeaders }) {
|
|
238
|
-
const headerName = 'x-content-type-options';
|
|
239
|
-
let header = responseHeaders[headerName];
|
|
240
|
-
|
|
241
|
-
if (header) {
|
|
242
|
-
header = toLowerCase(header);
|
|
243
|
-
if (header === 'nosniff') {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
reportFindings(sourceContext, {
|
|
249
|
-
ruleId: ResponseScanningRule.XCONTENTTYPE_HEADER_MISSING,
|
|
250
|
-
vulnerabilityMetadata: {
|
|
251
|
-
data: header || ''
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
};
|
|
255
|
-
|
|
256
265
|
responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, { responseHeaders }) {
|
|
257
266
|
const header = responseHeaders['x-xss-protection'];
|
|
258
267
|
|
|
259
|
-
|
|
260
|
-
if (header && header.startsWith('1')) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
268
|
+
if (header?.startsWith?.('1')) return;
|
|
263
269
|
|
|
264
270
|
reportFindings(sourceContext, {
|
|
265
271
|
ruleId: ResponseScanningRule.XXSPROTECTION_HEADER_DISABLED,
|
|
266
272
|
vulnerabilityMetadata: {
|
|
267
|
-
data: header
|
|
273
|
+
data: header,
|
|
268
274
|
}
|
|
269
275
|
});
|
|
270
276
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -30,7 +30,7 @@ module.exports = function(core) {
|
|
|
30
30
|
handleParameterPollution,
|
|
31
31
|
handleCspHeader,
|
|
32
32
|
handleHstsHeaderMissing,
|
|
33
|
-
|
|
33
|
+
handleXPoweredByHeader,
|
|
34
34
|
handleXContentTypeHeaderMissing,
|
|
35
35
|
handleXxsProtectionHeaderDisabled,
|
|
36
36
|
}
|
|
@@ -76,7 +76,7 @@ module.exports = function(core) {
|
|
|
76
76
|
handleParameterPollution(sourceContext, evaluationContext);
|
|
77
77
|
handleCspHeader(sourceContext, evaluationContext);
|
|
78
78
|
handleHstsHeaderMissing(sourceContext, evaluationContext);
|
|
79
|
-
|
|
79
|
+
handleXPoweredByHeader(sourceContext, evaluationContext);
|
|
80
80
|
handleXContentTypeHeaderMissing(sourceContext, evaluationContext);
|
|
81
81
|
handleXxsProtectionHeaderDisabled(sourceContext, evaluationContext);
|
|
82
82
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
patchType: 'session-configuration'
|
|
19
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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
|
+
Event,
|
|
20
|
+
SessionConfigurationRule: { HTTPONLY, SECURE_FLAG_MISSING },
|
|
21
|
+
} = require('@contrast/common');
|
|
22
|
+
|
|
23
|
+
module.exports = function (core) {
|
|
24
|
+
const {
|
|
25
|
+
assess: { sessionConfiguration },
|
|
26
|
+
messages,
|
|
27
|
+
} = core;
|
|
28
|
+
|
|
29
|
+
const checkCookieValue = (ruleId, sinkEvent, cookieValue, sourceContext) => {
|
|
30
|
+
if (cookieValue.includes(ruleId === HTTPONLY ? 'httponly' : 'secure')) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
sessionConfiguration.reportFindings(sourceContext, {
|
|
35
|
+
ruleId,
|
|
36
|
+
sinkEvent,
|
|
37
|
+
props: {
|
|
38
|
+
evidence: cookieValue,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleCookie = (
|
|
44
|
+
sourceContext,
|
|
45
|
+
cookieValue,
|
|
46
|
+
ruleId,
|
|
47
|
+
sessionEvent
|
|
48
|
+
) => {
|
|
49
|
+
if (Array.isArray(cookieValue)) {
|
|
50
|
+
return cookieValue.forEach((value) =>
|
|
51
|
+
checkCookieValue(ruleId, sessionEvent, value, sourceContext)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
checkCookieValue(ruleId, sessionEvent, cookieValue, sourceContext);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
sessionConfiguration.handleHttpOnly = function (
|
|
59
|
+
sourceContext,
|
|
60
|
+
cookieValue,
|
|
61
|
+
sessionEvent
|
|
62
|
+
) {
|
|
63
|
+
handleCookie(sourceContext, cookieValue, HTTPONLY, sessionEvent);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
sessionConfiguration.handleSecure = function (
|
|
67
|
+
sourceContext,
|
|
68
|
+
cookieValue,
|
|
69
|
+
sessionEvent
|
|
70
|
+
) {
|
|
71
|
+
handleCookie(sourceContext, cookieValue, SECURE_FLAG_MISSING, sessionEvent);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// _sourceContext is unused
|
|
75
|
+
sessionConfiguration.reportFindings = function (
|
|
76
|
+
_sourceContext,
|
|
77
|
+
vulnerabilityMetadata
|
|
78
|
+
) {
|
|
79
|
+
messages.emit(
|
|
80
|
+
Event.ASSESS_SESSION_CONFIGURATION_FINDING,
|
|
81
|
+
vulnerabilityMetadata
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return sessionConfiguration;
|
|
86
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -15,16 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { callChildComponentMethodsSync
|
|
18
|
+
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
module.exports = function(core) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
require('./install/http')(core);
|
|
21
|
+
const sessionConfiguration = core.assess.sessionConfiguration = {};
|
|
22
|
+
|
|
23
|
+
require('./handlers')(core);
|
|
24
|
+
require('./install/express-session')(core);
|
|
28
25
|
|
|
29
26
|
sessionConfiguration.install = function() {
|
|
30
27
|
callChildComponentMethodsSync(sessionConfiguration, 'install');
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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
|
+
|
|
17
|
+
const util = require('util');
|
|
18
|
+
const { toLowerCase } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../common');
|
|
20
|
+
|
|
21
|
+
module.exports = function (core) {
|
|
22
|
+
const {
|
|
23
|
+
assess: {
|
|
24
|
+
eventFactory: { createSessionEvent },
|
|
25
|
+
sessionConfiguration: {
|
|
26
|
+
handleHttpOnly,
|
|
27
|
+
handleSecure,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
depHooks,
|
|
31
|
+
patcher,
|
|
32
|
+
scopes: { sources },
|
|
33
|
+
} = core;
|
|
34
|
+
|
|
35
|
+
const expressSession = core.assess.sessionConfiguration.expressSession = {};
|
|
36
|
+
|
|
37
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
38
|
+
|
|
39
|
+
expressSession.install = function () {
|
|
40
|
+
return depHooks.resolve({ name: 'express-session' }, (session) => {
|
|
41
|
+
// Return the hooked function as the export.
|
|
42
|
+
const hooked = patcher.patch(session, {
|
|
43
|
+
name: 'express.hookedSessionConstructor',
|
|
44
|
+
patchType,
|
|
45
|
+
post(data) {
|
|
46
|
+
const options = data.args[0];
|
|
47
|
+
|
|
48
|
+
// obfuscate the cookie secret
|
|
49
|
+
if (Array.isArray(data.args) && data.args[0] && data.args[0].secret) {
|
|
50
|
+
data.args[0].secret = '[HIDDEN]';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { cookie } = options || {};
|
|
54
|
+
const hasOwnPropertyHttpOnly = cookie && Object.prototype.hasOwnProperty.call(
|
|
55
|
+
cookie,
|
|
56
|
+
'httpOnly'
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// httpOnly is true by default if it's not provided
|
|
60
|
+
const checkForHTTPOnly =
|
|
61
|
+
cookie && hasOwnPropertyHttpOnly
|
|
62
|
+
? !(cookie.httpOnly === true)
|
|
63
|
+
: false;
|
|
64
|
+
|
|
65
|
+
// secure is false by default
|
|
66
|
+
const checkForSecure = cookie ? !(cookie.secure === true) : true;
|
|
67
|
+
|
|
68
|
+
// skip instrumentation since the options are set correctly
|
|
69
|
+
if (!checkForHTTPOnly && !checkForSecure) return;
|
|
70
|
+
|
|
71
|
+
const sessionEvent = createSessionEvent({
|
|
72
|
+
args: [{
|
|
73
|
+
tracked: false,
|
|
74
|
+
value: inspect(options),
|
|
75
|
+
}],
|
|
76
|
+
context: `expressSession(${inspect(data.args)})`,
|
|
77
|
+
history: [],
|
|
78
|
+
name: 'express.hookedSessionConstructor',
|
|
79
|
+
moduleName: 'express-session',
|
|
80
|
+
methodName: '',
|
|
81
|
+
object: {
|
|
82
|
+
tracked: false,
|
|
83
|
+
value: 'Express.Response',
|
|
84
|
+
},
|
|
85
|
+
result: {
|
|
86
|
+
tracked: false,
|
|
87
|
+
value: undefined,
|
|
88
|
+
},
|
|
89
|
+
source: 'P0',
|
|
90
|
+
stacktraceOpts: {
|
|
91
|
+
constructorOpt: data.hooked,
|
|
92
|
+
},
|
|
93
|
+
framework: 'express',
|
|
94
|
+
options,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
patcher.patch(data, 'result', {
|
|
98
|
+
name: 'express-session.middleware',
|
|
99
|
+
patchType,
|
|
100
|
+
pre(data) {
|
|
101
|
+
const [, res] = data.args;
|
|
102
|
+
|
|
103
|
+
const sourceContext = sources.getStore()?.assess;
|
|
104
|
+
if (!sourceContext) return;
|
|
105
|
+
|
|
106
|
+
patcher.patch(res, 'setHeader', {
|
|
107
|
+
name: 'http.setHeader',
|
|
108
|
+
patchType,
|
|
109
|
+
pre({ args: [key, value] }) {
|
|
110
|
+
if (toLowerCase(key) !== 'set-cookie') return;
|
|
111
|
+
|
|
112
|
+
if (checkForHTTPOnly) {
|
|
113
|
+
handleHttpOnly(sourceContext, value, sessionEvent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (checkForSecure) {
|
|
117
|
+
handleSecure(sourceContext, value, sessionEvent);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return hooked;
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return expressSession;
|
|
131
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"node": ">= 14.15.0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@contrast/distringuish": "^4.
|
|
16
|
+
"@contrast/distringuish": "^4.4.0",
|
|
17
17
|
"@contrast/scopes": "1.4.0",
|
|
18
|
-
"@contrast/common": "1.
|
|
18
|
+
"@contrast/common": "1.14.0",
|
|
19
19
|
"parseurl": "^1.3.3"
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -1,79 +0,0 @@
|
|
|
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
|
-
};
|