@contrast/assess 1.3.0 → 1.5.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 +31 -78
- 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 +3 -3
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -3
- package/lib/dataflow/propagation/install/encode-uri-component.js +3 -3
- package/lib/dataflow/propagation/install/escape-html.js +3 -3
- package/lib/dataflow/propagation/install/escape.js +3 -3
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +3 -3
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +3 -3
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -3
- package/lib/dataflow/propagation/install/querystring/parse.js +3 -3
- 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 +5 -3
- package/lib/dataflow/propagation/install/string/match.js +122 -0
- package/lib/dataflow/propagation/install/string/replace.js +4 -4
- 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 +3 -3
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -3
- package/lib/dataflow/propagation/install/validator/hooks.js +2 -2
- package/lib/dataflow/sinks/index.js +11 -2
- package/lib/dataflow/sinks/install/child-process.js +87 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +29 -16
- package/lib/dataflow/sinks/install/http.js +21 -19
- package/lib/dataflow/sinks/install/koa/index.js +30 -0
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +113 -0
- package/lib/dataflow/sinks/install/mssql.js +8 -6
- package/lib/dataflow/sinks/install/postgres.js +27 -17
- package/lib/dataflow/sinks/install/sqlite3.js +94 -0
- package/lib/dataflow/sources/handler.js +9 -6
- 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 +40 -47
- 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 -1996
- package/lib/dataflow/signatures/mssql.js +0 -49
- package/lib/dataflow/sources/install/fastify/cookie.js +0 -61
|
@@ -0,0 +1,30 @@
|
|
|
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(core) {
|
|
21
|
+
const koa = core.assess.dataflow.sinks.koa = {};
|
|
22
|
+
|
|
23
|
+
require('./unvalidated-redirect')(core);
|
|
24
|
+
|
|
25
|
+
koa.install = function() {
|
|
26
|
+
callChildComponentMethodsSync(koa, 'install');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return koa;
|
|
30
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
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 { isString } = require('@contrast/common');
|
|
20
|
+
const { patchType } = require('../../common');
|
|
21
|
+
const { createSubsetTags } = require('../../../tag-utils');
|
|
22
|
+
|
|
23
|
+
module.exports = function (core) {
|
|
24
|
+
const {
|
|
25
|
+
depHooks,
|
|
26
|
+
patcher,
|
|
27
|
+
scopes: { sources },
|
|
28
|
+
assess: {
|
|
29
|
+
dataflow: {
|
|
30
|
+
tracker,
|
|
31
|
+
sinks: { isVulnerable, reportFindings },
|
|
32
|
+
eventFactory: { createSinkEvent },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} = core;
|
|
36
|
+
const unvalidatedRedirect =
|
|
37
|
+
(core.assess.dataflow.sinks.koa.unvalidatedRedirect = {});
|
|
38
|
+
|
|
39
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
40
|
+
|
|
41
|
+
const safeTags = [
|
|
42
|
+
'limited-chars',
|
|
43
|
+
'url-encoded',
|
|
44
|
+
'html-encoded',
|
|
45
|
+
'custom-validated',
|
|
46
|
+
'custom-encoded'
|
|
47
|
+
];
|
|
48
|
+
const requiredTag = 'untrusted';
|
|
49
|
+
|
|
50
|
+
unvalidatedRedirect.install = function () {
|
|
51
|
+
depHooks.resolve({ name: 'koa', file: 'lib/response', version: '<2.9.0' }, (Response) => {
|
|
52
|
+
patcher.patch(Response, 'redirect', {
|
|
53
|
+
name: 'Koa.Response.redirect',
|
|
54
|
+
patchType,
|
|
55
|
+
pre(data) {
|
|
56
|
+
const assessStore = sources.getStore()?.assess;
|
|
57
|
+
if (!assessStore) return;
|
|
58
|
+
|
|
59
|
+
let [url] = data.args;
|
|
60
|
+
|
|
61
|
+
if (url === 'back') {
|
|
62
|
+
url = data.obj.ctx.get('Referrer') || data.args[1];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!url || !isString(url)) return;
|
|
66
|
+
|
|
67
|
+
const strInfo = tracker.getData(url);
|
|
68
|
+
if (!strInfo) return;
|
|
69
|
+
|
|
70
|
+
let urlPathTags = strInfo.tags;
|
|
71
|
+
|
|
72
|
+
if (url.indexOf('?') > -1) {
|
|
73
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?'));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (urlPathTags && isVulnerable(requiredTag, safeTags, urlPathTags)) {
|
|
77
|
+
const event = createSinkEvent({
|
|
78
|
+
args: [{
|
|
79
|
+
tracked: true,
|
|
80
|
+
value: strInfo.value,
|
|
81
|
+
}],
|
|
82
|
+
context: `response.redirect(${inspect(strInfo.value)})`,
|
|
83
|
+
history: [strInfo],
|
|
84
|
+
name: 'Koa.Response.redirect',
|
|
85
|
+
object: {
|
|
86
|
+
tracked: false,
|
|
87
|
+
value: 'Koa.Response',
|
|
88
|
+
},
|
|
89
|
+
result: {
|
|
90
|
+
tracked: false,
|
|
91
|
+
value: undefined,
|
|
92
|
+
},
|
|
93
|
+
tags: urlPathTags,
|
|
94
|
+
source: 'P0',
|
|
95
|
+
stacktraceOpts: {
|
|
96
|
+
constructorOpt: data.hooked,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (event) {
|
|
101
|
+
reportFindings({
|
|
102
|
+
ruleId: 'unvalidated-redirect',
|
|
103
|
+
sinkEvent: event,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return unvalidatedRedirect;
|
|
113
|
+
};
|
|
@@ -54,12 +54,12 @@ module.exports = function (core) {
|
|
|
54
54
|
history: [strInfo],
|
|
55
55
|
object: {
|
|
56
56
|
value: `[${createModuleLabel('mssql', version)}].${obj}`,
|
|
57
|
-
|
|
57
|
+
tracked: false,
|
|
58
58
|
},
|
|
59
59
|
args: [
|
|
60
60
|
{
|
|
61
61
|
value: strInfo.value,
|
|
62
|
-
|
|
62
|
+
tracked: true,
|
|
63
63
|
},
|
|
64
64
|
],
|
|
65
65
|
tags: strInfo.tags,
|
|
@@ -69,10 +69,12 @@ module.exports = function (core) {
|
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
if (event) {
|
|
73
|
+
reportFindings({
|
|
74
|
+
ruleId: Rule.SQL_INJECTION,
|
|
75
|
+
sinkEvent: event,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
core.assess.dataflow.sinks.mssql = {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const util = require('util');
|
|
18
19
|
const { Rule, isString } = require('@contrast/common');
|
|
19
|
-
const { createModuleLabel } = require('../../propagation/common');
|
|
20
20
|
const { patchType } = require('../common');
|
|
21
21
|
|
|
22
22
|
module.exports = function (core) {
|
|
@@ -33,8 +33,6 @@ module.exports = function (core) {
|
|
|
33
33
|
},
|
|
34
34
|
} = core;
|
|
35
35
|
|
|
36
|
-
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
37
|
-
|
|
38
36
|
const safeTags = [
|
|
39
37
|
'sql-encoded',
|
|
40
38
|
'limited-chars',
|
|
@@ -42,31 +40,41 @@ module.exports = function (core) {
|
|
|
42
40
|
'custom-encoded',
|
|
43
41
|
];
|
|
44
42
|
const requiredTag = 'untrusted';
|
|
43
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
44
|
+
|
|
45
|
+
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
45
46
|
|
|
46
47
|
const preHook = (methodSignature, version, mod, obj) => (data) => {
|
|
47
48
|
const assessStore = sources.getStore()?.assess;
|
|
48
49
|
if (!assessStore) return;
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
+
const [arg0] = data.args;
|
|
52
|
+
const query = arg0?.text || arg0;
|
|
51
53
|
if (!query || !isString(query)) return;
|
|
52
54
|
|
|
53
55
|
const strInfo = tracker.getData(query);
|
|
54
56
|
if (!strInfo) return;
|
|
55
57
|
|
|
58
|
+
const objValue = methodSignature.includes('native') ? 'pg.native.Client' : 'pg.Client';
|
|
59
|
+
const arg0Val = inspect(arg0);
|
|
60
|
+
|
|
56
61
|
if (isVulnerable(requiredTag, safeTags, strInfo.tags)) {
|
|
57
62
|
const event = createSinkEvent({
|
|
58
|
-
|
|
63
|
+
args: [{
|
|
64
|
+
tracked: true,
|
|
65
|
+
value: strInfo.value,
|
|
66
|
+
}],
|
|
67
|
+
context: `${objValue}.query(${arg0Val})`,
|
|
59
68
|
history: [strInfo],
|
|
69
|
+
name: methodSignature,
|
|
60
70
|
object: {
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
tracked: false,
|
|
72
|
+
value: objValue,
|
|
73
|
+
},
|
|
74
|
+
result: {
|
|
75
|
+
tracked: false,
|
|
76
|
+
value: 'Promise',
|
|
63
77
|
},
|
|
64
|
-
args: [
|
|
65
|
-
{
|
|
66
|
-
value: strInfo.value,
|
|
67
|
-
isTracked: true,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
78
|
tags: strInfo.tags,
|
|
71
79
|
source: 'P0',
|
|
72
80
|
stacktraceOpts: {
|
|
@@ -74,10 +82,12 @@ module.exports = function (core) {
|
|
|
74
82
|
},
|
|
75
83
|
});
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
if (event) {
|
|
86
|
+
reportFindings({
|
|
87
|
+
ruleId: Rule.SQL_INJECTION,
|
|
88
|
+
sinkEvent: event,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
81
91
|
}
|
|
82
92
|
};
|
|
83
93
|
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
const { patchType } = require('../common');
|
|
18
|
+
const { Rule, isString } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
const SAFE_TAGS = [
|
|
21
|
+
'sql-encoded',
|
|
22
|
+
'limited-chars',
|
|
23
|
+
'custom-validated',
|
|
24
|
+
'custom-encoded',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
module.exports = function (core) {
|
|
28
|
+
const {
|
|
29
|
+
depHooks,
|
|
30
|
+
patcher,
|
|
31
|
+
scopes: { sources },
|
|
32
|
+
assess: {
|
|
33
|
+
dataflow: {
|
|
34
|
+
tracker,
|
|
35
|
+
sinks: { isVulnerable, reportFindings },
|
|
36
|
+
eventFactory: { createSinkEvent },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} = core;
|
|
40
|
+
|
|
41
|
+
const pre = (name) => (data) => {
|
|
42
|
+
const store = sources.getStore()?.assess;
|
|
43
|
+
if (!store || !data.args[0] || !isString(data.args[0])) return;
|
|
44
|
+
|
|
45
|
+
const strInfo = tracker.getData(data.args[0]);
|
|
46
|
+
if (!strInfo || !isVulnerable('untrusted', SAFE_TAGS, strInfo.tags)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const event = createSinkEvent({
|
|
51
|
+
name,
|
|
52
|
+
history: [strInfo],
|
|
53
|
+
object: {
|
|
54
|
+
value: '[Module<sqlite3>].Database',
|
|
55
|
+
tracked: false,
|
|
56
|
+
},
|
|
57
|
+
args: [
|
|
58
|
+
{
|
|
59
|
+
value: strInfo.value,
|
|
60
|
+
tracked: true,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
tags: strInfo.tags,
|
|
64
|
+
source: 'P0',
|
|
65
|
+
stacktraceOpts: {
|
|
66
|
+
contructorOpt: data.hooked,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (event) {
|
|
71
|
+
reportFindings({
|
|
72
|
+
ruleId: Rule.SQL_INJECTION,
|
|
73
|
+
sinkEvent: event,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
core.assess.dataflow.sinks.sqlite3 = {
|
|
79
|
+
install() {
|
|
80
|
+
depHooks.resolve({ name: 'sqlite3' }, sqlite3 => {
|
|
81
|
+
['all', 'run', 'get', 'each', 'exec', 'prepare'].forEach((method) => {
|
|
82
|
+
const name = `sqlite3.Database.prototype.${method}`;
|
|
83
|
+
patcher.patch(sqlite3.Database.prototype, method, {
|
|
84
|
+
name,
|
|
85
|
+
patchType,
|
|
86
|
+
pre: pre(name)
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return core.assess.dataflow.sinks.sqlite3;
|
|
94
|
+
};
|
|
@@ -55,25 +55,23 @@ module.exports = function(core) {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
sources.handle = function({
|
|
58
|
+
context,
|
|
58
59
|
name,
|
|
59
60
|
inputType = 'unknown',
|
|
60
61
|
stacktraceOpts,
|
|
61
62
|
data,
|
|
63
|
+
sourceContext
|
|
62
64
|
}) {
|
|
63
65
|
if (!data) return;
|
|
64
66
|
|
|
65
67
|
const max = config.assess.max_context_source_events;
|
|
66
|
-
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
67
68
|
|
|
68
69
|
if (!sourceContext) {
|
|
69
70
|
core.logger.trace({ inputType, name }, 'skipping assess source handling - no request context');
|
|
70
71
|
return null;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
const stack = config.assess.stacktraces === 'NONE'
|
|
75
|
-
? emptyStack
|
|
76
|
-
: createSnapshot(stacktraceOpts)();
|
|
74
|
+
let stack;
|
|
77
75
|
|
|
78
76
|
traverseValues(data, (path, type, value, obj) => {
|
|
79
77
|
if (sourceContext.sourceEventsCount >= max) {
|
|
@@ -82,10 +80,15 @@ module.exports = function(core) {
|
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
if (isString(value) && value.length) {
|
|
83
|
+
stack = stack || config.assess.stacktraces === 'NONE'
|
|
84
|
+
? emptyStack
|
|
85
|
+
: createSnapshot(stacktraceOpts)();
|
|
85
86
|
const key = path[path.length - 1];
|
|
86
87
|
const pathName = path.join('.');
|
|
87
88
|
const event = createSourceEvent({
|
|
89
|
+
context: `${context}.${pathName}`,
|
|
88
90
|
name,
|
|
91
|
+
fieldName: key,
|
|
89
92
|
pathName,
|
|
90
93
|
stack,
|
|
91
94
|
inputType,
|
|
@@ -100,7 +103,7 @@ module.exports = function(core) {
|
|
|
100
103
|
|
|
101
104
|
const { extern } = tracker.track(value, event);
|
|
102
105
|
if (extern) {
|
|
103
|
-
logger.trace({ extern, key, inputType }, 'tracked');
|
|
106
|
+
logger.trace({ extern, key, name, inputType }, 'tracked');
|
|
104
107
|
obj[key] = extern;
|
|
105
108
|
sourceContext.sourceEventsCount++;
|
|
106
109
|
}
|
|
@@ -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
|
};
|