@contrast/assess 1.11.0 → 1.13.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/LICENSE +12 -0
- package/lib/dataflow/index.js +0 -1
- package/lib/dataflow/propagation/index.js +2 -0
- package/lib/dataflow/propagation/install/JSON/parse.js +2 -4
- package/lib/dataflow/propagation/install/JSON/stringify.js +2 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +2 -1
- package/lib/dataflow/propagation/install/buffer.js +2 -4
- package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -1
- package/lib/dataflow/propagation/install/contrast-methods/string.js +2 -4
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +2 -4
- package/lib/dataflow/propagation/install/decode-uri-component.js +2 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +2 -1
- package/lib/dataflow/propagation/install/encode-uri-component.js +2 -1
- package/lib/dataflow/propagation/install/escape-html.js +2 -1
- package/lib/dataflow/propagation/install/escape.js +2 -1
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +2 -1
- package/lib/dataflow/propagation/install/joi/index.js +35 -0
- package/lib/dataflow/propagation/install/joi/keys.js +140 -0
- package/lib/dataflow/propagation/install/joi/string-schema.js +269 -0
- package/lib/dataflow/propagation/install/joi/values.js +141 -0
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -4
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +2 -1
- package/lib/dataflow/propagation/install/path/basename.js +2 -4
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -4
- package/lib/dataflow/propagation/install/path/normalize.js +2 -4
- package/lib/dataflow/propagation/install/pug/index.js +2 -2
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +2 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +2 -1
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -4
- package/lib/dataflow/propagation/install/send.js +60 -0
- package/lib/dataflow/propagation/install/sequelize.js +6 -8
- package/lib/dataflow/propagation/install/sql-template-strings.js +2 -1
- package/lib/dataflow/propagation/install/string/concat.js +2 -1
- package/lib/dataflow/propagation/install/string/format-methods.js +2 -1
- package/lib/dataflow/propagation/install/string/html-methods.js +2 -1
- package/lib/dataflow/propagation/install/string/index.js +2 -1
- package/lib/dataflow/propagation/install/string/match-all.js +1 -1
- package/lib/dataflow/propagation/install/string/match.js +1 -1
- package/lib/dataflow/propagation/install/string/replace.js +2 -1
- package/lib/dataflow/propagation/install/string/slice.js +2 -1
- package/lib/dataflow/propagation/install/string/split.js +2 -1
- package/lib/dataflow/propagation/install/string/substring.js +2 -1
- package/lib/dataflow/propagation/install/string/trim.js +2 -1
- package/lib/dataflow/propagation/install/unescape.js +2 -1
- package/lib/dataflow/propagation/install/url/domain-parsers.js +2 -1
- package/lib/dataflow/propagation/install/url/parse.js +3 -2
- package/lib/dataflow/propagation/install/url/searchParams.js +17 -10
- package/lib/dataflow/propagation/install/url/url.js +2 -1
- package/lib/dataflow/propagation/install/validator/hooks.js +2 -1
- package/lib/dataflow/sinks/install/child-process.js +1 -1
- package/lib/dataflow/sinks/install/eval.js +1 -1
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +1 -1
- package/lib/dataflow/sinks/install/fs.js +3 -3
- package/lib/dataflow/sinks/install/function.js +1 -1
- package/lib/dataflow/sinks/install/http/request.js +1 -1
- package/lib/dataflow/sinks/install/http/server-response.js +1 -1
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +1 -1
- package/lib/dataflow/sinks/install/marsdb.js +1 -1
- package/lib/dataflow/sinks/install/mongodb.js +2 -2
- package/lib/dataflow/sinks/install/mssql.js +1 -1
- package/lib/dataflow/sinks/install/mysql.js +2 -2
- package/lib/dataflow/sinks/install/postgres.js +1 -1
- package/lib/dataflow/sinks/install/sequelize.js +1 -1
- package/lib/dataflow/sinks/install/sqlite3.js +1 -1
- package/lib/dataflow/sinks/install/vm.js +1 -1
- package/lib/dataflow/sources/handler.js +2 -2
- package/lib/dataflow/sources/install/body-parser1.js +2 -2
- package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
- package/lib/dataflow/sources/install/http.js +11 -10
- package/lib/dataflow/tracker.js +1 -5
- package/lib/{dataflow/event-factory.js → event-factory.js} +57 -1
- package/lib/index.js +3 -1
- package/lib/response-scanning/install/http.js +3 -2
- package/lib/session-configuration/common.js +19 -0
- package/lib/session-configuration/handlers.js +86 -0
- package/lib/session-configuration/index.js +5 -8
- package/lib/session-configuration/install/express-session.js +131 -0
- package/package.json +11 -10
- package/lib/session-configuration/install/http.js +0 -79
|
@@ -39,10 +39,10 @@ module.exports = function(core) {
|
|
|
39
39
|
config,
|
|
40
40
|
scopes: { sources },
|
|
41
41
|
assess: {
|
|
42
|
+
eventFactory: { createSinkEvent },
|
|
42
43
|
dataflow: {
|
|
43
44
|
tracker,
|
|
44
|
-
sinks: { isVulnerable, reportFindings, reportSafePositive }
|
|
45
|
-
eventFactory: { createSinkEvent },
|
|
45
|
+
sinks: { isVulnerable, reportFindings, reportSafePositive }
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
} = core;
|
|
@@ -58,10 +58,10 @@ module.exports = function(core) {
|
|
|
58
58
|
patcher,
|
|
59
59
|
scopes: { sources },
|
|
60
60
|
assess: {
|
|
61
|
+
eventFactory: { createSinkEvent },
|
|
61
62
|
dataflow: {
|
|
62
63
|
tracker,
|
|
63
64
|
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
64
|
-
eventFactory: { createSinkEvent },
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
} = core;
|
|
@@ -34,12 +34,12 @@ module.exports = function(core) {
|
|
|
34
34
|
const {
|
|
35
35
|
depHooks,
|
|
36
36
|
patcher,
|
|
37
|
-
scopes: { sources },
|
|
37
|
+
scopes: { instrumentation, sources },
|
|
38
38
|
assess: {
|
|
39
|
+
eventFactory: { createSinkEvent },
|
|
39
40
|
dataflow: {
|
|
40
41
|
tracker,
|
|
41
42
|
sinks: { isVulnerable, reportFindings },
|
|
42
|
-
eventFactory: { createSinkEvent },
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
} = core;
|
|
@@ -62,7 +62,7 @@ module.exports = function(core) {
|
|
|
62
62
|
const pre = (name, method, moduleName = 'fs', fullMethodName = '') => (data) => {
|
|
63
63
|
const { name: methodName, indices } = method;
|
|
64
64
|
const store = sources.getStore()?.assess;
|
|
65
|
-
if (!store) return;
|
|
65
|
+
if (!store || instrumentation.isLocked()) return;
|
|
66
66
|
|
|
67
67
|
const values = getValues(indices, data.args);
|
|
68
68
|
if (!values.length) return;
|
|
@@ -46,10 +46,10 @@ module.exports = function(core) {
|
|
|
46
46
|
patcher,
|
|
47
47
|
scopes: { sources, instrumentation },
|
|
48
48
|
assess: {
|
|
49
|
+
eventFactory: { createSinkEvent },
|
|
49
50
|
dataflow: {
|
|
50
51
|
tracker,
|
|
51
52
|
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
52
|
-
eventFactory: { createSinkEvent },
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
} = core;
|
|
@@ -38,13 +38,13 @@ module.exports = function(core) {
|
|
|
38
38
|
patcher,
|
|
39
39
|
scopes: { sources },
|
|
40
40
|
assess: {
|
|
41
|
+
eventFactory: { createSinkEvent },
|
|
41
42
|
dataflow: {
|
|
42
43
|
tracker,
|
|
43
44
|
sinks: {
|
|
44
45
|
isVulnerable,
|
|
45
46
|
reportFindings
|
|
46
47
|
},
|
|
47
|
-
eventFactory: { createSinkEvent },
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
} = core;
|
|
@@ -40,6 +40,7 @@ module.exports = function(core) {
|
|
|
40
40
|
patcher,
|
|
41
41
|
scopes: { sources },
|
|
42
42
|
assess: {
|
|
43
|
+
eventFactory: { createSinkEvent },
|
|
43
44
|
dataflow: {
|
|
44
45
|
tracker,
|
|
45
46
|
sinks: {
|
|
@@ -48,7 +49,6 @@ module.exports = function(core) {
|
|
|
48
49
|
reportSafePositive,
|
|
49
50
|
isSafeContentType
|
|
50
51
|
},
|
|
51
|
-
eventFactory: { createSinkEvent },
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
} = core;
|
|
@@ -39,10 +39,10 @@ module.exports = function(core) {
|
|
|
39
39
|
config,
|
|
40
40
|
scopes: { sources },
|
|
41
41
|
assess: {
|
|
42
|
+
eventFactory: { createSinkEvent },
|
|
42
43
|
dataflow: {
|
|
43
44
|
tracker,
|
|
44
45
|
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
45
|
-
eventFactory: { createSinkEvent },
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
} = core;
|
|
@@ -43,10 +43,10 @@ module.exports = function(core) {
|
|
|
43
43
|
patcher,
|
|
44
44
|
scopes: { sources, instrumentation },
|
|
45
45
|
assess: {
|
|
46
|
+
eventFactory: { createSinkEvent },
|
|
46
47
|
dataflow: {
|
|
47
48
|
tracker,
|
|
48
49
|
sinks: { isVulnerable, reportFindings },
|
|
49
|
-
eventFactory: { createSinkEvent },
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
} = core;
|
|
@@ -74,10 +74,10 @@ module.exports = function(core) {
|
|
|
74
74
|
patcher,
|
|
75
75
|
scopes: { sources, instrumentation },
|
|
76
76
|
assess: {
|
|
77
|
+
eventFactory: { createSinkEvent },
|
|
77
78
|
dataflow: {
|
|
78
79
|
tracker,
|
|
79
|
-
sinks: { isVulnerable, runInActiveSink, isLocked, reportFindings, reportSafePositive }
|
|
80
|
-
eventFactory: { createSinkEvent }
|
|
80
|
+
sinks: { isVulnerable, runInActiveSink, isLocked, reportFindings, reportSafePositive }
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
} = core;
|
|
@@ -39,10 +39,10 @@ module.exports = function(core) {
|
|
|
39
39
|
config,
|
|
40
40
|
scopes: { sources },
|
|
41
41
|
assess: {
|
|
42
|
+
eventFactory: { createSinkEvent },
|
|
42
43
|
dataflow: {
|
|
43
44
|
tracker,
|
|
44
45
|
sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
|
|
45
|
-
eventFactory: { createSinkEvent },
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
} = core;
|
|
@@ -46,10 +46,10 @@ module.exports = function(core) {
|
|
|
46
46
|
patcher,
|
|
47
47
|
scopes: { sources },
|
|
48
48
|
assess: {
|
|
49
|
+
eventFactory: { createSinkEvent },
|
|
49
50
|
dataflow: {
|
|
50
51
|
tracker,
|
|
51
|
-
sinks: { isVulnerable, isLocked, reportFindings }
|
|
52
|
-
eventFactory: { createSinkEvent },
|
|
52
|
+
sinks: { isVulnerable, isLocked, reportFindings }
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
} = core;
|
|
@@ -30,10 +30,10 @@ module.exports = function(core) {
|
|
|
30
30
|
patcher,
|
|
31
31
|
scopes: { sources },
|
|
32
32
|
assess: {
|
|
33
|
+
eventFactory: { createSinkEvent },
|
|
33
34
|
dataflow: {
|
|
34
35
|
tracker,
|
|
35
36
|
sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
|
|
36
|
-
eventFactory: { createSinkEvent },
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
} = core;
|
|
@@ -35,10 +35,10 @@ module.exports = function(core) {
|
|
|
35
35
|
config,
|
|
36
36
|
scopes: { sources },
|
|
37
37
|
assess: {
|
|
38
|
+
eventFactory: { createSinkEvent },
|
|
38
39
|
dataflow: {
|
|
39
40
|
tracker,
|
|
40
41
|
sinks: { isVulnerable, runInActiveSink, reportFindings, reportSafePositive },
|
|
41
|
-
eventFactory: { createSinkEvent },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
} = core;
|
|
@@ -35,10 +35,10 @@ module.exports = function(core) {
|
|
|
35
35
|
patcher,
|
|
36
36
|
scopes: { sources },
|
|
37
37
|
assess: {
|
|
38
|
+
eventFactory: { createSinkEvent },
|
|
38
39
|
dataflow: {
|
|
39
40
|
tracker,
|
|
40
41
|
sinks: { isVulnerable, isLocked, reportFindings },
|
|
41
|
-
eventFactory: { createSinkEvent },
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
} = core;
|
|
@@ -48,6 +48,7 @@ module.exports = function(core) {
|
|
|
48
48
|
patcher,
|
|
49
49
|
scopes: { sources, instrumentation },
|
|
50
50
|
assess: {
|
|
51
|
+
eventFactory: { createSinkEvent },
|
|
51
52
|
dataflow: {
|
|
52
53
|
tracker,
|
|
53
54
|
sinks: {
|
|
@@ -57,7 +58,6 @@ module.exports = function(core) {
|
|
|
57
58
|
reportFindings,
|
|
58
59
|
reportSafePositive,
|
|
59
60
|
},
|
|
60
|
-
eventFactory: { createSinkEvent },
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
63
|
} = core;
|
|
@@ -31,7 +31,7 @@ module.exports = function init(core) {
|
|
|
31
31
|
|
|
32
32
|
const createPreHook = (name) => (data) => {
|
|
33
33
|
const [req, , next] = data.args;
|
|
34
|
-
data.args[2] = function contrastNext(...args) {
|
|
34
|
+
data.args[2] = scopes.wrap(function contrastNext(...args) {
|
|
35
35
|
const sourceContext = scopes.sources.getStore()?.assess;
|
|
36
36
|
|
|
37
37
|
if (!sourceContext) {
|
|
@@ -86,7 +86,7 @@ module.exports = function init(core) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return next(...args);
|
|
89
|
-
};
|
|
89
|
+
});
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
assess.dataflow.sources.bodyParser1Instrumentation = {
|
|
@@ -73,17 +73,18 @@ module.exports = function(core) {
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
patcher.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
if (!patcher.hooks.get(res?.setHeader)?.funcKeys.has(`${patchType}:set-header`)) {
|
|
77
|
+
patcher.patch(res, 'setHeader', {
|
|
78
|
+
name: 'set-header',
|
|
79
|
+
patchType,
|
|
80
|
+
pre(data) {
|
|
81
|
+
const [name = '', value] = data.args;
|
|
82
|
+
if (toLowerCase(name) === 'content-type' && scopes.sources.getStore()?.assess && value) {
|
|
83
|
+
store.assess.responseData.contentType = value;
|
|
84
|
+
}
|
|
84
85
|
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
87
88
|
|
|
88
89
|
let uriPath, queries;
|
|
89
90
|
const ix = req.url.indexOf('?');
|
package/lib/dataflow/tracker.js
CHANGED
|
@@ -26,7 +26,7 @@ module.exports = function(core) {
|
|
|
26
26
|
scopes: { sources },
|
|
27
27
|
} = core;
|
|
28
28
|
|
|
29
|
-
const eventFactory = core.assess.
|
|
29
|
+
const eventFactory = core.assess.eventFactory = {};
|
|
30
30
|
|
|
31
31
|
eventFactory.createdEvents = new WeakSet();
|
|
32
32
|
|
|
@@ -214,5 +214,61 @@ module.exports = function(core) {
|
|
|
214
214
|
return event;
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
eventFactory.createSessionEvent = function(data) {
|
|
218
|
+
const {
|
|
219
|
+
context,
|
|
220
|
+
name = '',
|
|
221
|
+
moduleName,
|
|
222
|
+
methodName,
|
|
223
|
+
object = { value: null, tracked: false },
|
|
224
|
+
args = [],
|
|
225
|
+
result = { value: null, tracked: false },
|
|
226
|
+
source,
|
|
227
|
+
stacktraceOpts,
|
|
228
|
+
framework,
|
|
229
|
+
options
|
|
230
|
+
} = data;
|
|
231
|
+
|
|
232
|
+
if (!name) {
|
|
233
|
+
logger.debug({ data }, 'no sink event name');
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (
|
|
238
|
+
(!source || !source.match(annotationRegExp))
|
|
239
|
+
) {
|
|
240
|
+
logger.debug({ data }, 'malformed or missing sink event source field');
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let stack;
|
|
245
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
246
|
+
stack = createSnapshot(stacktraceOpts)();
|
|
247
|
+
} else {
|
|
248
|
+
stack = [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const event = {
|
|
252
|
+
args,
|
|
253
|
+
context,
|
|
254
|
+
history: [],
|
|
255
|
+
name,
|
|
256
|
+
moduleName,
|
|
257
|
+
methodName,
|
|
258
|
+
object,
|
|
259
|
+
result,
|
|
260
|
+
source,
|
|
261
|
+
stack,
|
|
262
|
+
tags: {},
|
|
263
|
+
time: Date.now(),
|
|
264
|
+
framework,
|
|
265
|
+
options,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
eventFactory.createdEvents.add(event);
|
|
269
|
+
|
|
270
|
+
return event;
|
|
271
|
+
};
|
|
272
|
+
|
|
217
273
|
return eventFactory;
|
|
218
274
|
};
|
package/lib/index.js
CHANGED
|
@@ -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)
|
|
@@ -38,8 +38,9 @@ module.exports = function(core) {
|
|
|
38
38
|
} = core;
|
|
39
39
|
const http = core.assess.responseScanning.httpInstrumentation = {};
|
|
40
40
|
|
|
41
|
-
function parseHeaders(rawHeaders
|
|
42
|
-
const
|
|
41
|
+
function parseHeaders(rawHeaders) {
|
|
42
|
+
const headersToParse = rawHeaders || '';
|
|
43
|
+
const headersArray = split(headersToParse, '\r\n').filter(Boolean);
|
|
43
44
|
return headersArray.reduce((acc, header) => {
|
|
44
45
|
const idx = header.indexOf(':');
|
|
45
46
|
|
|
@@ -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
|
+
};
|
|
@@ -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
|
+
};
|