@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,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
|
+
};
|
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
const { patchType } = require('../common');
|
|
18
|
-
const { toLowerCase } = require('@contrast/common');
|
|
18
|
+
const { toLowerCase, InputType } = require('@contrast/common');
|
|
19
19
|
|
|
20
|
-
// This is only just initiating an async storage for the http source
|
|
21
|
-
// TODO Tracking the user input
|
|
22
20
|
module.exports = function(core) {
|
|
23
21
|
const {
|
|
24
22
|
scopes: { sources },
|
|
25
23
|
instrumentation: { instrument },
|
|
24
|
+
assess: { dataflow: { sources: dataflowSources } },
|
|
26
25
|
patcher,
|
|
27
26
|
} = core;
|
|
28
27
|
|
|
@@ -40,7 +39,7 @@ module.exports = function(core) {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
try {
|
|
43
|
-
const [, req,
|
|
42
|
+
const [, req, res] = data.args;
|
|
44
43
|
const store = sources.getStore();
|
|
45
44
|
|
|
46
45
|
if (!store) {
|
|
@@ -48,7 +47,7 @@ module.exports = function(core) {
|
|
|
48
47
|
return next();
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
patcher.patch(
|
|
50
|
+
patcher.patch(res, 'writeHead', {
|
|
52
51
|
name: 'write-head',
|
|
53
52
|
patchType,
|
|
54
53
|
pre(data) {
|
|
@@ -74,7 +73,7 @@ module.exports = function(core) {
|
|
|
74
73
|
}
|
|
75
74
|
});
|
|
76
75
|
|
|
77
|
-
patcher.patch(
|
|
76
|
+
patcher.patch(res, 'setHeader', {
|
|
78
77
|
alwaysRun: true,
|
|
79
78
|
name: 'set-header',
|
|
80
79
|
patchType,
|
|
@@ -98,14 +97,42 @@ module.exports = function(core) {
|
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
const headers = {};
|
|
100
|
+
const sourceInputType = InputType.HEADER;
|
|
101
|
+
const sourceName = 'ClientRequest';
|
|
102
|
+
|
|
103
|
+
store.assess = {
|
|
104
|
+
responseData: {},
|
|
105
|
+
sourceEventsCount: 0,
|
|
106
|
+
propagationEventsCount: 0,
|
|
107
|
+
findings: {},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
dataflowSources.handle({
|
|
112
|
+
context: 'req.headers',
|
|
113
|
+
data: req.headers,
|
|
114
|
+
inputType: sourceInputType,
|
|
115
|
+
name: sourceName,
|
|
116
|
+
stacktraceOpts: {
|
|
117
|
+
constructorOpt: data.hooked,
|
|
118
|
+
prependFrames: [data.orig]
|
|
119
|
+
},
|
|
120
|
+
sourceContext: store.assess
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
} catch (err) {
|
|
124
|
+
logger.error({ err, inputType: sourceInputType, name: sourceName }, 'unable to handle http source');
|
|
125
|
+
}
|
|
101
126
|
|
|
102
127
|
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
103
128
|
const header = toLowerCase(req.rawHeaders[i]);
|
|
104
129
|
headers[header] = req.rawHeaders[i + 1];
|
|
130
|
+
req.rawHeaders[i + 1] = req.headers[header];
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
const contentType = headers['content-type'] && toLowerCase(headers['content-type']);
|
|
108
|
-
|
|
134
|
+
|
|
135
|
+
store.assess.reqData = {
|
|
109
136
|
ip: req.socket.remoteAddress,
|
|
110
137
|
httpVersion: req.httpVersion,
|
|
111
138
|
method: req.method,
|
|
@@ -115,13 +142,6 @@ module.exports = function(core) {
|
|
|
115
142
|
contentType,
|
|
116
143
|
};
|
|
117
144
|
|
|
118
|
-
store.assess = {
|
|
119
|
-
reqData,
|
|
120
|
-
responseData: {},
|
|
121
|
-
sourceEventsCount: 0,
|
|
122
|
-
propagationEventsCount: 0,
|
|
123
|
-
findings: {},
|
|
124
|
-
};
|
|
125
145
|
} catch (err) {
|
|
126
146
|
logger.error({ err }, 'Error during assess request handling');
|
|
127
147
|
}
|
|
@@ -132,47 +152,20 @@ module.exports = function(core) {
|
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
function install() {
|
|
135
|
-
[{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
moduleName: 'https'
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
moduleName: 'spdy'
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
moduleName: 'http2',
|
|
146
|
-
patchObjectsProps: [
|
|
147
|
-
{
|
|
148
|
-
methods: ['createServer', 'createSecureServer'],
|
|
149
|
-
patchType,
|
|
150
|
-
patchObjects: [
|
|
151
|
-
{
|
|
152
|
-
name: 'Server.prototype',
|
|
153
|
-
methods: ['emit'],
|
|
154
|
-
patchType,
|
|
155
|
-
around
|
|
156
|
-
}
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
]
|
|
160
|
-
}].forEach(({ moduleName, patchObjectsProps }) => {
|
|
161
|
-
const patchObjects = patchObjectsProps || [
|
|
162
|
-
{
|
|
155
|
+
['http', 'https', 'spdy', 'http2'].forEach((moduleName) => {
|
|
156
|
+
instrument({
|
|
157
|
+
moduleName,
|
|
158
|
+
patchObjects: [{
|
|
163
159
|
name: 'Server.prototype',
|
|
164
160
|
methods: ['emit'],
|
|
165
161
|
patchType,
|
|
166
162
|
around
|
|
167
|
-
}
|
|
168
|
-
];
|
|
169
|
-
instrument({
|
|
170
|
-
moduleName,
|
|
171
|
-
patchObjects
|
|
163
|
+
}]
|
|
172
164
|
});
|
|
173
165
|
});
|
|
174
166
|
}
|
|
175
167
|
|
|
168
|
+
/* c8 ignore next 3 */
|
|
176
169
|
function uninstall() {
|
|
177
170
|
return null;
|
|
178
171
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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 koaSources = core.assess.dataflow.sources.koaInstrumentation = {};
|
|
22
|
+
|
|
23
|
+
require('./koa2')(core);
|
|
24
|
+
require('./koa-bodyparsers')(core);
|
|
25
|
+
require('./koa-routers')(core);
|
|
26
|
+
|
|
27
|
+
koaSources.install = function install() {
|
|
28
|
+
callChildComponentMethodsSync(koaSources, 'install');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return koaSources;
|
|
32
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
// Patch `koa-body` v4.x.x and `koa-bodyparser` v5.x.x packages
|
|
30
|
+
function install() {
|
|
31
|
+
['koa-body', 'koa-bodyparser'].forEach(function (packageName) {
|
|
32
|
+
depHooks.resolve({ name: packageName }, (koaBody) => patcher.patch(koaBody, {
|
|
33
|
+
name: packageName,
|
|
34
|
+
patchType,
|
|
35
|
+
post(data) {
|
|
36
|
+
data.result = patcher.patch(data.result, {
|
|
37
|
+
name: packageName,
|
|
38
|
+
patchType,
|
|
39
|
+
pre(data) {
|
|
40
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
41
|
+
const [ctx, origNext] = data.args;
|
|
42
|
+
|
|
43
|
+
if (!sourceContext) {
|
|
44
|
+
logger.error({ name: packageName }, 'unable to handle source. Missing `sourceContext`');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sourceContext.parsedBody) {
|
|
49
|
+
logger.trace({ name: packageName }, 'values already tracked');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
data.args[1] = async function contrastNext(origErr) {
|
|
55
|
+
const inputType = sourceContext.reqData.headers?.['content-type']?.includes('/json')
|
|
56
|
+
? InputType.JSON_VALUE
|
|
57
|
+
: typeof ctx.request.body == 'object'
|
|
58
|
+
? InputType.PARAMETER_VALUE
|
|
59
|
+
: InputType.BODY;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
sources.handle({
|
|
63
|
+
context: 'ctx.request.body',
|
|
64
|
+
name: packageName,
|
|
65
|
+
inputType,
|
|
66
|
+
stacktraceOpts: {
|
|
67
|
+
constructorOpt: contrastNext,
|
|
68
|
+
},
|
|
69
|
+
data: ctx.request.body,
|
|
70
|
+
sourceContext
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
sourceContext.parsedBody = !!Object.keys(ctx.request.body).length;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.error({ err, inputType, name: packageName }, 'unable to handle Koa source');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await origNext(origErr);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const koaBodyparsersInstrumentation = sources.koaInstrumentation.koaBodyparsers = {
|
|
88
|
+
install
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return koaBodyparsersInstrumentation;
|
|
92
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
// Patch `koa-router` and `@koa/router` to handle parsed params
|
|
30
|
+
function install() {
|
|
31
|
+
['koa-router', '@koa/router'].forEach(router => {
|
|
32
|
+
depHooks.resolve(
|
|
33
|
+
{ name: router, file: 'lib/layer.js' },
|
|
34
|
+
(layer) => {
|
|
35
|
+
const name = `[${router}].layer.prototype`;
|
|
36
|
+
|
|
37
|
+
layer.prototype = patcher.patch(layer.prototype, 'params', {
|
|
38
|
+
name,
|
|
39
|
+
patchType,
|
|
40
|
+
post({ orig, hooked, result }) {
|
|
41
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
42
|
+
const inputType = InputType.URL_PARAMETER;
|
|
43
|
+
|
|
44
|
+
if (!sourceContext) {
|
|
45
|
+
logger.error({ name, inputType }, 'unable to handle source. Missing `sourceContext`');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (sourceContext.parsedParams) {
|
|
50
|
+
logger.trace({ name, inputType }, 'values already tracked');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
sources.handle({
|
|
56
|
+
context: 'ctx.params',
|
|
57
|
+
data: result,
|
|
58
|
+
inputType,
|
|
59
|
+
name,
|
|
60
|
+
stacktraceOpts: {
|
|
61
|
+
constructorOpt: hooked,
|
|
62
|
+
prependFrames: [orig]
|
|
63
|
+
},
|
|
64
|
+
sourceContext
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
sourceContext.parsedParams = Boolean(Object.keys(result).length);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
logger.error({ err, inputType, name }, 'unable to handle Koa source');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return layer;
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const koaRoutersInstrumentation = sources.koaInstrumentation.koaRouters = {
|
|
80
|
+
install
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return koaRoutersInstrumentation;
|
|
84
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
/**
|
|
22
|
+
* Function that exports an install method to patch Koa framework with our instrumentation
|
|
23
|
+
* @param {Object} core - the core Contrast object in v5
|
|
24
|
+
* @return {Object} object with install method and the other relative functions exported for testing purposes
|
|
25
|
+
*/
|
|
26
|
+
module.exports = (core) => {
|
|
27
|
+
const {
|
|
28
|
+
logger,
|
|
29
|
+
depHooks,
|
|
30
|
+
patcher,
|
|
31
|
+
assess: { dataflow: { sources } },
|
|
32
|
+
} = core;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* registers a depHook for koa module instrumentation
|
|
36
|
+
*/
|
|
37
|
+
function install() {
|
|
38
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
39
|
+
function contrastStartMiddleware(ctx, next) {
|
|
40
|
+
const name = 'Koa.Application';
|
|
41
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
42
|
+
const inputType = InputType.QUERYSTRING;
|
|
43
|
+
|
|
44
|
+
if (!sourceContext) {
|
|
45
|
+
logger.error({ name, inputType }, 'unable to handle Koa source. Missing `sourceContext`');
|
|
46
|
+
return next();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// We check the contents mainly to trigger the getter for `ctx.query`
|
|
50
|
+
// that is eventually set up by `koa-qs`
|
|
51
|
+
if (ctx.query) {
|
|
52
|
+
if (sourceContext.parsedQuery) {
|
|
53
|
+
logger.trace({ name, inputType }, 'values already tracked');
|
|
54
|
+
return next();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
sources.handle({
|
|
59
|
+
context: 'ctx.query',
|
|
60
|
+
data: ctx.query,
|
|
61
|
+
inputType,
|
|
62
|
+
name,
|
|
63
|
+
stacktraceOpts: {
|
|
64
|
+
constructorOpt: contrastStartMiddleware,
|
|
65
|
+
},
|
|
66
|
+
sourceContext
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
sourceContext.parsedQuery = true;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error({ err, inputType, name }, 'unable to handle Koa source');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return next();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// mark these middleware as ours
|
|
79
|
+
contrastStartMiddleware._isContrastStartMiddleware = true;
|
|
80
|
+
|
|
81
|
+
patcher.patch(Koa.prototype, 'use', {
|
|
82
|
+
name: 'Koa.Application',
|
|
83
|
+
patchType,
|
|
84
|
+
pre({ obj: app }) {
|
|
85
|
+
// if not already inserted, insert the initial middleware.
|
|
86
|
+
if (
|
|
87
|
+
app.middleware &&
|
|
88
|
+
(!app.middleware[0] || !app.middleware[0]._isContrastStartMiddleware)
|
|
89
|
+
) {
|
|
90
|
+
app.middleware.splice(0, 0, contrastStartMiddleware);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const koa2Instrumentation = sources.koaInstrumentation.koa2 = {
|
|
99
|
+
install
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return koa2Instrumentation;
|
|
103
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
// Patch `qs`
|
|
30
|
+
function install() {
|
|
31
|
+
const name = 'qs.parse';
|
|
32
|
+
depHooks.resolve({ name: 'qs' },
|
|
33
|
+
(qs) => patcher.patch(qs, 'parse', {
|
|
34
|
+
name,
|
|
35
|
+
patchType,
|
|
36
|
+
post({ args, hooked, orig, result }) {
|
|
37
|
+
const sourceContext = core.scopes.sources.getStore()?.assess;
|
|
38
|
+
const inputType = InputType.QUERYSTRING;
|
|
39
|
+
|
|
40
|
+
if (!sourceContext) {
|
|
41
|
+
logger.error({ inputType, name }, 'unable to handle source. Missing `sourceContext`');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (sourceContext.parsedQuery) {
|
|
46
|
+
logger.trace({ inputType, name }, 'values already tracked');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
51
|
+
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
52
|
+
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
53
|
+
// some cases its use is optional and we cannot rely on it.
|
|
54
|
+
if (sourceContext.reqData?.queries === args[0]) {
|
|
55
|
+
try {
|
|
56
|
+
sources.handle({
|
|
57
|
+
context: 'req.query',
|
|
58
|
+
name,
|
|
59
|
+
inputType,
|
|
60
|
+
stacktraceOpts: {
|
|
61
|
+
constructorOpt: hooked,
|
|
62
|
+
prependFrames: [orig]
|
|
63
|
+
},
|
|
64
|
+
data: result,
|
|
65
|
+
sourceContext
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
sourceContext.parsedQuery = true;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.error({ err, inputType, name }, 'unable to handle source');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const qs6Instrumentation = sources.qs6Instrumentation = {
|
|
79
|
+
install
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return qs6Instrumentation;
|
|
83
|
+
};
|
|
84
|
+
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
17
|
function mergeOverlapRange(overlapTags) {
|
|
18
|
-
if (overlapTags.length <= 1) return overlapTags;
|
|
18
|
+
if (overlapTags.length <= 1 && overlapTags[0].length === 2) return overlapTags;
|
|
19
19
|
|
|
20
20
|
const tagsInTuple = [];
|
|
21
21
|
for (const overTagsCurr of overlapTags) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@contrast/distringuish": "^4.1.0",
|
|
17
17
|
"@contrast/scopes": "1.3.0",
|
|
18
|
-
"@contrast/common": "1.
|
|
18
|
+
"@contrast/common": "1.8.0",
|
|
19
19
|
"parseurl": "^1.3.3"
|
|
20
20
|
}
|
|
21
21
|
}
|