@contrast/assess 1.9.0 → 1.11.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 +1 -1
- package/lib/dataflow/index.js +1 -1
- package/lib/dataflow/propagation/common.js +1 -1
- package/lib/dataflow/propagation/index.js +3 -1
- package/lib/dataflow/propagation/install/JSON/index.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +1 -1
- package/lib/dataflow/propagation/install/JSON/stringify.js +1 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
- package/lib/dataflow/propagation/install/buffer.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/number.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/string.js +1 -1
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +1 -1
- package/lib/dataflow/propagation/install/decode-uri-component.js +1 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +1 -1
- package/lib/dataflow/propagation/install/ejs/index.js +1 -1
- package/lib/dataflow/propagation/install/encode-uri-component.js +1 -1
- package/lib/dataflow/propagation/install/escape-html.js +1 -1
- package/lib/dataflow/propagation/install/escape.js +1 -1
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +1 -1
- package/lib/dataflow/propagation/install/isnumeric-0.js +1 -1
- package/lib/dataflow/propagation/install/mongoose/common.js +20 -0
- package/lib/dataflow/propagation/install/mongoose/index.js +5 -9
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +149 -0
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +162 -0
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +91 -37
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +1 -1
- package/lib/dataflow/propagation/install/parse-int.js +1 -1
- package/lib/dataflow/propagation/install/path/basename.js +124 -0
- package/lib/dataflow/propagation/install/path/common.js +176 -0
- package/lib/dataflow/propagation/install/path/index.js +32 -0
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
- package/lib/dataflow/propagation/install/path/normalize.js +123 -0
- package/lib/dataflow/propagation/install/pug/index.js +1 -1
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +1 -1
- package/lib/dataflow/propagation/install/querystring/index.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +2 -2
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +182 -0
- package/lib/dataflow/propagation/install/sequelize.js +1 -1
- package/lib/dataflow/propagation/install/sql-template-strings.js +1 -1
- package/lib/dataflow/propagation/install/string/concat.js +1 -1
- package/lib/dataflow/propagation/install/string/format-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/index.js +65 -1
- package/lib/dataflow/propagation/install/string/match-all.js +236 -0
- package/lib/dataflow/propagation/install/string/match.js +84 -38
- package/lib/dataflow/propagation/install/string/replace.js +3 -3
- package/lib/dataflow/propagation/install/string/slice.js +2 -2
- package/lib/dataflow/propagation/install/string/split.js +2 -2
- package/lib/dataflow/propagation/install/string/substring.js +3 -3
- package/lib/dataflow/propagation/install/string/trim.js +2 -2
- package/lib/dataflow/propagation/install/unescape.js +1 -1
- package/lib/dataflow/propagation/install/url/domain-parsers.js +1 -1
- package/lib/dataflow/propagation/install/url/index.js +4 -1
- package/lib/dataflow/propagation/install/url/parse.js +131 -0
- package/lib/dataflow/propagation/install/url/searchParams.js +133 -0
- package/lib/dataflow/propagation/install/url/url.js +185 -0
- package/lib/dataflow/propagation/install/validator/hooks.js +1 -1
- package/lib/dataflow/propagation/install/validator/index.js +1 -1
- package/lib/dataflow/propagation/install/validator/methods.js +1 -1
- package/lib/dataflow/sinks/common.js +1 -1
- package/lib/dataflow/sinks/index.js +9 -5
- package/lib/dataflow/sinks/install/child-process.js +1 -1
- package/lib/dataflow/sinks/install/eval.js +138 -0
- package/lib/dataflow/sinks/install/express/index.js +1 -1
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/fastify/index.js +1 -1
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +3 -2
- package/lib/dataflow/sinks/install/fs.js +4 -4
- package/lib/dataflow/sinks/install/function.js +160 -0
- package/lib/dataflow/sinks/install/http/index.js +31 -0
- package/lib/dataflow/sinks/install/http/request.js +152 -0
- package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +3 -3
- package/lib/dataflow/sinks/install/koa/index.js +1 -1
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/marsdb.js +1 -1
- package/lib/dataflow/sinks/install/mongodb.js +34 -46
- package/lib/dataflow/sinks/install/mssql.js +45 -32
- package/lib/dataflow/sinks/install/mysql.js +1 -1
- package/lib/dataflow/sinks/install/postgres.js +1 -1
- package/lib/dataflow/sinks/install/sequelize.js +1 -1
- package/lib/dataflow/sinks/install/sqlite3.js +1 -1
- package/lib/dataflow/sinks/install/vm.js +276 -0
- package/lib/dataflow/sources/common.js +1 -1
- package/lib/dataflow/sources/handler.js +1 -1
- package/lib/dataflow/sources/index.js +1 -1
- package/lib/dataflow/sources/install/body-parser1.js +1 -1
- package/lib/dataflow/sources/install/busboy1.js +1 -1
- package/lib/dataflow/sources/install/cookie-parser1.js +1 -1
- package/lib/dataflow/sources/install/express/index.js +1 -1
- package/lib/dataflow/sources/install/express/params.js +1 -1
- package/lib/dataflow/sources/install/express/parsedUrl.js +1 -1
- package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
- package/lib/dataflow/sources/install/fastify/index.js +1 -1
- package/lib/dataflow/sources/install/formidable1.js +1 -1
- package/lib/dataflow/sources/install/http.js +1 -1
- package/lib/dataflow/sources/install/koa/index.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-routers.js +1 -1
- package/lib/dataflow/sources/install/koa/koa2.js +1 -1
- package/lib/dataflow/sources/install/qs6.js +1 -1
- package/lib/dataflow/sources/install/querystring.js +1 -1
- package/lib/dataflow/tag-utils.js +71 -2
- package/lib/dataflow/tracker.js +1 -1
- package/lib/dataflow/utils/is-safe-content-type.js +1 -1
- package/lib/dataflow/utils/is-vulnerable.js +1 -1
- package/lib/index.js +1 -1
- package/lib/response-scanning/handlers/index.js +36 -30
- package/lib/response-scanning/handlers/utils.js +1 -1
- package/lib/response-scanning/index.js +1 -1
- package/lib/response-scanning/install/http.js +3 -3
- package/lib/session-configuration/index.js +1 -1
- package/lib/session-configuration/install/http.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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 {
|
|
19
|
+
isString,
|
|
20
|
+
inspect,
|
|
21
|
+
join,
|
|
22
|
+
DataflowTag: {
|
|
23
|
+
UNTRUSTED,
|
|
24
|
+
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
25
|
+
CUSTOM_ENCODED,
|
|
26
|
+
CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
|
|
27
|
+
CUSTOM_VALIDATED,
|
|
28
|
+
LIMITED_CHARS,
|
|
29
|
+
},
|
|
30
|
+
} = require('@contrast/common');
|
|
31
|
+
const { patchType, filterSafeTags } = require('../common');
|
|
32
|
+
|
|
33
|
+
const ruleId = 'unsafe-code-execution';
|
|
34
|
+
const safeTags = [
|
|
35
|
+
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
36
|
+
CUSTOM_ENCODED,
|
|
37
|
+
CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
|
|
38
|
+
CUSTOM_VALIDATED,
|
|
39
|
+
LIMITED_CHARS,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
module.exports = function(core) {
|
|
43
|
+
const {
|
|
44
|
+
config,
|
|
45
|
+
logger,
|
|
46
|
+
patcher,
|
|
47
|
+
scopes: { sources, instrumentation },
|
|
48
|
+
assess: {
|
|
49
|
+
dataflow: {
|
|
50
|
+
tracker,
|
|
51
|
+
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
52
|
+
eventFactory: { createSinkEvent },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} = core;
|
|
56
|
+
|
|
57
|
+
core.assess.dataflow.sinks.contrastFunction = {
|
|
58
|
+
install() {
|
|
59
|
+
if (!global.ContrastMethods?.Function) {
|
|
60
|
+
logger.error(
|
|
61
|
+
'Cannot install `Function` instrumentation - Contrast method DNE'
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Object.assign(global.ContrastMethods, {
|
|
67
|
+
Function: patcher.patch(global.ContrastMethods.Function, {
|
|
68
|
+
name: 'global.ContrastMethods.Function',
|
|
69
|
+
patchType,
|
|
70
|
+
pre({ args: origArgs, hooked, orig, name }) {
|
|
71
|
+
const store = sources.getStore()?.assess;
|
|
72
|
+
const fnBody = origArgs[origArgs.length - 1];
|
|
73
|
+
if (
|
|
74
|
+
!store ||
|
|
75
|
+
instrumentation.isLocked() ||
|
|
76
|
+
!fnBody ||
|
|
77
|
+
!isString(fnBody)
|
|
78
|
+
)
|
|
79
|
+
return;
|
|
80
|
+
|
|
81
|
+
const strInfo = tracker.getData(fnBody);
|
|
82
|
+
|
|
83
|
+
if (!strInfo) return;
|
|
84
|
+
|
|
85
|
+
const isArgVulnerable = isVulnerable(UNTRUSTED, safeTags, strInfo.tags);
|
|
86
|
+
|
|
87
|
+
if (!isArgVulnerable && config.assess.safe_positives.enable) {
|
|
88
|
+
const foundSafeTags = filterSafeTags(safeTags, strInfo);
|
|
89
|
+
const safeStrInfo = {
|
|
90
|
+
value: strInfo.value,
|
|
91
|
+
tags: strInfo.tags,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
reportSafePositive({
|
|
95
|
+
name,
|
|
96
|
+
ruleId,
|
|
97
|
+
safeTags: foundSafeTags,
|
|
98
|
+
strInfo: safeStrInfo,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isArgVulnerable) {
|
|
103
|
+
const args = origArgs.map((arg, idx) => {
|
|
104
|
+
if (idx === origArgs.length - 1) {
|
|
105
|
+
return {
|
|
106
|
+
value: strInfo.value,
|
|
107
|
+
tracked: true,
|
|
108
|
+
inspectedValue: `'${strInfo.value}'`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const inspectedValue = inspect(arg);
|
|
113
|
+
const argInfo = arg && isString(arg) ? tracker.getData(arg) : null;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
value: argInfo ? argInfo.value : inspectedValue,
|
|
117
|
+
tracked: !!argInfo,
|
|
118
|
+
inspectedValue
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const event = createSinkEvent({
|
|
122
|
+
name,
|
|
123
|
+
context: `${name}(${join(
|
|
124
|
+
args.map((a) => a.inspectedValue),
|
|
125
|
+
', '
|
|
126
|
+
)})`,
|
|
127
|
+
history: [strInfo],
|
|
128
|
+
object: {
|
|
129
|
+
value: 'global.ContrastMethods',
|
|
130
|
+
tracked: false,
|
|
131
|
+
},
|
|
132
|
+
moduleName: 'global.ContrastMethods',
|
|
133
|
+
methodName: 'Function',
|
|
134
|
+
args: args.map((a) => ({
|
|
135
|
+
value: a.value,
|
|
136
|
+
tracked: a.tracked,
|
|
137
|
+
})),
|
|
138
|
+
tags: strInfo.tags,
|
|
139
|
+
source: `P${origArgs.length - 1}`,
|
|
140
|
+
stacktraceOpts: {
|
|
141
|
+
contructorOpt: hooked,
|
|
142
|
+
prependFrames: [orig],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (event) {
|
|
147
|
+
reportFindings({
|
|
148
|
+
ruleId,
|
|
149
|
+
sinkEvent: event,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return core.assess.dataflow.sinks.contrastFunction;
|
|
160
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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 http = core.assess.dataflow.sinks.http = {};
|
|
22
|
+
|
|
23
|
+
require('./request')(core);
|
|
24
|
+
require('./server-response')(core);
|
|
25
|
+
|
|
26
|
+
http.install = function() {
|
|
27
|
+
callChildComponentMethodsSync(http, 'install');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return http;
|
|
31
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2023 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 {
|
|
19
|
+
inspect,
|
|
20
|
+
isString,
|
|
21
|
+
DataflowTag: {
|
|
22
|
+
UNTRUSTED,
|
|
23
|
+
CUSTOM_ENCODED_SSRF,
|
|
24
|
+
CUSTOM_ENCODED,
|
|
25
|
+
CUSTOM_VALIDATED_SSRF,
|
|
26
|
+
CUSTOM_VALIDATED,
|
|
27
|
+
LIMITED_CHARS
|
|
28
|
+
}
|
|
29
|
+
} = require('@contrast/common');
|
|
30
|
+
const Url = require('url');
|
|
31
|
+
const trustedLibs = [/^(?!.*(newrelic)).*http.*$/];
|
|
32
|
+
const { patchType } = require('../../common');
|
|
33
|
+
const { createAppendTags } = require('../../../tag-utils');
|
|
34
|
+
|
|
35
|
+
module.exports = function(core) {
|
|
36
|
+
const {
|
|
37
|
+
depHooks,
|
|
38
|
+
patcher,
|
|
39
|
+
scopes: { sources },
|
|
40
|
+
assess: {
|
|
41
|
+
dataflow: {
|
|
42
|
+
tracker,
|
|
43
|
+
sinks: {
|
|
44
|
+
isVulnerable,
|
|
45
|
+
reportFindings
|
|
46
|
+
},
|
|
47
|
+
eventFactory: { createSinkEvent },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
} = core;
|
|
51
|
+
const http = core.assess.dataflow.sinks.http.request = {};
|
|
52
|
+
|
|
53
|
+
const safeTags = [
|
|
54
|
+
CUSTOM_ENCODED_SSRF,
|
|
55
|
+
CUSTOM_ENCODED,
|
|
56
|
+
CUSTOM_VALIDATED_SSRF,
|
|
57
|
+
CUSTOM_VALIDATED,
|
|
58
|
+
LIMITED_CHARS
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function getValueFromReq(value, key) {
|
|
62
|
+
if (isString(value)) {
|
|
63
|
+
const url = new Url.URL(value);
|
|
64
|
+
if (isString(url[key])) {
|
|
65
|
+
return url[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isString(value[key])) {
|
|
70
|
+
return value[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function containsTrustedLib(stack) {
|
|
75
|
+
for (const { file } of stack) {
|
|
76
|
+
for (const trusted of trustedLibs) {
|
|
77
|
+
if (trusted.exec(file)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
http.install = function() {
|
|
87
|
+
['http', 'https'].forEach((moduleName) => {
|
|
88
|
+
depHooks.resolve({ name: moduleName }, (module) => {
|
|
89
|
+
const name = `${moduleName}.request`;
|
|
90
|
+
const methodName = 'request';
|
|
91
|
+
patcher.patch(module, methodName, {
|
|
92
|
+
name,
|
|
93
|
+
patchType,
|
|
94
|
+
pre(data) {
|
|
95
|
+
const sourceContext = sources.getStore()?.assess;
|
|
96
|
+
if (!sourceContext) return;
|
|
97
|
+
|
|
98
|
+
const [req] = data.args;
|
|
99
|
+
if (!req) return;
|
|
100
|
+
|
|
101
|
+
['host', 'hostname', 'localAddress', 'protocol'].forEach((key) => {
|
|
102
|
+
const value = getValueFromReq(req, key);
|
|
103
|
+
if (!value) return;
|
|
104
|
+
|
|
105
|
+
const strInfo = tracker.getData(value);
|
|
106
|
+
if (!strInfo) return;
|
|
107
|
+
|
|
108
|
+
if (containsTrustedLib(strInfo.stack)) return;
|
|
109
|
+
|
|
110
|
+
const arg0 = isString(req) ? req : inspect(req);
|
|
111
|
+
const idx = arg0.indexOf(value);
|
|
112
|
+
const urlTags = createAppendTags({}, strInfo.tags, idx);
|
|
113
|
+
|
|
114
|
+
if (urlTags && isVulnerable(UNTRUSTED, safeTags, urlTags)) {
|
|
115
|
+
const event = createSinkEvent({
|
|
116
|
+
name,
|
|
117
|
+
moduleName,
|
|
118
|
+
methodName: 'request',
|
|
119
|
+
context: `${moduleName}.request(${arg0})`,
|
|
120
|
+
history: [strInfo],
|
|
121
|
+
object: {
|
|
122
|
+
tracked: false,
|
|
123
|
+
value: moduleName
|
|
124
|
+
},
|
|
125
|
+
args: [{
|
|
126
|
+
value: arg0,
|
|
127
|
+
tracked: true
|
|
128
|
+
}],
|
|
129
|
+
source: 'P0',
|
|
130
|
+
stacktraceOpts: {
|
|
131
|
+
constructorOpt: data.hooked,
|
|
132
|
+
prependFrames: [data.orig]
|
|
133
|
+
},
|
|
134
|
+
tags: urlTags,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (event) {
|
|
138
|
+
reportFindings({
|
|
139
|
+
ruleId: 'ssrf',
|
|
140
|
+
sinkEvent: event
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return http;
|
|
152
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ const {
|
|
|
31
31
|
},
|
|
32
32
|
Rule: { REFLECTED_XSS: ruleId },
|
|
33
33
|
} = require('@contrast/common');
|
|
34
|
-
const { patchType, filterSafeTags } = require('
|
|
34
|
+
const { patchType, filterSafeTags } = require('../../common');
|
|
35
35
|
|
|
36
36
|
module.exports = function(core) {
|
|
37
37
|
const {
|
|
@@ -52,7 +52,7 @@ module.exports = function(core) {
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
} = core;
|
|
55
|
-
const http = core.assess.dataflow.sinks.http = {};
|
|
55
|
+
const http = core.assess.dataflow.sinks.http.serverResponse = {};
|
|
56
56
|
|
|
57
57
|
const safeTags = [
|
|
58
58
|
ALPHANUM_SPACE_HYPHEN,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -84,7 +84,7 @@ module.exports = function(core) {
|
|
|
84
84
|
let urlPathTags = strInfo.tags;
|
|
85
85
|
|
|
86
86
|
if (url.indexOf('?') > -1) {
|
|
87
|
-
urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?'));
|
|
87
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?') + 1);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
if (urlPathTags && isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2023 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
DataflowTag: {
|
|
21
20
|
UNTRUSTED,
|
|
@@ -28,7 +27,8 @@ const {
|
|
|
28
27
|
Rule: { NOSQL_INJECTION_MONGO },
|
|
29
28
|
isNonEmptyObject,
|
|
30
29
|
traverseValues,
|
|
31
|
-
isString
|
|
30
|
+
isString,
|
|
31
|
+
inspect
|
|
32
32
|
} = require('@contrast/common');
|
|
33
33
|
const utils = require('../../tag-utils');
|
|
34
34
|
const { patchType, filterSafeTags } = require('../common');
|
|
@@ -82,20 +82,19 @@ module.exports = function(core) {
|
|
|
82
82
|
}
|
|
83
83
|
} = core;
|
|
84
84
|
|
|
85
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
86
85
|
const instr = core.assess.dataflow.sinks.mongodb = {};
|
|
87
86
|
|
|
88
87
|
instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(query) {
|
|
88
|
+
const reportSafe = [];
|
|
89
89
|
let vulnInfo = null;
|
|
90
|
-
let reportSafe = null;
|
|
91
90
|
|
|
92
91
|
if (isString(query)) {
|
|
93
92
|
const strInfo = tracker.getData(query);
|
|
94
93
|
if (strInfo) {
|
|
95
94
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
96
95
|
vulnInfo = { strInfo };
|
|
97
|
-
} else {
|
|
98
|
-
reportSafe
|
|
96
|
+
} else if (config.assess.safe_positives.enable) {
|
|
97
|
+
reportSafe.push({ strInfo });
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -110,8 +109,8 @@ module.exports = function(core) {
|
|
|
110
109
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
111
110
|
vulnInfo = { path: [...path], strInfo };
|
|
112
111
|
return true; // halts traversal
|
|
113
|
-
} else {
|
|
114
|
-
reportSafe
|
|
112
|
+
} else if (config.assess.safe_positives.enable) {
|
|
113
|
+
reportSafe.push({ path: [...path], strInfo });
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
});
|
|
@@ -120,8 +119,8 @@ module.exports = function(core) {
|
|
|
120
119
|
};
|
|
121
120
|
|
|
122
121
|
instr.getAggregateVulnerabilityInfo = function getAggregateVulnerabilityInfo(aggregation) {
|
|
122
|
+
const reportSafe = [];
|
|
123
123
|
let vulnInfo = null;
|
|
124
|
-
let reportSafe = null;
|
|
125
124
|
|
|
126
125
|
if (!isNonEmptyObject(aggregation)) return { vulnInfo, reportSafe };
|
|
127
126
|
|
|
@@ -142,8 +141,8 @@ module.exports = function(core) {
|
|
|
142
141
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
143
142
|
vulnInfo = { path: [...path], strInfo };
|
|
144
143
|
return true; // halts traversal
|
|
145
|
-
} else {
|
|
146
|
-
reportSafe
|
|
144
|
+
} else if (config.assess.safe_positives.enable) {
|
|
145
|
+
reportSafe.push({ path: [...path], strInfo });
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
148
|
}
|
|
@@ -153,16 +152,16 @@ module.exports = function(core) {
|
|
|
153
152
|
};
|
|
154
153
|
|
|
155
154
|
instr.getMapReduceVulnerabilityInfo = function getMapReduceVulnerabilityInfo(argToCheck, argIdx) {
|
|
155
|
+
const reportSafe = [];
|
|
156
156
|
let vulnInfo = null;
|
|
157
|
-
let reportSafe = null;
|
|
158
157
|
|
|
159
158
|
if (argIdx !== 2 && isString(argToCheck)) {
|
|
160
159
|
const strInfo = tracker.getData(argToCheck);
|
|
161
160
|
if (strInfo) {
|
|
162
161
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
163
162
|
vulnInfo = { strInfo };
|
|
164
|
-
} else {
|
|
165
|
-
reportSafe
|
|
163
|
+
} else if (config.assess.safe_positives.enable) {
|
|
164
|
+
reportSafe.push({ strInfo });
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
|
|
@@ -182,8 +181,8 @@ module.exports = function(core) {
|
|
|
182
181
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
183
182
|
vulnInfo = { path: [...path], strInfo };
|
|
184
183
|
return true; // halts traversal
|
|
185
|
-
} else {
|
|
186
|
-
reportSafe
|
|
184
|
+
} else if (config.assess.safe_positives.enable) {
|
|
185
|
+
reportSafe.push({ path: [...path], strInfo });
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
188
|
}
|
|
@@ -193,16 +192,16 @@ module.exports = function(core) {
|
|
|
193
192
|
};
|
|
194
193
|
|
|
195
194
|
instr.getGroupVulnerabilityInfo = function getGroupVulnerabilityInfo(argToCheck, argIdx) {
|
|
195
|
+
const reportSafe = [];
|
|
196
196
|
let vulnInfo = null;
|
|
197
|
-
let reportSafe = null;
|
|
198
197
|
|
|
199
198
|
if (argIdx !== 1 && isString(argToCheck)) {
|
|
200
199
|
const strInfo = tracker.getData(argToCheck);
|
|
201
200
|
if (strInfo) {
|
|
202
201
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
203
202
|
vulnInfo = { strInfo };
|
|
204
|
-
} else {
|
|
205
|
-
reportSafe
|
|
203
|
+
} else if (config.assess.safe_positives.enable) {
|
|
204
|
+
reportSafe.push({ strInfo });
|
|
206
205
|
}
|
|
207
206
|
}
|
|
208
207
|
|
|
@@ -217,8 +216,8 @@ module.exports = function(core) {
|
|
|
217
216
|
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
218
217
|
vulnInfo = { path: [...path], strInfo };
|
|
219
218
|
return true; // halts traversal
|
|
220
|
-
} else {
|
|
221
|
-
reportSafe
|
|
219
|
+
} else if (config.assess.safe_positives.enable) {
|
|
220
|
+
reportSafe.push({ path: [...path], strInfo });
|
|
222
221
|
}
|
|
223
222
|
}
|
|
224
223
|
});
|
|
@@ -249,14 +248,21 @@ module.exports = function(core) {
|
|
|
249
248
|
vulnArgIdx = argIdx;
|
|
250
249
|
break;
|
|
251
250
|
}
|
|
252
|
-
|
|
251
|
+
|
|
252
|
+
if (config.assess.safe_positives.enable && reportSafe.length) {
|
|
253
|
+
reportSafe.forEach(el => safeReports.push({ ...el, argIdx }));
|
|
254
|
+
}
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
if (!vulnInfo) {
|
|
256
258
|
if (safeReports.length && config.assess.safe_positives.enable) {
|
|
257
|
-
const safeTags = safeReports
|
|
259
|
+
const safeTags = safeReports
|
|
260
|
+
.map((report) => filterSafeTags(querySafeTags, report.strInfo))
|
|
261
|
+
.flat()
|
|
262
|
+
.filter((value, index, self) => index === self.indexOf(value));
|
|
263
|
+
|
|
258
264
|
const strInfo = safeReports.map((report) => {
|
|
259
|
-
const tags = report.path ?
|
|
265
|
+
const tags = report.path ? utils.createAdjustedQueryTags(report.path, report.strInfo.tags, report.strInfo.value, inspect(origArgs[report.argIdx], { depth: 4 })) : report.strInfo?.tags;
|
|
260
266
|
|
|
261
267
|
return {
|
|
262
268
|
value: inspect(origArgs[report.argIdx], { depth: 4 }),
|
|
@@ -267,7 +273,7 @@ module.exports = function(core) {
|
|
|
267
273
|
reportSafePositive({
|
|
268
274
|
name,
|
|
269
275
|
ruleId: NOSQL_INJECTION_MONGO,
|
|
270
|
-
safeTags
|
|
276
|
+
safeTags,
|
|
271
277
|
strInfo: strInfo.length === 1 ? strInfo[0] : strInfo
|
|
272
278
|
});
|
|
273
279
|
}
|
|
@@ -282,7 +288,7 @@ module.exports = function(core) {
|
|
|
282
288
|
tracked: idx === vulnArgIdx,
|
|
283
289
|
}));
|
|
284
290
|
|
|
285
|
-
const tags = path ?
|
|
291
|
+
const tags = path ? utils.createAdjustedQueryTags(path, strInfo.tags, strInfo.value, args[vulnArgIdx].value) : strInfo?.tags;
|
|
286
292
|
const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
|
|
287
293
|
const sinkEvent = createSinkEvent({
|
|
288
294
|
args,
|
|
@@ -324,8 +330,6 @@ module.exports = function(core) {
|
|
|
324
330
|
});
|
|
325
331
|
};
|
|
326
332
|
|
|
327
|
-
return instr;
|
|
328
|
-
|
|
329
333
|
function patchCollection(mongodb, version) {
|
|
330
334
|
for (const method of collectionMethods) {
|
|
331
335
|
const proto = mongodb.Collection.prototype;
|
|
@@ -402,21 +406,5 @@ module.exports = function(core) {
|
|
|
402
406
|
return name;
|
|
403
407
|
}
|
|
404
408
|
|
|
405
|
-
|
|
406
|
-
const { tags } = strInfo;
|
|
407
|
-
let idx = -1;
|
|
408
|
-
for (const str of [...path, strInfo.value]) {
|
|
409
|
-
// This is the case with the `.aggregate` method
|
|
410
|
-
// where the argument is an array
|
|
411
|
-
if (str === 0) continue;
|
|
412
|
-
|
|
413
|
-
idx = argString.indexOf(str, idx);
|
|
414
|
-
if (idx == -1) {
|
|
415
|
-
idx = -1;
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return idx > 0 ? utils.createAppendTags([], tags, idx) : strInfo.tags;
|
|
421
|
-
}
|
|
409
|
+
return instr;
|
|
422
410
|
};
|