@contrast/assess 1.4.0 → 1.6.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 +41 -83
- package/lib/dataflow/index.js +0 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
- package/lib/dataflow/propagation/install/contrast-methods/add.js +23 -16
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +30 -22
- package/lib/dataflow/propagation/install/decode-uri-component.js +8 -5
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -5
- package/lib/dataflow/propagation/install/encode-uri-component.js +3 -3
- package/lib/dataflow/propagation/install/escape-html.js +10 -7
- package/lib/dataflow/propagation/install/escape.js +8 -5
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +8 -5
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +8 -5
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -3
- package/lib/dataflow/propagation/install/querystring/parse.js +11 -6
- package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
- package/lib/dataflow/propagation/install/string/concat.js +4 -4
- package/lib/dataflow/propagation/install/string/format-methods.js +2 -2
- package/lib/dataflow/propagation/install/string/html-methods.js +5 -5
- package/lib/dataflow/propagation/install/string/index.js +1 -0
- package/lib/dataflow/propagation/install/string/match.js +3 -3
- package/lib/dataflow/propagation/install/string/replace.js +9 -5
- package/lib/dataflow/propagation/install/string/slice.js +104 -0
- package/lib/dataflow/propagation/install/string/split.js +4 -4
- package/lib/dataflow/propagation/install/string/substring.js +6 -4
- package/lib/dataflow/propagation/install/string/trim.js +2 -2
- package/lib/dataflow/propagation/install/unescape.js +8 -5
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -3
- package/lib/dataflow/propagation/install/validator/hooks.js +2 -2
- package/lib/dataflow/propagation/install/validator/methods.js +60 -51
- package/lib/dataflow/sinks/index.js +15 -2
- package/lib/dataflow/sinks/install/child-process.js +224 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +47 -23
- package/lib/dataflow/sinks/install/fs.js +136 -0
- package/lib/dataflow/sinks/install/http.js +48 -32
- package/lib/dataflow/sinks/install/koa/index.js +30 -0
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +122 -0
- package/lib/dataflow/sinks/install/marsdb.js +135 -0
- package/lib/dataflow/sinks/install/mongodb.js +205 -0
- package/lib/dataflow/sinks/install/mssql.js +19 -13
- package/lib/dataflow/sinks/install/mysql.js +122 -0
- package/lib/dataflow/sinks/install/postgres.js +40 -29
- package/lib/dataflow/sinks/install/sqlite3.js +99 -0
- package/lib/dataflow/sources/handler.js +19 -15
- package/lib/dataflow/sources/index.js +9 -0
- package/lib/dataflow/sources/install/busboy1.js +112 -0
- package/lib/dataflow/sources/install/fastify/fastify.js +23 -29
- package/lib/dataflow/sources/install/fastify/index.js +4 -5
- package/lib/dataflow/sources/install/formidable1.js +91 -0
- package/lib/dataflow/sources/install/http.js +35 -14
- package/lib/dataflow/sources/install/koa/index.js +32 -0
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +92 -0
- package/lib/dataflow/sources/install/koa/koa-routers.js +84 -0
- package/lib/dataflow/sources/install/koa/koa2.js +103 -0
- package/lib/dataflow/sources/install/qs6.js +84 -0
- package/lib/dataflow/utils/is-vulnerable.js +1 -1
- package/package.json +2 -2
- package/lib/dataflow/signatures/index.js +0 -2006
- package/lib/dataflow/signatures/mssql.js +0 -49
- package/lib/dataflow/sources/install/fastify/cookie.js +0 -61
|
@@ -15,8 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const {
|
|
18
|
+
const util = require('util');
|
|
19
|
+
const {
|
|
20
|
+
DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
|
|
21
|
+
Rule,
|
|
22
|
+
isString
|
|
23
|
+
} = require('@contrast/common');
|
|
20
24
|
const { patchType } = require('../common');
|
|
21
25
|
|
|
22
26
|
module.exports = function (core) {
|
|
@@ -33,40 +37,48 @@ module.exports = function (core) {
|
|
|
33
37
|
},
|
|
34
38
|
} = core;
|
|
35
39
|
|
|
36
|
-
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
37
|
-
|
|
38
40
|
const safeTags = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
SQL_ENCODED,
|
|
42
|
+
LIMITED_CHARS,
|
|
43
|
+
CUSTOM_VALIDATED,
|
|
44
|
+
CUSTOM_ENCODED,
|
|
43
45
|
];
|
|
44
|
-
|
|
46
|
+
|
|
47
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
48
|
+
|
|
49
|
+
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
45
50
|
|
|
46
51
|
const preHook = (methodSignature, version, mod, obj) => (data) => {
|
|
47
52
|
const assessStore = sources.getStore()?.assess;
|
|
48
53
|
if (!assessStore) return;
|
|
49
54
|
|
|
50
|
-
const
|
|
55
|
+
const [arg0] = data.args;
|
|
56
|
+
const query = arg0?.text || arg0;
|
|
51
57
|
if (!query || !isString(query)) return;
|
|
52
58
|
|
|
53
59
|
const strInfo = tracker.getData(query);
|
|
54
60
|
if (!strInfo) return;
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
const objValue = methodSignature.includes('native') ? 'pg.native.Client' : 'pg.Client';
|
|
63
|
+
const arg0Val = inspect(arg0);
|
|
64
|
+
|
|
65
|
+
if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
57
66
|
const event = createSinkEvent({
|
|
58
|
-
|
|
67
|
+
args: [{
|
|
68
|
+
tracked: true,
|
|
69
|
+
value: strInfo.value,
|
|
70
|
+
}],
|
|
71
|
+
context: `${objValue}.query(${arg0Val})`,
|
|
59
72
|
history: [strInfo],
|
|
73
|
+
name: methodSignature,
|
|
60
74
|
object: {
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
tracked: false,
|
|
76
|
+
value: objValue,
|
|
77
|
+
},
|
|
78
|
+
result: {
|
|
79
|
+
tracked: false,
|
|
80
|
+
value: 'Promise',
|
|
63
81
|
},
|
|
64
|
-
args: [
|
|
65
|
-
{
|
|
66
|
-
value: strInfo.value,
|
|
67
|
-
isTracked: true,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
82
|
tags: strInfo.tags,
|
|
71
83
|
source: 'P0',
|
|
72
84
|
stacktraceOpts: {
|
|
@@ -74,10 +86,12 @@ module.exports = function (core) {
|
|
|
74
86
|
},
|
|
75
87
|
});
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
if (event) {
|
|
90
|
+
reportFindings({
|
|
91
|
+
ruleId: Rule.SQL_INJECTION,
|
|
92
|
+
sinkEvent: event,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
};
|
|
83
97
|
|
|
@@ -106,8 +120,6 @@ module.exports = function (core) {
|
|
|
106
120
|
},
|
|
107
121
|
);
|
|
108
122
|
|
|
109
|
-
const pgClientPatchName = `${patchType}:${pgClientQueryPatchName}.query`;
|
|
110
|
-
const pgNativeClientPatchName = `${patchType}:${pgNativeClientQueryPatchName}.query`;
|
|
111
123
|
depHooks.resolve({ name: 'pg-pool' }, (pool, version) => {
|
|
112
124
|
const name = 'pg-pool.Pool.prototype.query';
|
|
113
125
|
patcher.patch(pool.prototype, 'query', {
|
|
@@ -119,9 +131,8 @@ module.exports = function (core) {
|
|
|
119
131
|
)?.funcKeys;
|
|
120
132
|
|
|
121
133
|
if (
|
|
122
|
-
funcKeys
|
|
123
|
-
|
|
124
|
-
funcKeys.has(pgNativeClientPatchName))
|
|
134
|
+
funcKeys?.has(`${patchType}:${pgClientQueryPatchName}`) ||
|
|
135
|
+
funcKeys?.has(`${patchType}:${pgNativeClientQueryPatchName}`)
|
|
125
136
|
) {
|
|
126
137
|
return;
|
|
127
138
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
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 { patchType } = require('../common');
|
|
19
|
+
const {
|
|
20
|
+
DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
|
|
21
|
+
Rule,
|
|
22
|
+
isString
|
|
23
|
+
} = require('@contrast/common');
|
|
24
|
+
|
|
25
|
+
const safeTags = [
|
|
26
|
+
SQL_ENCODED,
|
|
27
|
+
LIMITED_CHARS,
|
|
28
|
+
CUSTOM_VALIDATED,
|
|
29
|
+
CUSTOM_ENCODED,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
module.exports = function (core) {
|
|
33
|
+
const {
|
|
34
|
+
depHooks,
|
|
35
|
+
patcher,
|
|
36
|
+
scopes: { sources },
|
|
37
|
+
assess: {
|
|
38
|
+
dataflow: {
|
|
39
|
+
tracker,
|
|
40
|
+
sinks: { isVulnerable, reportFindings },
|
|
41
|
+
eventFactory: { createSinkEvent },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} = core;
|
|
45
|
+
|
|
46
|
+
const pre = (name) => (data) => {
|
|
47
|
+
const store = sources.getStore()?.assess;
|
|
48
|
+
if (!store || !data.args[0] || !isString(data.args[0])) return;
|
|
49
|
+
|
|
50
|
+
const strInfo = tracker.getData(data.args[0]);
|
|
51
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const event = createSinkEvent({
|
|
56
|
+
name,
|
|
57
|
+
history: [strInfo],
|
|
58
|
+
object: {
|
|
59
|
+
value: '[Module<sqlite3>].Database',
|
|
60
|
+
tracked: false,
|
|
61
|
+
},
|
|
62
|
+
args: [
|
|
63
|
+
{
|
|
64
|
+
value: strInfo.value,
|
|
65
|
+
tracked: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
tags: strInfo.tags,
|
|
69
|
+
source: 'P0',
|
|
70
|
+
stacktraceOpts: {
|
|
71
|
+
contructorOpt: data.hooked,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (event) {
|
|
76
|
+
reportFindings({
|
|
77
|
+
ruleId: Rule.SQL_INJECTION,
|
|
78
|
+
sinkEvent: event,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
core.assess.dataflow.sinks.sqlite3 = {
|
|
84
|
+
install() {
|
|
85
|
+
depHooks.resolve({ name: 'sqlite3' }, sqlite3 => {
|
|
86
|
+
['all', 'run', 'get', 'each', 'exec', 'prepare'].forEach((method) => {
|
|
87
|
+
const name = `sqlite3.Database.prototype.${method}`;
|
|
88
|
+
patcher.patch(sqlite3.Database.prototype, method, {
|
|
89
|
+
name,
|
|
90
|
+
patchType,
|
|
91
|
+
pre: pre(name)
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return core.assess.dataflow.sinks.sqlite3;
|
|
99
|
+
};
|
|
@@ -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,40 +45,34 @@ 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;
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
sources.handle = function({
|
|
59
|
+
context,
|
|
58
60
|
name,
|
|
59
|
-
inputType =
|
|
61
|
+
inputType = InputType.UNKNOWN,
|
|
60
62
|
stacktraceOpts,
|
|
61
63
|
data,
|
|
64
|
+
sourceContext
|
|
62
65
|
}) {
|
|
63
66
|
if (!data) return;
|
|
64
67
|
|
|
65
68
|
const max = config.assess.max_context_source_events;
|
|
66
|
-
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
67
69
|
|
|
68
70
|
if (!sourceContext) {
|
|
69
71
|
core.logger.trace({ inputType, name }, 'skipping assess source handling - no request context');
|
|
70
72
|
return null;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
const stack = config.assess.stacktraces === 'NONE'
|
|
75
|
-
? emptyStack
|
|
76
|
-
: createSnapshot(stacktraceOpts)();
|
|
75
|
+
let stack;
|
|
77
76
|
|
|
78
77
|
traverseValues(data, (path, type, value, obj) => {
|
|
79
78
|
if (sourceContext.sourceEventsCount >= max) {
|
|
@@ -82,10 +81,15 @@ module.exports = function(core) {
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
if (isString(value) && value.length) {
|
|
84
|
+
stack = stack || config.assess.stacktraces === 'NONE'
|
|
85
|
+
? emptyStack
|
|
86
|
+
: createSnapshot(stacktraceOpts)();
|
|
85
87
|
const key = path[path.length - 1];
|
|
86
88
|
const pathName = path.join('.');
|
|
87
89
|
const event = createSourceEvent({
|
|
90
|
+
context: `${context}.${pathName}`,
|
|
88
91
|
name,
|
|
92
|
+
fieldName: key,
|
|
89
93
|
pathName,
|
|
90
94
|
stack,
|
|
91
95
|
inputType,
|
|
@@ -100,7 +104,7 @@ module.exports = function(core) {
|
|
|
100
104
|
|
|
101
105
|
const { extern } = tracker.track(value, event);
|
|
102
106
|
if (extern) {
|
|
103
|
-
logger.trace({ extern, key, inputType }, 'tracked');
|
|
107
|
+
logger.trace({ extern, key, name, inputType }, 'tracked');
|
|
104
108
|
obj[key] = extern;
|
|
105
109
|
sourceContext.sourceEventsCount++;
|
|
106
110
|
}
|
|
@@ -23,8 +23,17 @@ module.exports = function(core) {
|
|
|
23
23
|
// API
|
|
24
24
|
require('./handler')(core);
|
|
25
25
|
// installers
|
|
26
|
+
// general
|
|
26
27
|
require('./install/http')(core);
|
|
28
|
+
|
|
29
|
+
// frameworks and frameworks specific libraries
|
|
27
30
|
require('./install/fastify')(core);
|
|
31
|
+
require('./install/koa')(core);
|
|
32
|
+
|
|
33
|
+
// libraries
|
|
34
|
+
require('./install/busboy1')(core);
|
|
35
|
+
require('./install/formidable1')(core);
|
|
36
|
+
require('./install/qs6')(core);
|
|
28
37
|
|
|
29
38
|
sources.install = function install() {
|
|
30
39
|
callChildComponentMethodsSync(sources, 'install');
|
|
@@ -0,0 +1,112 @@
|
|
|
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 = (core) => {
|
|
22
|
+
const {
|
|
23
|
+
depHooks,
|
|
24
|
+
patcher,
|
|
25
|
+
logger,
|
|
26
|
+
assess: { dataflow: { sources } },
|
|
27
|
+
} = core;
|
|
28
|
+
|
|
29
|
+
const name = 'busboy';
|
|
30
|
+
|
|
31
|
+
function createPreHook(finalEventName) {
|
|
32
|
+
return function(data) {
|
|
33
|
+
const { orig, hooked } = data;
|
|
34
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
35
|
+
const inputType = InputType.MULTIPART_VALUE;
|
|
36
|
+
|
|
37
|
+
if (!sourceContext) {
|
|
38
|
+
logger.error({ inputType, name }, 'unable to handle source. Missing `sourceContext`');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (sourceContext.parsedBody) {
|
|
43
|
+
logger.trace({ inputType, name }, 'values already tracked');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [eventName, fieldName, fieldValue] = data.args;
|
|
48
|
+
|
|
49
|
+
if (eventName === 'field' && fieldName && fieldValue) {
|
|
50
|
+
const obj = {
|
|
51
|
+
[fieldName]: fieldValue
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
sources.handle({
|
|
56
|
+
context: 'req.body',
|
|
57
|
+
name,
|
|
58
|
+
inputType,
|
|
59
|
+
stacktraceOpts: {
|
|
60
|
+
constructorOpt: hooked,
|
|
61
|
+
prependFrames: [orig]
|
|
62
|
+
},
|
|
63
|
+
data: obj,
|
|
64
|
+
sourceContext
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
data.args[2] = obj[fieldName];
|
|
68
|
+
sourceContext.busboyParsedBody = true;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.error({ err, inputType, name }, 'unable to handle source');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (eventName === finalEventName && sourceContext.busboyParsedBody) {
|
|
75
|
+
sourceContext.parsedBody = true;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Patch `busboy`
|
|
81
|
+
function install() {
|
|
82
|
+
depHooks.resolve({ name, version: '<1.0.0' }, (busboy) => {
|
|
83
|
+
patcher.patch(busboy.prototype, 'emit', {
|
|
84
|
+
name: 'busboy.prototype.emit',
|
|
85
|
+
patchType,
|
|
86
|
+
pre: createPreHook('finish')
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
depHooks.resolve({ name, version: '>=1.0.0' }, (busboy) => patcher.patch(
|
|
92
|
+
busboy, {
|
|
93
|
+
name,
|
|
94
|
+
patchType,
|
|
95
|
+
post(data) {
|
|
96
|
+
data.result = patcher.patch(data.result, 'emit', {
|
|
97
|
+
name: 'busboy.emit',
|
|
98
|
+
patchType,
|
|
99
|
+
pre: createPreHook('close')
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const busboy1Instrumentation = sources.busboy1Instrumentation = {
|
|
107
|
+
install
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return busboy1Instrumentation;
|
|
111
|
+
};
|
|
112
|
+
|
|
@@ -31,50 +31,44 @@ module.exports = function(core) {
|
|
|
31
31
|
depHooks.resolve({ name: 'fastify', version: '>=3.0.0' }, (fastify) => patcher.patch(fastify, {
|
|
32
32
|
name: 'fastify.constructor',
|
|
33
33
|
patchType,
|
|
34
|
-
post({
|
|
35
|
-
server.addHook('onRequest', function handler(request, reply, done) {
|
|
36
|
-
const name = 'fastify.onRequest';
|
|
37
|
-
const inputType = InputType.HEADER;
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
sources.handle({
|
|
41
|
-
data: request.raw.headers,
|
|
42
|
-
inputType,
|
|
43
|
-
name,
|
|
44
|
-
stacktraceOpts: {
|
|
45
|
-
constructorOpt: handler
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
} catch (err) {
|
|
50
|
-
logger.error({ err, inputType, name }, 'unable to handle fastify source');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
done();
|
|
54
|
-
});
|
|
55
|
-
|
|
34
|
+
post({ result: server }) {
|
|
56
35
|
server.addHook('preValidation', function preValidationHandler(request, reply, done) {
|
|
57
36
|
const name = 'fastify.preValidation';
|
|
58
37
|
const bodyType = request?.headers?.['content-type']?.includes('/json')
|
|
59
38
|
? InputType.JSON_VALUE
|
|
60
|
-
:
|
|
39
|
+
: typeof request.body == 'object'
|
|
40
|
+
? InputType.PARAMETER_VALUE
|
|
41
|
+
: InputType.BODY;
|
|
42
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
43
|
+
|
|
44
|
+
if (!sourceContext) {
|
|
45
|
+
logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
61
48
|
|
|
62
49
|
[
|
|
63
|
-
{ key: 'query', inputType: InputType.
|
|
64
|
-
{ key: 'params', inputType: InputType.URL_PARAMETER },
|
|
65
|
-
{ key: 'body', inputType: bodyType }
|
|
66
|
-
].forEach(({ key, inputType }) => {
|
|
50
|
+
{ key: 'query', inputType: InputType.QUERYSTRING, alreadyTrackedFlag: 'parsedQuery' },
|
|
51
|
+
{ key: 'params', inputType: InputType.URL_PARAMETER, alreadyTrackedFlag: 'parsedParams' },
|
|
52
|
+
{ key: 'body', inputType: bodyType, alreadyTrackedFlag: 'parsedBody' }
|
|
53
|
+
].forEach(({ key, inputType, alreadyTrackedFlag }) => {
|
|
54
|
+
if (sourceContext[alreadyTrackedFlag]) {
|
|
55
|
+
logger.trace({ inputType, name }, 'values already tracked');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
67
59
|
try {
|
|
68
60
|
sources.handle({
|
|
61
|
+
context: `req.${key}`,
|
|
69
62
|
data: request[key],
|
|
70
63
|
inputType,
|
|
71
64
|
name,
|
|
72
65
|
stacktraceOpts: {
|
|
73
66
|
constructorOpt: preValidationHandler,
|
|
74
|
-
}
|
|
67
|
+
},
|
|
68
|
+
sourceContext
|
|
75
69
|
});
|
|
76
70
|
} catch (err) {
|
|
77
|
-
logger.error({ err, inputType, name }, 'unable to handle
|
|
71
|
+
logger.error({ err, inputType, name }, 'unable to handle Fastify source');
|
|
78
72
|
}
|
|
79
73
|
});
|
|
80
74
|
|
|
@@ -18,14 +18,13 @@
|
|
|
18
18
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
module.exports = function(core) {
|
|
21
|
-
const
|
|
21
|
+
const fastifySources = core.assess.dataflow.sources.fastifyInstrumentation = {};
|
|
22
22
|
|
|
23
23
|
require('./fastify')(core);
|
|
24
|
-
require('./cookie')(core);
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
callChildComponentMethodsSync(
|
|
25
|
+
fastifySources.install = function install() {
|
|
26
|
+
callChildComponentMethodsSync(fastifySources, 'install');
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
return
|
|
29
|
+
return fastifySources;
|
|
31
30
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
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 = (core) => {
|
|
22
|
+
const {
|
|
23
|
+
depHooks,
|
|
24
|
+
patcher,
|
|
25
|
+
logger,
|
|
26
|
+
assess: { dataflow: { sources } },
|
|
27
|
+
} = core;
|
|
28
|
+
const name = 'Formidable.IncomingForm.prototype.parse';
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// Patch `formidable`
|
|
32
|
+
function install() {
|
|
33
|
+
depHooks.resolve({ name: 'formidable' }, (formidable) => {
|
|
34
|
+
formidable.IncomingForm.prototype.parse = patcher.patch(formidable.IncomingForm.prototype.parse, {
|
|
35
|
+
name,
|
|
36
|
+
patchType,
|
|
37
|
+
pre(data) {
|
|
38
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
39
|
+
const inputType = InputType.MULTIPART_VALUE;
|
|
40
|
+
|
|
41
|
+
if (!sourceContext) {
|
|
42
|
+
logger.error({ inputType, name }, 'unable to handle source. Missing `sourceContext`');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (sourceContext.parsedBody) {
|
|
47
|
+
logger.trace({ inputType, name }, 'values already tracked');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const origCb = data.args[1];
|
|
52
|
+
|
|
53
|
+
data.args[1] = function hookedCb(...cbArgs) {
|
|
54
|
+
const [, fields] = cbArgs;
|
|
55
|
+
|
|
56
|
+
if (fields) {
|
|
57
|
+
try {
|
|
58
|
+
sources.handle({
|
|
59
|
+
context: 'req.body',
|
|
60
|
+
name,
|
|
61
|
+
inputType,
|
|
62
|
+
stacktraceOpts: {
|
|
63
|
+
constructorOpt: data.hooked,
|
|
64
|
+
prependFrames: [data.orig]
|
|
65
|
+
},
|
|
66
|
+
data: fields,
|
|
67
|
+
sourceContext
|
|
68
|
+
});
|
|
69
|
+
sourceContext.parsedBody = true;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error({ err, inputType, name }, 'unable to handle source');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (origCb && typeof origCb === 'function') {
|
|
76
|
+
origCb.apply(this, cbArgs);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return formidable;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const formidable1Instrumentation = sources.formidable1Instrumentation = {
|
|
87
|
+
install
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return formidable1Instrumentation;
|
|
91
|
+
};
|