@contrast/assess 1.1.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/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +266 -0
- package/coverage/lcov-report/lib/dataflow/common.js.html +154 -0
- package/coverage/lcov-report/lib/dataflow/event-factory.js.html +598 -0
- package/coverage/lcov-report/lib/dataflow/index.html +191 -0
- package/coverage/lcov-report/lib/dataflow/index.js.html +190 -0
- package/coverage/lcov-report/lib/dataflow/propagation/common.js.html +145 -0
- package/coverage/lcov-report/lib/dataflow/propagation/index.html +131 -0
- package/coverage/lcov-report/lib/dataflow/propagation/index.js.html +190 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.html +131 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.js.html +184 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/plus.js.html +397 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/concat.js.html +478 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.html +176 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.js.html +202 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/replace.js.html +496 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/substring.js.html +415 -0
- package/coverage/lcov-report/lib/dataflow/propagation/install/string/trim.js.html +403 -0
- package/coverage/lcov-report/lib/dataflow/signatures.js.html +5923 -0
- package/coverage/lcov-report/lib/dataflow/sinks/common.js.html +145 -0
- package/coverage/lcov-report/lib/dataflow/sinks/index.html +131 -0
- package/coverage/lcov-report/lib/dataflow/sinks/index.js.html +211 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.html +146 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.js.html +172 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js.html +319 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/xss.js.html +721 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/http.js.html +340 -0
- package/coverage/lcov-report/lib/dataflow/sinks/install/index.html +116 -0
- package/coverage/lcov-report/lib/dataflow/sources/common.js.html +145 -0
- package/coverage/lcov-report/lib/dataflow/sources/index.html +131 -0
- package/coverage/lcov-report/lib/dataflow/sources/index.js.html +226 -0
- package/coverage/lcov-report/lib/dataflow/sources/install/fastify.js.html +379 -0
- package/coverage/lcov-report/lib/dataflow/sources/install/http.js.html +502 -0
- package/coverage/lcov-report/lib/dataflow/sources/install/index.html +146 -0
- package/coverage/lcov-report/lib/dataflow/sources/install/qs.js.html +322 -0
- package/coverage/lcov-report/lib/dataflow/tag-utils.js.html +418 -0
- package/coverage/lcov-report/lib/dataflow/tracker.js.html +466 -0
- package/coverage/lcov-report/lib/dataflow/utils/index.html +116 -0
- package/coverage/lcov-report/lib/dataflow/utils/is-vulnerable.js.html +424 -0
- package/coverage/lcov-report/lib/index.html +116 -0
- package/coverage/lcov-report/lib/index.js.html +193 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +4856 -0
- package/coverage/tmp/coverage-9548-1675168551025-0.json +1 -0
- package/coverage/tmp/coverage-9551-1675168550963-0.json +1 -0
- package/coverage/tmp/coverage-9552-1675168550969-0.json +1 -0
- package/coverage/tmp/coverage-9553-1675168550970-0.json +1 -0
- package/coverage/tmp/coverage-9554-1675168550962-0.json +1 -0
- package/coverage/tmp/coverage-9555-1675168550965-0.json +1 -0
- package/coverage/tmp/coverage-9556-1675168550964-0.json +1 -0
- package/coverage/tmp/coverage-9557-1675168550969-0.json +1 -0
- package/coverage/tmp/coverage-9558-1675168550964-0.json +1 -0
- package/coverage/tmp/coverage-9559-1675168550971-0.json +1 -0
- package/lib/dataflow/event-factory.js +256 -0
- package/lib/dataflow/index.js +35 -0
- package/lib/dataflow/propagation/common.js +26 -0
- package/lib/dataflow/propagation/index.js +50 -0
- package/lib/dataflow/propagation/install/array-prototype-join.js +125 -0
- package/lib/dataflow/propagation/install/contrast-methods/add.js +104 -0
- package/lib/dataflow/propagation/install/contrast-methods/index.js +34 -0
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +102 -0
- package/lib/dataflow/propagation/install/decode-uri-component.js +88 -0
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +89 -0
- package/lib/dataflow/propagation/install/ejs/index.js +30 -0
- package/lib/dataflow/propagation/install/encode-uri-component.js +87 -0
- package/lib/dataflow/propagation/install/escape-html.js +89 -0
- package/lib/dataflow/propagation/install/escape.js +89 -0
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +89 -0
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +111 -0
- package/lib/dataflow/propagation/install/pug/index.js +64 -0
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +89 -0
- package/lib/dataflow/propagation/install/sql-template-strings.js +103 -0
- package/lib/dataflow/propagation/install/string/concat.js +108 -0
- package/lib/dataflow/propagation/install/string/format-methods.js +83 -0
- package/lib/dataflow/propagation/install/string/html-methods.js +163 -0
- package/lib/dataflow/propagation/install/string/index.js +38 -0
- package/lib/dataflow/propagation/install/string/replace.js +197 -0
- package/lib/dataflow/propagation/install/string/substring.js +117 -0
- package/lib/dataflow/propagation/install/string/trim.js +115 -0
- package/lib/dataflow/propagation/install/unescape.js +90 -0
- package/lib/dataflow/propagation/install/url/domain-parsers.js +89 -0
- package/lib/dataflow/propagation/install/url/index.js +33 -0
- package/lib/dataflow/propagation/install/validator/hooks.js +172 -0
- package/lib/dataflow/propagation/install/validator/index.js +28 -0
- package/lib/dataflow/propagation/install/validator/methods.js +82 -0
- package/lib/dataflow/signatures.js +2022 -0
- package/lib/dataflow/sinks/common.js +20 -0
- package/lib/dataflow/sinks/index.js +44 -0
- package/lib/dataflow/sinks/install/fastify/index.js +30 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +107 -0
- package/lib/dataflow/sinks/install/http.js +119 -0
- package/lib/dataflow/sinks/install/postgres/index.js +129 -0
- package/lib/dataflow/sources/common.js +20 -0
- package/lib/dataflow/sources/handler.js +114 -0
- package/lib/dataflow/sources/index.js +35 -0
- package/lib/dataflow/sources/install/fastify/cookie.js +61 -0
- package/lib/dataflow/sources/install/fastify/fastify.js +89 -0
- package/lib/dataflow/sources/install/fastify/index.js +31 -0
- package/lib/dataflow/sources/install/http.js +181 -0
- package/lib/dataflow/sources/install/qs.js +88 -0
- package/lib/dataflow/tag-utils.js +122 -0
- package/lib/dataflow/tracker.js +127 -0
- package/lib/dataflow/utils/is-safe-content-type.js +30 -0
- package/lib/dataflow/utils/is-vulnerable.js +113 -0
- package/lib/index.js +36 -0
- package/package.json +21 -0
|
@@ -0,0 +1,89 @@
|
|
|
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(core) {
|
|
22
|
+
const {
|
|
23
|
+
logger,
|
|
24
|
+
depHooks,
|
|
25
|
+
patcher,
|
|
26
|
+
assess: { dataflow: { sources } },
|
|
27
|
+
} = core;
|
|
28
|
+
|
|
29
|
+
const source = sources.fastifyInstrumentation.fastify = {
|
|
30
|
+
install() {
|
|
31
|
+
depHooks.resolve({ name: 'fastify', version: '>=3.0.0' }, (fastify) => patcher.patch(fastify, {
|
|
32
|
+
name: 'fastify.constructor',
|
|
33
|
+
patchType,
|
|
34
|
+
post({ orig, result: server }) {
|
|
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
|
+
|
|
56
|
+
server.addHook('preValidation', function preValidationHandler(request, reply, done) {
|
|
57
|
+
const name = 'fastify.preValidation';
|
|
58
|
+
const bodyType = request?.headers?.['content-type']?.includes('/json')
|
|
59
|
+
? InputType.JSON_VALUE
|
|
60
|
+
: InputType.PARAMETER_VALUE;
|
|
61
|
+
|
|
62
|
+
[
|
|
63
|
+
{ key: 'query', inputType: InputType.PARAMETER_VALUE },
|
|
64
|
+
{ key: 'params', inputType: InputType.URL_PARAMETER },
|
|
65
|
+
{ key: 'body', inputType: bodyType }
|
|
66
|
+
].forEach(({ key, inputType }) => {
|
|
67
|
+
try {
|
|
68
|
+
sources.handle({
|
|
69
|
+
data: request[key],
|
|
70
|
+
inputType,
|
|
71
|
+
name,
|
|
72
|
+
stacktraceOpts: {
|
|
73
|
+
constructorOpt: preValidationHandler,
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.error({ err, inputType, name }, 'unable to handle fastify source');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
done();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return source;
|
|
89
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
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 sources = core.assess.dataflow.sources.fastifyInstrumentation = {};
|
|
22
|
+
|
|
23
|
+
require('./fastify')(core);
|
|
24
|
+
require('./cookie')(core);
|
|
25
|
+
|
|
26
|
+
sources.install = function install() {
|
|
27
|
+
callChildComponentMethodsSync(sources, 'install');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return sources;
|
|
31
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
|
|
19
|
+
// This is only just initiating an async storage for the http source
|
|
20
|
+
// TODO Tracking the user input
|
|
21
|
+
module.exports = function(core) {
|
|
22
|
+
const {
|
|
23
|
+
scopes: { sources },
|
|
24
|
+
instrumentation: { instrument },
|
|
25
|
+
patcher,
|
|
26
|
+
} = core;
|
|
27
|
+
|
|
28
|
+
const logger = core.logger.child('contrast:assess');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The around hook for `emit` that
|
|
32
|
+
* invokes the protect service to do analysis when appropriate.
|
|
33
|
+
*/
|
|
34
|
+
function around(next, data) {
|
|
35
|
+
const [type] = data.args;
|
|
36
|
+
|
|
37
|
+
if (type !== 'request') {
|
|
38
|
+
return next();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const [, req, response] = data.args;
|
|
43
|
+
const store = sources.getStore();
|
|
44
|
+
|
|
45
|
+
if (!store) {
|
|
46
|
+
logger.debug('cannot acquire store for assess request handling');
|
|
47
|
+
return next();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
patcher.patch(response, 'writeHead', {
|
|
51
|
+
name: 'write-head',
|
|
52
|
+
patchType,
|
|
53
|
+
pre(data) {
|
|
54
|
+
const obj = data.args[data.args.length - 1];
|
|
55
|
+
if (!obj) return;
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(obj)) {
|
|
58
|
+
for (let i = 0; i < obj.length; i += 2) {
|
|
59
|
+
const key = obj[i];
|
|
60
|
+
const value = obj[i + 1];
|
|
61
|
+
|
|
62
|
+
if (key.toLowerCase() === 'content-type') {
|
|
63
|
+
store.assess.responseData.contentType = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else if (typeof obj === 'object') {
|
|
67
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
68
|
+
if (key.toLowerCase() === 'content-type') {
|
|
69
|
+
store.assess.responseData.contentType = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
patcher.patch(response, 'setHeader', {
|
|
77
|
+
alwaysRun: true,
|
|
78
|
+
name: 'set-header',
|
|
79
|
+
patchType,
|
|
80
|
+
pre(data) {
|
|
81
|
+
const [name = '', value] = data.args;
|
|
82
|
+
if (name.toLowerCase() === 'content-type' && value) {
|
|
83
|
+
store.assess.responseData.contentType = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let uriPath, queries;
|
|
89
|
+
const ix = req.url.indexOf('?');
|
|
90
|
+
|
|
91
|
+
if (ix >= 0) {
|
|
92
|
+
uriPath = req.url.slice(0, ix);
|
|
93
|
+
queries = req.url.slice(ix + 1);
|
|
94
|
+
} else {
|
|
95
|
+
uriPath = req.url;
|
|
96
|
+
queries = '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const headers = {};
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
102
|
+
const header = req.rawHeaders[i].toLowerCase();
|
|
103
|
+
headers[header] = req.rawHeaders[i + 1];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const contentType = headers['content-type']?.toLowerCase();
|
|
107
|
+
const reqData = {
|
|
108
|
+
ip: req.socket.remoteAddress,
|
|
109
|
+
httpVersion: req.httpVersion,
|
|
110
|
+
method: req.method,
|
|
111
|
+
headers,
|
|
112
|
+
uriPath,
|
|
113
|
+
queries,
|
|
114
|
+
contentType,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
store.assess = {
|
|
118
|
+
reqData,
|
|
119
|
+
responseData: {},
|
|
120
|
+
sourceEventsCount: 0,
|
|
121
|
+
propagationEventsCount: 0,
|
|
122
|
+
findings: {},
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.error({ err }, 'Error during assess request handling');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return next();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function install() {
|
|
132
|
+
[{
|
|
133
|
+
moduleName: 'http'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
moduleName: 'https'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
moduleName: 'spdy'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
moduleName: 'http2',
|
|
143
|
+
patchObjectsProps: [
|
|
144
|
+
{
|
|
145
|
+
methods: ['createServer', 'createSecureServer'],
|
|
146
|
+
patchType,
|
|
147
|
+
patchObjects: [
|
|
148
|
+
{
|
|
149
|
+
name: 'Server.prototype',
|
|
150
|
+
methods: ['emit'],
|
|
151
|
+
patchType,
|
|
152
|
+
around
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}].forEach(({ moduleName, patchObjectsProps }) => {
|
|
158
|
+
const patchObjects = patchObjectsProps || [
|
|
159
|
+
{
|
|
160
|
+
name: 'Server.prototype',
|
|
161
|
+
methods: ['emit'],
|
|
162
|
+
patchType,
|
|
163
|
+
around
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
instrument({
|
|
167
|
+
moduleName,
|
|
168
|
+
patchObjects
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function uninstall() {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return core.assess.dataflow.sources.httpSourcesInstr = {
|
|
178
|
+
install,
|
|
179
|
+
uninstall
|
|
180
|
+
};
|
|
181
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
|
|
20
|
+
module.exports = function(core) {
|
|
21
|
+
const {
|
|
22
|
+
createSnapshot,
|
|
23
|
+
patcher,
|
|
24
|
+
depHooks,
|
|
25
|
+
scopes,
|
|
26
|
+
assess: {
|
|
27
|
+
dataflow: { tracker, sources },
|
|
28
|
+
},
|
|
29
|
+
logger
|
|
30
|
+
} = core;
|
|
31
|
+
|
|
32
|
+
function makeCapturer(opts) {
|
|
33
|
+
const snapshot = createSnapshot(opts);
|
|
34
|
+
return function(obj) {
|
|
35
|
+
obj.stack = snapshot();
|
|
36
|
+
return obj;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const qsSourcesInstr = sources.qsSourcesInstr = {
|
|
41
|
+
install() {
|
|
42
|
+
depHooks.resolve({ name: 'qs' }, (qs) => {
|
|
43
|
+
patcher.patch(qs, 'parse', {
|
|
44
|
+
name: 'qs',
|
|
45
|
+
patchType,
|
|
46
|
+
post({ args, orig, hooked, result }) {
|
|
47
|
+
if (result && Object.keys(result).length) {
|
|
48
|
+
const assessStore = scopes.sources.getStore().assess;
|
|
49
|
+
|
|
50
|
+
if (!assessStore) {
|
|
51
|
+
logger.debug('assessStore not available in `qs` hook');
|
|
52
|
+
} else {
|
|
53
|
+
const captureStack = makeCapturer({ constructorOpt: hooked, prependFrames: [orig] });
|
|
54
|
+
|
|
55
|
+
// We need to track `qs` result only when it's used as a query parser.
|
|
56
|
+
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
57
|
+
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
58
|
+
// some cases its use is optional and we cannot rely on it.
|
|
59
|
+
if (assessStore.reqData.queries === args[0]) {
|
|
60
|
+
for (const [key, value] of Object.entries(result)) {
|
|
61
|
+
// TODO Same as fastify - do we need a try-catch
|
|
62
|
+
// and checks for already tracked strings
|
|
63
|
+
const { extern } = tracker.track(
|
|
64
|
+
value,
|
|
65
|
+
captureStack({
|
|
66
|
+
inputType: 'query',
|
|
67
|
+
name: key,
|
|
68
|
+
tags: {
|
|
69
|
+
untrusted: [0, value.length - 1],
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (extern) {
|
|
75
|
+
result[key] = extern;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return qsSourcesInstr;
|
|
88
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
function ensureTagsImmutable(obj, tagName) {
|
|
19
|
+
return obj[tagName] ? [...obj[tagName]] : [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureObject(tags) {
|
|
23
|
+
return tags ? { ...tags } : {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function atomicAppend(firstTagRanges, secondTagRanges, offset) {
|
|
27
|
+
const newTagRanges = [...firstTagRanges];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < secondTagRanges.length; i++) {
|
|
30
|
+
secondTagRanges[i] += offset;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const firstTagRangesLastEnd = firstTagRanges[firstTagRanges.length - 1];
|
|
34
|
+
|
|
35
|
+
if (firstTagRangesLastEnd === secondTagRanges[0] || firstTagRangesLastEnd === secondTagRanges[0] - 1) {
|
|
36
|
+
newTagRanges.pop();
|
|
37
|
+
secondTagRanges.shift();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
newTagRanges.push(...secondTagRanges);
|
|
41
|
+
|
|
42
|
+
return newTagRanges;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function atomicSubset(tags, subsetStart, len) {
|
|
46
|
+
const ret = [];
|
|
47
|
+
const subsetStop = subsetStart + len;
|
|
48
|
+
|
|
49
|
+
for (let idx = 0; idx < tags.length - 1; idx += 2) {
|
|
50
|
+
const tagStart = tags[idx];
|
|
51
|
+
const tagStop = tags[idx + 1];
|
|
52
|
+
|
|
53
|
+
if (tagStop < subsetStart) {
|
|
54
|
+
// substr is below - continue to check next range
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (tagStart > subsetStop) {
|
|
59
|
+
// all other tags are above subset so we can stop
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
ret.push(
|
|
63
|
+
Math.max(tagStart, subsetStart) - subsetStart,
|
|
64
|
+
Math.min(tagStop, subsetStop) - subsetStart,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return ret;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createAppendTags(firstTags, secondTags, offset) {
|
|
72
|
+
const ret = Object.create(null);
|
|
73
|
+
const firstTagsObject = ensureObject(firstTags);
|
|
74
|
+
const secondTagsObject = ensureObject(secondTags);
|
|
75
|
+
const tagNames = new Set([...Object.keys(firstTagsObject), ...Object.keys(secondTagsObject)]);
|
|
76
|
+
|
|
77
|
+
for (const tagName of tagNames) {
|
|
78
|
+
const newTagRanges = atomicAppend(ensureTagsImmutable(firstTagsObject, tagName), ensureTagsImmutable(secondTagsObject, tagName), offset);
|
|
79
|
+
|
|
80
|
+
newTagRanges.length && (ret[tagName] = newTagRanges);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Object.keys(ret).length ? ret : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* assumes:
|
|
88
|
+
* - no mutation of arguments
|
|
89
|
+
* - input ranges will be in non-decreasing order
|
|
90
|
+
* - input ranges are "merged"
|
|
91
|
+
* i.e. no ranges are adjacent
|
|
92
|
+
* i.e. [0, 0, 1, 1] should be [0, 1]
|
|
93
|
+
* - return ranges will be merged and in non-decreasing order
|
|
94
|
+
*/
|
|
95
|
+
function createSubsetTags(tags, subsetStart, len) {
|
|
96
|
+
const ret = Object.create(null);
|
|
97
|
+
|
|
98
|
+
for (const tagName of Object.keys(ensureObject(tags))) {
|
|
99
|
+
const newTagRanges = atomicSubset(ensureTagsImmutable(tags, tagName), subsetStart, len);
|
|
100
|
+
|
|
101
|
+
newTagRanges.length && (ret[tagName] = newTagRanges);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Object.keys(ret).length ? ret : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createFullLengthCopyTags(tags, resultLength) {
|
|
108
|
+
if (!resultLength || resultLength <= 0) return null;
|
|
109
|
+
const ret = Object.create(null);
|
|
110
|
+
|
|
111
|
+
for (const tagName of Object.keys(ensureObject(tags))) {
|
|
112
|
+
ret[tagName] = [0, resultLength - 1];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Object.keys(ret).length ? ret : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
createSubsetTags,
|
|
120
|
+
createAppendTags,
|
|
121
|
+
createFullLengthCopyTags
|
|
122
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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 distringuish = require('@contrast/distringuish');
|
|
19
|
+
|
|
20
|
+
module.exports = function tracker(core) {
|
|
21
|
+
const {
|
|
22
|
+
assess: {
|
|
23
|
+
dataflow: {
|
|
24
|
+
eventFactory: {
|
|
25
|
+
createdEvents
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
logger
|
|
30
|
+
} = core;
|
|
31
|
+
|
|
32
|
+
const objMap = new WeakMap();
|
|
33
|
+
|
|
34
|
+
function getData(value) {
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
return distringuish.getProperties(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return objMap.get(value) || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function track(value, metadata) {
|
|
43
|
+
let ret = Object.create(null);
|
|
44
|
+
|
|
45
|
+
if (!value) {
|
|
46
|
+
const err = new Error();
|
|
47
|
+
logger.error({ err, value }, 'tracker.track called with invalid argument: value is falsy');
|
|
48
|
+
return { extern: null };
|
|
49
|
+
}
|
|
50
|
+
if (!metadata) {
|
|
51
|
+
const err = new Error();
|
|
52
|
+
logger.error({ err, metadata }, 'tracker.track called with invalid argument: metadata is falsy');
|
|
53
|
+
return { extern: null };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!createdEvents.has(metadata)) {
|
|
57
|
+
const err = new Error();
|
|
58
|
+
logger.error({ err, metadata }, 'tracker.track called without validated metadata');
|
|
59
|
+
return { extern: null };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
if (distringuish.getProperties(value)) {
|
|
64
|
+
const err = new Error();
|
|
65
|
+
logger.error({ err, value }, 'tracker.track called with a string value that is already tracked');
|
|
66
|
+
return { extern: null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const extern = distringuish.externalize(value);
|
|
70
|
+
/* c8 ignore next 5 */
|
|
71
|
+
if (!extern) {
|
|
72
|
+
const err = new Error();
|
|
73
|
+
logger.error({ err, value }, 'tracker.track was unable to externalize');
|
|
74
|
+
return { extern: null };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const externMetadata = distringuish.getProperties(extern);
|
|
78
|
+
metadata.value = value;
|
|
79
|
+
metadata.extern = extern;
|
|
80
|
+
|
|
81
|
+
ret = Object.assign(externMetadata, metadata);
|
|
82
|
+
|
|
83
|
+
return ret;
|
|
84
|
+
} else if (typeof value === 'object') {
|
|
85
|
+
const objInfo = objMap.get(value);
|
|
86
|
+
if (objInfo) {
|
|
87
|
+
return objInfo;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
objMap.set(value, metadata);
|
|
91
|
+
|
|
92
|
+
return metadata;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const err = new Error();
|
|
96
|
+
logger.error({ err, value }, 'tracker.track called with a value type that is not trackable');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function untrack(value) {
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
const props = distringuish.getProperties(value);
|
|
103
|
+
if (props) {
|
|
104
|
+
Object.assign(props, {
|
|
105
|
+
history: [],
|
|
106
|
+
tags: {},
|
|
107
|
+
});
|
|
108
|
+
delete props.resultTracked;
|
|
109
|
+
}
|
|
110
|
+
return distringuish.internalize(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof value === 'object') {
|
|
114
|
+
objMap.delete(value);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return core.assess.dataflow.tracker = {
|
|
122
|
+
track,
|
|
123
|
+
untrack,
|
|
124
|
+
getData,
|
|
125
|
+
getInfo: getData,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
@@ -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
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const SAFE_XSS_CONTENT_TYPES = [
|
|
18
|
+
'/json',
|
|
19
|
+
'/x-json',
|
|
20
|
+
'/javascript',
|
|
21
|
+
'/x-javascript',
|
|
22
|
+
'/pdf',
|
|
23
|
+
'/csv'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function isSafeContentType(contentType) {
|
|
27
|
+
return new RegExp(SAFE_XSS_CONTENT_TYPES.join('|')).test(contentType);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { isSafeContentType, SAFE_XSS_CONTENT_TYPES };
|