@contrast/assess 1.5.0 → 1.7.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 +10 -5
- package/lib/dataflow/propagation/index.js +1 -0
- package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
- package/lib/dataflow/propagation/install/decode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +5 -2
- package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/escape-html.js +7 -4
- package/lib/dataflow/propagation/install/escape.js +5 -2
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +5 -2
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -2
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
- package/lib/dataflow/propagation/install/querystring/parse.js +8 -3
- package/lib/dataflow/propagation/install/sequelize.js +310 -0
- package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
- package/lib/dataflow/propagation/install/string/match.js +2 -2
- package/lib/dataflow/propagation/install/string/replace.js +13 -4
- package/lib/dataflow/propagation/install/unescape.js +5 -2
- package/lib/dataflow/propagation/install/validator/methods.js +60 -51
- package/lib/dataflow/sinks/common.js +10 -1
- package/lib/dataflow/sinks/index.js +34 -1
- package/lib/dataflow/sinks/install/child-process.js +150 -13
- package/lib/dataflow/sinks/install/express/index.js +29 -0
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +113 -75
- package/lib/dataflow/sinks/install/fs.js +136 -0
- package/lib/dataflow/sinks/install/http.js +46 -17
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +50 -17
- package/lib/dataflow/sinks/install/marsdb.js +135 -0
- package/lib/dataflow/sinks/install/mongodb.js +322 -0
- package/lib/dataflow/sinks/install/mssql.js +19 -10
- package/lib/dataflow/sinks/install/mysql.js +138 -0
- package/lib/dataflow/sinks/install/postgres.js +37 -23
- package/lib/dataflow/sinks/install/sequelize.js +142 -0
- package/lib/dataflow/sinks/install/sqlite3.js +20 -10
- package/lib/dataflow/sources/handler.js +14 -9
- package/lib/dataflow/sources/index.js +4 -1
- package/lib/dataflow/sources/install/body-parser1.js +120 -0
- package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
- package/lib/dataflow/sources/install/express/index.js +28 -0
- package/package.json +3 -3
|
@@ -16,49 +16,56 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const util = require('util');
|
|
19
|
-
const {
|
|
20
|
-
|
|
19
|
+
const {
|
|
20
|
+
DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
|
|
21
|
+
Rule: { SQL_INJECTION: ruleId },
|
|
22
|
+
isString
|
|
23
|
+
} = require('@contrast/common');
|
|
24
|
+
const { filterSafeTags, patchType } = require('../common');
|
|
21
25
|
|
|
22
26
|
module.exports = function (core) {
|
|
23
27
|
const {
|
|
28
|
+
config,
|
|
24
29
|
depHooks,
|
|
25
30
|
patcher,
|
|
26
31
|
scopes: { sources },
|
|
27
32
|
assess: {
|
|
28
33
|
dataflow: {
|
|
29
34
|
tracker,
|
|
30
|
-
sinks: { isVulnerable, reportFindings },
|
|
35
|
+
sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
|
|
31
36
|
eventFactory: { createSinkEvent },
|
|
32
37
|
},
|
|
33
38
|
},
|
|
34
39
|
} = core;
|
|
35
40
|
|
|
36
41
|
const safeTags = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
SQL_ENCODED,
|
|
43
|
+
LIMITED_CHARS,
|
|
44
|
+
CUSTOM_VALIDATED,
|
|
45
|
+
CUSTOM_ENCODED,
|
|
41
46
|
];
|
|
42
|
-
|
|
47
|
+
|
|
43
48
|
const inspect = patcher.unwrap(util.inspect);
|
|
44
49
|
|
|
45
50
|
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
46
51
|
|
|
47
|
-
const preHook = (methodSignature
|
|
52
|
+
const preHook = (methodSignature) => (data) => {
|
|
48
53
|
const assessStore = sources.getStore()?.assess;
|
|
49
|
-
if (!assessStore) return;
|
|
54
|
+
if (!assessStore || isLocked(ruleId)) return;
|
|
50
55
|
|
|
51
56
|
const [arg0] = data.args;
|
|
52
57
|
const query = arg0?.text || arg0;
|
|
53
58
|
if (!query || !isString(query)) return;
|
|
54
59
|
|
|
55
60
|
const strInfo = tracker.getData(query);
|
|
61
|
+
// todo: if the query isn't tracked but users are sending tracked strings in the values
|
|
62
|
+
// array (args[1]), then shouldn't we report a "safe-positive" event of some kind?
|
|
56
63
|
if (!strInfo) return;
|
|
57
64
|
|
|
58
65
|
const objValue = methodSignature.includes('native') ? 'pg.native.Client' : 'pg.Client';
|
|
59
66
|
const arg0Val = inspect(arg0);
|
|
60
67
|
|
|
61
|
-
if (isVulnerable(
|
|
68
|
+
if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
62
69
|
const event = createSinkEvent({
|
|
63
70
|
args: [{
|
|
64
71
|
tracked: true,
|
|
@@ -84,10 +91,20 @@ module.exports = function (core) {
|
|
|
84
91
|
|
|
85
92
|
if (event) {
|
|
86
93
|
reportFindings({
|
|
87
|
-
ruleId
|
|
94
|
+
ruleId,
|
|
88
95
|
sinkEvent: event,
|
|
89
96
|
});
|
|
90
97
|
}
|
|
98
|
+
} else if (config.assess.safe_positives.enable) {
|
|
99
|
+
reportSafePositive({
|
|
100
|
+
name: methodSignature,
|
|
101
|
+
ruleId,
|
|
102
|
+
safeTags: filterSafeTags(safeTags, strInfo),
|
|
103
|
+
strInfo: {
|
|
104
|
+
value: strInfo.value,
|
|
105
|
+
tags: strInfo.tags,
|
|
106
|
+
}
|
|
107
|
+
});
|
|
91
108
|
}
|
|
92
109
|
};
|
|
93
110
|
|
|
@@ -95,11 +112,11 @@ module.exports = function (core) {
|
|
|
95
112
|
const pgClientQueryPatchName = 'pg.Client.prototype.query';
|
|
96
113
|
depHooks.resolve(
|
|
97
114
|
{ name: 'pg', file: 'lib/client.js' },
|
|
98
|
-
(client
|
|
115
|
+
(client) => {
|
|
99
116
|
patcher.patch(client.prototype, 'query', {
|
|
100
117
|
name: pgClientQueryPatchName,
|
|
101
118
|
patchType,
|
|
102
|
-
pre: preHook('pg/lib/client.prototype.query'
|
|
119
|
+
pre: preHook('pg/lib/client.prototype.query'),
|
|
103
120
|
});
|
|
104
121
|
},
|
|
105
122
|
);
|
|
@@ -107,18 +124,16 @@ module.exports = function (core) {
|
|
|
107
124
|
const pgNativeClientQueryPatchName = 'pg.native.Client.prototype.query';
|
|
108
125
|
depHooks.resolve(
|
|
109
126
|
{ name: 'pg', file: 'lib/native/client.js' },
|
|
110
|
-
(client
|
|
127
|
+
(client) => {
|
|
111
128
|
patcher.patch(client.prototype, 'query', {
|
|
112
129
|
name: pgNativeClientQueryPatchName,
|
|
113
130
|
patchType,
|
|
114
|
-
pre: preHook('pg/lib/native/client.prototype.query'
|
|
131
|
+
pre: preHook('pg/lib/native/client.prototype.query'),
|
|
115
132
|
});
|
|
116
133
|
},
|
|
117
134
|
);
|
|
118
135
|
|
|
119
|
-
|
|
120
|
-
const pgNativeClientPatchName = `${patchType}:${pgNativeClientQueryPatchName}.query`;
|
|
121
|
-
depHooks.resolve({ name: 'pg-pool' }, (pool, version) => {
|
|
136
|
+
depHooks.resolve({ name: 'pg-pool' }, (pool) => {
|
|
122
137
|
const name = 'pg-pool.Pool.prototype.query';
|
|
123
138
|
patcher.patch(pool.prototype, 'query', {
|
|
124
139
|
name,
|
|
@@ -129,14 +144,13 @@ module.exports = function (core) {
|
|
|
129
144
|
)?.funcKeys;
|
|
130
145
|
|
|
131
146
|
if (
|
|
132
|
-
funcKeys
|
|
133
|
-
|
|
134
|
-
funcKeys.has(pgNativeClientPatchName))
|
|
147
|
+
funcKeys?.has(`${patchType}:${pgClientQueryPatchName}`) ||
|
|
148
|
+
funcKeys?.has(`${patchType}:${pgNativeClientQueryPatchName}`)
|
|
135
149
|
) {
|
|
136
150
|
return;
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
preHook(name
|
|
153
|
+
preHook(name)(data);
|
|
140
154
|
},
|
|
141
155
|
});
|
|
142
156
|
});
|
|
@@ -0,0 +1,142 @@
|
|
|
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 util = require('util');
|
|
19
|
+
const {
|
|
20
|
+
Rule: { SQL_INJECTION },
|
|
21
|
+
DataflowTag: {
|
|
22
|
+
UNTRUSTED,
|
|
23
|
+
SQL_ENCODED,
|
|
24
|
+
LIMITED_CHARS,
|
|
25
|
+
CUSTOM_VALIDATED,
|
|
26
|
+
CUSTOM_ENCODED,
|
|
27
|
+
},
|
|
28
|
+
} = require('@contrast/common');
|
|
29
|
+
const { patchType, filterSafeTags } = require('../common');
|
|
30
|
+
|
|
31
|
+
module.exports = function (core) {
|
|
32
|
+
const {
|
|
33
|
+
depHooks,
|
|
34
|
+
patcher,
|
|
35
|
+
config,
|
|
36
|
+
scopes: { sources },
|
|
37
|
+
assess: {
|
|
38
|
+
dataflow: {
|
|
39
|
+
tracker,
|
|
40
|
+
sinks: { isVulnerable, runInActiveSink, reportFindings, reportSafePositive },
|
|
41
|
+
eventFactory: { createSinkEvent },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} = core;
|
|
45
|
+
|
|
46
|
+
const safeTags = [
|
|
47
|
+
SQL_ENCODED,
|
|
48
|
+
LIMITED_CHARS,
|
|
49
|
+
CUSTOM_VALIDATED,
|
|
50
|
+
CUSTOM_ENCODED
|
|
51
|
+
];
|
|
52
|
+
const requiredTag = UNTRUSTED;
|
|
53
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
54
|
+
|
|
55
|
+
const sequelize = (core.assess.dataflow.sinks.sequelize = {});
|
|
56
|
+
|
|
57
|
+
sequelize.install = function () {
|
|
58
|
+
const sequelizeQueryPatchName = 'sequelize.prototype.query';
|
|
59
|
+
depHooks.resolve({ name: 'sequelize' }, (sequelize) => {
|
|
60
|
+
patcher.patch(sequelize.prototype, 'query', {
|
|
61
|
+
name: sequelizeQueryPatchName,
|
|
62
|
+
patchType,
|
|
63
|
+
around(next, data) {
|
|
64
|
+
const { args, hooked, orig } = data;
|
|
65
|
+
const sourceContext = sources.getStore()?.assess;
|
|
66
|
+
if (!sourceContext || !args[0]) return next();
|
|
67
|
+
|
|
68
|
+
const query = typeof args[0] === 'string' ? args[0] : args[0].query;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const queryInfo = tracker.getData(query);
|
|
72
|
+
const isVulnerableQuery = isVulnerable(requiredTag, safeTags, queryInfo.tags);
|
|
73
|
+
|
|
74
|
+
if (queryInfo && !isVulnerableQuery && config.assess.safe_positives.enable) {
|
|
75
|
+
reportSafePositive({
|
|
76
|
+
name: sequelizeQueryPatchName,
|
|
77
|
+
ruleId: SQL_INJECTION,
|
|
78
|
+
safeTags: filterSafeTags(safeTags, queryInfo),
|
|
79
|
+
strInfo: {
|
|
80
|
+
value: queryInfo?.value,
|
|
81
|
+
tags: queryInfo.tags,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
!queryInfo ||
|
|
88
|
+
!isVulnerableQuery
|
|
89
|
+
) {
|
|
90
|
+
return runInActiveSink(SQL_INJECTION, async () => await next());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sqlValue =
|
|
94
|
+
typeof args[0] === 'string' ? args[0] : inspect(args[0]);
|
|
95
|
+
const inspectedOptions = args[1] ? inspect(args[1]) : '';
|
|
96
|
+
const contextArgs = args[1]
|
|
97
|
+
? `${sqlValue}, ${inspectedOptions}`
|
|
98
|
+
: sqlValue;
|
|
99
|
+
|
|
100
|
+
const reportedArgs = [{ value: sqlValue, tracked: true }];
|
|
101
|
+
args[1] &&
|
|
102
|
+
reportedArgs.push({ value: inspectedOptions, tracked: false });
|
|
103
|
+
|
|
104
|
+
const event = createSinkEvent({
|
|
105
|
+
context: `sequelize.prototype.query(${contextArgs})`,
|
|
106
|
+
name: sequelizeQueryPatchName,
|
|
107
|
+
history: [queryInfo],
|
|
108
|
+
object: {
|
|
109
|
+
value: 'sequelize.prototype',
|
|
110
|
+
tracked: false,
|
|
111
|
+
},
|
|
112
|
+
args: reportedArgs,
|
|
113
|
+
tags: queryInfo?.tags,
|
|
114
|
+
source: 'P0',
|
|
115
|
+
stacktraceOpts: {
|
|
116
|
+
contructorOpt: hooked,
|
|
117
|
+
prependFrames: [orig],
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (event) {
|
|
122
|
+
reportFindings({
|
|
123
|
+
ruleId: SQL_INJECTION,
|
|
124
|
+
sinkEvent: event,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/* c8 ignore next 3 */
|
|
128
|
+
} catch (err) {
|
|
129
|
+
core.logger.error(
|
|
130
|
+
{ name: sequelizeQueryPatchName, err },
|
|
131
|
+
'assess sink analysis failed'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return runInActiveSink(SQL_INJECTION, async () => await next());
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return sequelize;
|
|
142
|
+
};
|
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
|
+
|
|
17
18
|
const { patchType } = require('../common');
|
|
18
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
|
|
21
|
+
Rule: { SQL_INJECTION },
|
|
22
|
+
isString
|
|
23
|
+
} = require('@contrast/common');
|
|
19
24
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const safeTags = [
|
|
26
|
+
SQL_ENCODED,
|
|
27
|
+
LIMITED_CHARS,
|
|
28
|
+
CUSTOM_VALIDATED,
|
|
29
|
+
CUSTOM_ENCODED,
|
|
25
30
|
];
|
|
26
31
|
|
|
27
32
|
module.exports = function (core) {
|
|
@@ -32,7 +37,7 @@ module.exports = function (core) {
|
|
|
32
37
|
assess: {
|
|
33
38
|
dataflow: {
|
|
34
39
|
tracker,
|
|
35
|
-
sinks: { isVulnerable, reportFindings },
|
|
40
|
+
sinks: { isVulnerable, isLocked, reportFindings },
|
|
36
41
|
eventFactory: { createSinkEvent },
|
|
37
42
|
},
|
|
38
43
|
},
|
|
@@ -40,10 +45,15 @@ module.exports = function (core) {
|
|
|
40
45
|
|
|
41
46
|
const pre = (name) => (data) => {
|
|
42
47
|
const store = sources.getStore()?.assess;
|
|
43
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
!store ||
|
|
50
|
+
!data.args[0] ||
|
|
51
|
+
!isString(data.args[0]) ||
|
|
52
|
+
isLocked(SQL_INJECTION)
|
|
53
|
+
) return;
|
|
44
54
|
|
|
45
55
|
const strInfo = tracker.getData(data.args[0]);
|
|
46
|
-
if (!strInfo || !isVulnerable(
|
|
56
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
47
57
|
return;
|
|
48
58
|
}
|
|
49
59
|
|
|
@@ -69,7 +79,7 @@ module.exports = function (core) {
|
|
|
69
79
|
|
|
70
80
|
if (event) {
|
|
71
81
|
reportFindings({
|
|
72
|
-
ruleId:
|
|
82
|
+
ruleId: SQL_INJECTION,
|
|
73
83
|
sinkEvent: event,
|
|
74
84
|
});
|
|
75
85
|
}
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
InputType,
|
|
20
|
+
DataflowTag,
|
|
21
|
+
isString,
|
|
22
|
+
traverseValues
|
|
23
|
+
} = require('@contrast/common');
|
|
19
24
|
|
|
20
25
|
module.exports = function(core) {
|
|
21
26
|
const {
|
|
@@ -40,15 +45,11 @@ module.exports = function(core) {
|
|
|
40
45
|
|
|
41
46
|
const stop = value.length - 1;
|
|
42
47
|
const tags = {
|
|
43
|
-
|
|
48
|
+
[DataflowTag.UNTRUSTED]: [0, stop]
|
|
44
49
|
};
|
|
45
50
|
|
|
46
|
-
if (inputType ===
|
|
47
|
-
tags.
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (inputType === 'cookie') {
|
|
51
|
-
tags.cookie = [0, stop];
|
|
51
|
+
if (inputType === InputType.HEADER && key.toLowerCase() === 'referer') {
|
|
52
|
+
tags[DataflowTag.HEADER] = [0, stop];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
return tags;
|
|
@@ -57,7 +58,7 @@ module.exports = function(core) {
|
|
|
57
58
|
sources.handle = function({
|
|
58
59
|
context,
|
|
59
60
|
name,
|
|
60
|
-
inputType =
|
|
61
|
+
inputType = InputType.UNKNOWN,
|
|
61
62
|
stacktraceOpts,
|
|
62
63
|
data,
|
|
63
64
|
sourceContext
|
|
@@ -71,6 +72,10 @@ module.exports = function(core) {
|
|
|
71
72
|
return null;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (!context) {
|
|
76
|
+
context = inputType;
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
let stack;
|
|
75
80
|
|
|
76
81
|
traverseValues(data, (path, type, value, obj) => {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
|
-
module.exports = function(core) {
|
|
20
|
+
module.exports = function (core) {
|
|
21
21
|
const sources = core.assess.dataflow.sources = {};
|
|
22
22
|
|
|
23
23
|
// API
|
|
@@ -27,11 +27,14 @@ module.exports = function(core) {
|
|
|
27
27
|
require('./install/http')(core);
|
|
28
28
|
|
|
29
29
|
// frameworks and frameworks specific libraries
|
|
30
|
+
require('./install/express')(core);
|
|
30
31
|
require('./install/fastify')(core);
|
|
31
32
|
require('./install/koa')(core);
|
|
32
33
|
|
|
33
34
|
// libraries
|
|
35
|
+
require('./install/body-parser1')(core);
|
|
34
36
|
require('./install/busboy1')(core);
|
|
37
|
+
require('./install/cookie-parser1')(core);
|
|
35
38
|
require('./install/formidable1')(core);
|
|
36
39
|
require('./install/qs6')(core);
|
|
37
40
|
|
|
@@ -0,0 +1,120 @@
|
|
|
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 { InputType } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../common');
|
|
20
|
+
|
|
21
|
+
const METHODS = ['json', 'raw', 'text', 'urlencoded'];
|
|
22
|
+
const INPUT_TYPES = {
|
|
23
|
+
'body-parser.json.jsonParser': InputType.JSON_VALUE,
|
|
24
|
+
'body-parser.raw.rawParser': InputType.BODY,
|
|
25
|
+
'body-parser.text.textParser': InputType.BODY,
|
|
26
|
+
'body-parser.urlencoded.urlencodedParser': InputType.PARAMETER_VALUE,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = function init(core) {
|
|
30
|
+
const { assess, depHooks, logger, patcher, scopes } = core;
|
|
31
|
+
|
|
32
|
+
const createPreHook = (name) => (data) => {
|
|
33
|
+
const [req, , next] = data.args;
|
|
34
|
+
data.args[2] = function contrastNext(...args) {
|
|
35
|
+
const sourceContext = scopes.sources.getStore()?.assess;
|
|
36
|
+
|
|
37
|
+
if (!sourceContext) {
|
|
38
|
+
logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (sourceContext.parsedBody) {
|
|
43
|
+
logger.trace({ name }, 'values already tracked');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// when using a specific parser, we know the input type.
|
|
48
|
+
let inputType = INPUT_TYPES[name];
|
|
49
|
+
// when using `bodyParser()`, determine input type by content type.
|
|
50
|
+
if (!inputType) {
|
|
51
|
+
inputType = req.headers?.['content-type']?.includes('json')
|
|
52
|
+
? InputType.JSON_VALUE
|
|
53
|
+
: req.headers?.['content-type']?.includes('urlencoded')
|
|
54
|
+
? InputType.PARAMETER_VALUE
|
|
55
|
+
: InputType.BODY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
assess.dataflow.sources.handle({
|
|
60
|
+
context: 'req.body',
|
|
61
|
+
name,
|
|
62
|
+
inputType,
|
|
63
|
+
stacktraceOpts: {
|
|
64
|
+
constructorOpt: contrastNext
|
|
65
|
+
},
|
|
66
|
+
data: req.body,
|
|
67
|
+
sourceContext,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
sourceContext.parsedBody = !!Object.keys(req.body).length;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.error({ name, err }, 'unable to handle source');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return next(...args);
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
assess.dataflow.sources.bodyParser1Instrumentation = {
|
|
80
|
+
install() {
|
|
81
|
+
depHooks.resolve(
|
|
82
|
+
{ name: 'body-parser', version: '>=1.0.0' },
|
|
83
|
+
/** @param {import('body-parser').BodyParser} bodyParser */
|
|
84
|
+
(bodyParser) => {
|
|
85
|
+
bodyParser = patcher.patch(bodyParser, {
|
|
86
|
+
name: 'body-parser',
|
|
87
|
+
patchType,
|
|
88
|
+
post(data) {
|
|
89
|
+
const name = 'body-parser.bodyParser';
|
|
90
|
+
data.result = patcher.patch(data.result, {
|
|
91
|
+
name,
|
|
92
|
+
patchType,
|
|
93
|
+
pre: createPreHook(name),
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
METHODS.forEach((method) => {
|
|
99
|
+
patcher.patch(bodyParser, method, {
|
|
100
|
+
name: `body-parser.${method}`,
|
|
101
|
+
patchType,
|
|
102
|
+
post(data) {
|
|
103
|
+
const name = `body-parser.${method}.${method}Parser`;
|
|
104
|
+
data.result = patcher.patch(data.result, {
|
|
105
|
+
name,
|
|
106
|
+
patchType,
|
|
107
|
+
pre: createPreHook(name)
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return bodyParser;
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return assess.dataflow.sources.bodyParser1Instrumentation;
|
|
120
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
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 { InputType } = require('@contrast/common');
|
|
19
|
+
const { patchType } = require('../common');
|
|
20
|
+
|
|
21
|
+
module.exports = function init(core) {
|
|
22
|
+
const { assess, depHooks, logger, patcher, scopes } = core;
|
|
23
|
+
|
|
24
|
+
assess.dataflow.sources.cookieParser1Instrumentation = {
|
|
25
|
+
install() {
|
|
26
|
+
depHooks.resolve(
|
|
27
|
+
{ name: 'cookie-parser', version: '>=1.0.0' },
|
|
28
|
+
/** @param {import('cookie-parser')} cookieParser */
|
|
29
|
+
(cookieParser) =>
|
|
30
|
+
patcher.patch(cookieParser, {
|
|
31
|
+
name: 'cookie-parser',
|
|
32
|
+
patchType,
|
|
33
|
+
post(data) {
|
|
34
|
+
const name = 'cookie-parser.cookieParser';
|
|
35
|
+
data.result = patcher.patch(data.result, {
|
|
36
|
+
name,
|
|
37
|
+
patchType,
|
|
38
|
+
pre(data) {
|
|
39
|
+
const [req, , next] = data.args;
|
|
40
|
+
data.args[2] = function contrastNext(...args) {
|
|
41
|
+
const sourceContext = scopes.sources.getStore()?.assess;
|
|
42
|
+
|
|
43
|
+
if (!sourceContext) {
|
|
44
|
+
logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sourceContext.parsedCookies) {
|
|
49
|
+
logger.trace({ name }, 'cookies already tracked');
|
|
50
|
+
} else if (req.cookies) {
|
|
51
|
+
try {
|
|
52
|
+
assess.dataflow.sources.handle({
|
|
53
|
+
context: 'req.cookies',
|
|
54
|
+
name,
|
|
55
|
+
inputType: InputType.COOKIE_VALUE,
|
|
56
|
+
stacktraceOpts: {
|
|
57
|
+
constructorOpt: contrastNext
|
|
58
|
+
},
|
|
59
|
+
data: req.cookies,
|
|
60
|
+
sourceContext,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
sourceContext.parsedCookies = !!Object.keys(req.cookies).length;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error({ name, err }, 'unable to handle source');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (sourceContext.parsedSignedCookies) {
|
|
70
|
+
logger.trace({ name }, 'signedCookies already tracked');
|
|
71
|
+
} else if (req.signedCookies) {
|
|
72
|
+
try {
|
|
73
|
+
assess.dataflow.sources.handle({
|
|
74
|
+
context: 'req.signedCookies',
|
|
75
|
+
name,
|
|
76
|
+
inputType: InputType.COOKIE_VALUE,
|
|
77
|
+
stacktraceOpts: {
|
|
78
|
+
constructorOpt: contrastNext
|
|
79
|
+
},
|
|
80
|
+
data: req.signedCookies,
|
|
81
|
+
sourceContext,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
sourceContext.parsedSignedCookies = !!Object.keys(req.signedCookies).length;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
logger.error({ name, err }, 'unable to handle source');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return next(...args);
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return assess.dataflow.sources.cookieParser1Instrumentation;
|
|
101
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = function init(core) {
|
|
21
|
+
core.assess.dataflow.sources.expressInstrumentation = {
|
|
22
|
+
install() {
|
|
23
|
+
callChildComponentMethodsSync(this, 'install');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return core.assess.dataflow.sources.expressInstrumentation;
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@contrast/distringuish": "^4.1.0",
|
|
17
|
-
"@contrast/scopes": "1.
|
|
18
|
-
"@contrast/common": "1.
|
|
17
|
+
"@contrast/scopes": "1.4.0",
|
|
18
|
+
"@contrast/common": "1.10.0",
|
|
19
19
|
"parseurl": "^1.3.3"
|
|
20
20
|
}
|
|
21
21
|
}
|