@contrast/assess 1.5.0 → 1.7.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 +10 -5
- package/lib/dataflow/propagation/index.js +1 -0
- package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
- package/lib/dataflow/propagation/install/decode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +5 -2
- package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/escape-html.js +7 -4
- package/lib/dataflow/propagation/install/escape.js +5 -2
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +5 -2
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -2
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
- package/lib/dataflow/propagation/install/querystring/parse.js +8 -3
- package/lib/dataflow/propagation/install/sequelize.js +310 -0
- package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
- package/lib/dataflow/propagation/install/string/match.js +2 -2
- package/lib/dataflow/propagation/install/string/replace.js +13 -4
- package/lib/dataflow/propagation/install/unescape.js +5 -2
- package/lib/dataflow/propagation/install/validator/methods.js +60 -51
- package/lib/dataflow/sinks/common.js +10 -1
- package/lib/dataflow/sinks/index.js +34 -1
- package/lib/dataflow/sinks/install/child-process.js +150 -13
- package/lib/dataflow/sinks/install/express/index.js +29 -0
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +113 -75
- package/lib/dataflow/sinks/install/fs.js +136 -0
- package/lib/dataflow/sinks/install/http.js +46 -17
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +50 -17
- package/lib/dataflow/sinks/install/marsdb.js +135 -0
- package/lib/dataflow/sinks/install/mongodb.js +322 -0
- package/lib/dataflow/sinks/install/mssql.js +19 -10
- package/lib/dataflow/sinks/install/mysql.js +138 -0
- package/lib/dataflow/sinks/install/postgres.js +37 -23
- package/lib/dataflow/sinks/install/sequelize.js +142 -0
- package/lib/dataflow/sinks/install/sqlite3.js +20 -10
- package/lib/dataflow/sources/handler.js +14 -9
- package/lib/dataflow/sources/index.js +4 -1
- package/lib/dataflow/sources/install/body-parser1.js +120 -0
- package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
- package/lib/dataflow/sources/install/express/index.js +28 -0
- package/package.json +3 -3
|
@@ -0,0 +1,135 @@
|
|
|
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 util = require('util');
|
|
18
|
+
const { patchType } = require('../common');
|
|
19
|
+
const {
|
|
20
|
+
traverseValues,
|
|
21
|
+
Rule,
|
|
22
|
+
DataflowTag: {
|
|
23
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
24
|
+
LIMITED_CHARS,
|
|
25
|
+
UNTRUSTED,
|
|
26
|
+
STRING_TYPE_CHECKED,
|
|
27
|
+
CUSTOM_VALIDATED_NOSQL_INJECTION,
|
|
28
|
+
},
|
|
29
|
+
} = require('@contrast/common');
|
|
30
|
+
|
|
31
|
+
const collectionMethods = ['find', 'findOne', 'update', 'remove'];
|
|
32
|
+
const querySafeTags = [
|
|
33
|
+
LIMITED_CHARS,
|
|
34
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
35
|
+
STRING_TYPE_CHECKED,
|
|
36
|
+
CUSTOM_VALIDATED_NOSQL_INJECTION,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
module.exports = function(core) {
|
|
40
|
+
const {
|
|
41
|
+
depHooks,
|
|
42
|
+
logger,
|
|
43
|
+
patcher,
|
|
44
|
+
scopes: { sources, instrumentation },
|
|
45
|
+
assess: {
|
|
46
|
+
dataflow: {
|
|
47
|
+
tracker,
|
|
48
|
+
sinks: { isVulnerable, reportFindings },
|
|
49
|
+
eventFactory: { createSinkEvent },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
} = core;
|
|
53
|
+
|
|
54
|
+
const instr = core.assess.dataflow.sinks.marsdb = {};
|
|
55
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
56
|
+
|
|
57
|
+
function getVulnerabilityInfo(query) {
|
|
58
|
+
let vulnInfo = null;
|
|
59
|
+
if (!query) return vulnInfo;
|
|
60
|
+
|
|
61
|
+
traverseValues(query, (path, type, value) => {
|
|
62
|
+
const strInfo = tracker.getData(value);
|
|
63
|
+
if (strInfo && isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
64
|
+
vulnInfo = { path, strInfo };
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return vulnInfo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function patchCollection(marsdb, method) {
|
|
73
|
+
const proto = marsdb.Collection.prototype;
|
|
74
|
+
const name = `marsdb.Collection.prototype.${method}`;
|
|
75
|
+
|
|
76
|
+
if (!proto[method]) {
|
|
77
|
+
logger.trace({ name }, `marsdb method ${method} not found!`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
patcher.patch(proto, method, {
|
|
82
|
+
name,
|
|
83
|
+
patchType,
|
|
84
|
+
around(next, data) {
|
|
85
|
+
const sourceCtx = sources.getStore()?.assess;
|
|
86
|
+
if (!sourceCtx || instrumentation.isLocked()) {
|
|
87
|
+
return next();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const argIdx = 0;
|
|
91
|
+
const result = getVulnerabilityInfo(data.args[argIdx]);
|
|
92
|
+
if (!result) {
|
|
93
|
+
return next();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { strInfo } = result;
|
|
97
|
+
const args = data.args.map((arg, idx) => ({
|
|
98
|
+
value: inspect(arg),
|
|
99
|
+
tracked: idx === argIdx,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
const sinkEvent = createSinkEvent({
|
|
103
|
+
args,
|
|
104
|
+
context: `marsdb.Collection.${method}(${args.map((a) => a.value)})`,
|
|
105
|
+
history: [strInfo],
|
|
106
|
+
object: {
|
|
107
|
+
tracked: false,
|
|
108
|
+
value: 'marsdb.Collection',
|
|
109
|
+
},
|
|
110
|
+
name,
|
|
111
|
+
result: strInfo.result,
|
|
112
|
+
source: `P${argIdx}`,
|
|
113
|
+
stacktraceOpts: {
|
|
114
|
+
constructorOpt: data.hooked,
|
|
115
|
+
},
|
|
116
|
+
tags: strInfo.tags,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (sinkEvent) {
|
|
120
|
+
reportFindings({ ruleId: Rule.NOSQL_INJECTION_MONGO, sinkEvent });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return next();
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
instr.install = function() {
|
|
129
|
+
depHooks.resolve({ name: 'marsdb' }, (marsdb) => {
|
|
130
|
+
collectionMethods.forEach((method) => patchCollection(marsdb, method));
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return instr;
|
|
135
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
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 {
|
|
20
|
+
DataflowTag: {
|
|
21
|
+
UNTRUSTED,
|
|
22
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
23
|
+
CUSTOM_VALIDATED,
|
|
24
|
+
CUSTOM_VALIDATED_NOSQL_INJECTION,
|
|
25
|
+
LIMITED_CHARS,
|
|
26
|
+
STRING_TYPE_CHECKED,
|
|
27
|
+
},
|
|
28
|
+
Rule: { NOSQL_INJECTION_MONGO },
|
|
29
|
+
isNonEmptyObject,
|
|
30
|
+
traverseValues,
|
|
31
|
+
isString
|
|
32
|
+
} = require('@contrast/common');
|
|
33
|
+
const utils = require('../../tag-utils');
|
|
34
|
+
const { patchType, filterSafeTags } = require('../common');
|
|
35
|
+
|
|
36
|
+
const collectionMethods = [
|
|
37
|
+
'find',
|
|
38
|
+
'findOne',
|
|
39
|
+
'findAndModify',
|
|
40
|
+
'findOneAndDelete',
|
|
41
|
+
'findOneAndReplace',
|
|
42
|
+
'findOneAndUpdate',
|
|
43
|
+
'remove',
|
|
44
|
+
'replaceOne',
|
|
45
|
+
'update',
|
|
46
|
+
'updateOne',
|
|
47
|
+
'updateMany',
|
|
48
|
+
'deleteOne',
|
|
49
|
+
'deleteMany',
|
|
50
|
+
];
|
|
51
|
+
const dbMethods = [
|
|
52
|
+
'command',
|
|
53
|
+
'eval'
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const querySafeTags = [
|
|
57
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
58
|
+
CUSTOM_VALIDATED,
|
|
59
|
+
CUSTOM_VALIDATED_NOSQL_INJECTION,
|
|
60
|
+
LIMITED_CHARS,
|
|
61
|
+
STRING_TYPE_CHECKED,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
module.exports = function(core) {
|
|
65
|
+
const {
|
|
66
|
+
config,
|
|
67
|
+
depHooks,
|
|
68
|
+
logger,
|
|
69
|
+
patcher,
|
|
70
|
+
scopes: { sources, instrumentation },
|
|
71
|
+
assess: {
|
|
72
|
+
dataflow: {
|
|
73
|
+
tracker,
|
|
74
|
+
sinks: { isVulnerable, runInActiveSink, isLocked, reportFindings, reportSafePositive },
|
|
75
|
+
eventFactory: { createSinkEvent }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} = core;
|
|
79
|
+
|
|
80
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
81
|
+
const instr = core.assess.dataflow.sinks.mongodb = {};
|
|
82
|
+
|
|
83
|
+
instr.getVulnerabilityInfo = function getVulnerabilityInfo(query) {
|
|
84
|
+
let vulnInfo = null;
|
|
85
|
+
let reportSafe = null;
|
|
86
|
+
|
|
87
|
+
if (isString(query)) {
|
|
88
|
+
const strInfo = tracker.getData(query);
|
|
89
|
+
if (strInfo) {
|
|
90
|
+
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
91
|
+
vulnInfo = { strInfo };
|
|
92
|
+
} else {
|
|
93
|
+
reportSafe = { strInfo };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { vulnInfo, reportSafe };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!isNonEmptyObject(query)) return { vulnInfo, reportSafe };
|
|
101
|
+
|
|
102
|
+
traverseValues(query, (path, _type, value) => {
|
|
103
|
+
const strInfo = tracker.getData(value);
|
|
104
|
+
if (strInfo) {
|
|
105
|
+
if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
|
|
106
|
+
vulnInfo = { path, strInfo };
|
|
107
|
+
return true; // halts traversal
|
|
108
|
+
} else {
|
|
109
|
+
reportSafe = { path, strInfo };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return { vulnInfo, reportSafe };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
instr.install = function() {
|
|
118
|
+
depHooks.resolve({ name: 'mongodb' }, (mongodb, version) => {
|
|
119
|
+
patchCollection(mongodb, version);
|
|
120
|
+
patchDatabase(mongodb, version);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return instr;
|
|
125
|
+
|
|
126
|
+
function patchCollection(mongodb, version) {
|
|
127
|
+
for (const method of collectionMethods) {
|
|
128
|
+
const proto = mongodb.Collection.prototype;
|
|
129
|
+
const name = `mongodb.Collection.prototype.${method}`;
|
|
130
|
+
|
|
131
|
+
if (!proto[method]) {
|
|
132
|
+
logger.trace({ name, version }, 'method not found - skipping instrumentation');
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
patcher.patch(proto, method, {
|
|
137
|
+
name,
|
|
138
|
+
patchType,
|
|
139
|
+
around(next, data) {
|
|
140
|
+
const { obj, args: origArgs } = data;
|
|
141
|
+
const sourceCtx = sources.getStore()?.assess;
|
|
142
|
+
|
|
143
|
+
if (isLocked(NOSQL_INJECTION_MONGO) || instrumentation.isLocked() || !sourceCtx) {
|
|
144
|
+
return next();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const argIdx = 0;
|
|
148
|
+
try {
|
|
149
|
+
const { vulnInfo, reportSafe } = instr.getVulnerabilityInfo(origArgs[argIdx]);
|
|
150
|
+
|
|
151
|
+
if (!vulnInfo) {
|
|
152
|
+
reportSafe && config.assess.safe_positives.enable && reportSafePositive({
|
|
153
|
+
name,
|
|
154
|
+
ruleId: NOSQL_INJECTION_MONGO,
|
|
155
|
+
safeTags: filterSafeTags(querySafeTags, reportSafe.strInfo),
|
|
156
|
+
strInfo: {
|
|
157
|
+
value: inspect(origArgs[argIdx]),
|
|
158
|
+
tags: getAdjustedQueryTags(reportSafe.path, reportSafe.strInfo, inspect(origArgs[argIdx])),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
return method === 'findOne' ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { path, strInfo } = vulnInfo;
|
|
165
|
+
const objName = getObjectName(obj);
|
|
166
|
+
const args = origArgs.map((arg, idx) => ({
|
|
167
|
+
value: inspect(arg),
|
|
168
|
+
tracked: idx === argIdx,
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
const tags = getAdjustedQueryTags(path, strInfo, args[argIdx].value);
|
|
172
|
+
const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
|
|
173
|
+
const sinkEvent = createSinkEvent({
|
|
174
|
+
args,
|
|
175
|
+
context: `${objName}.${method}(${args.map((a) => a.value)})`,
|
|
176
|
+
history: [strInfo],
|
|
177
|
+
object: {
|
|
178
|
+
tracked: false,
|
|
179
|
+
value: 'mongodb.Collection',
|
|
180
|
+
},
|
|
181
|
+
name,
|
|
182
|
+
result: {
|
|
183
|
+
tracked: false,
|
|
184
|
+
value: resultVal,
|
|
185
|
+
},
|
|
186
|
+
source: `P${argIdx}`,
|
|
187
|
+
stacktraceOpts: {
|
|
188
|
+
constructorOpt: data.hooked,
|
|
189
|
+
},
|
|
190
|
+
tags,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (sinkEvent) {
|
|
194
|
+
reportFindings({ ruleId: NOSQL_INJECTION_MONGO, sinkEvent });
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
core.logger.error({ name, err }, 'assess sink analysis failed');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (method === 'findOne') {
|
|
201
|
+
// `findOne` will call `find` so don't analyze in nested call
|
|
202
|
+
return runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next());
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return next();
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function patchDatabase(mongodb, version) {
|
|
212
|
+
for (const method of dbMethods) {
|
|
213
|
+
const proto = mongodb.Db.prototype;
|
|
214
|
+
const name = `mongodb.Db.prototype.${method}`;
|
|
215
|
+
|
|
216
|
+
if (!proto[method]) {
|
|
217
|
+
logger.trace({ name, version }, 'method not found - skipping instrumentation');
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
patcher.patch(proto, method, {
|
|
222
|
+
name,
|
|
223
|
+
patchType,
|
|
224
|
+
around(next, data) {
|
|
225
|
+
const { obj, args: origArgs } = data;
|
|
226
|
+
const sourceCtx = sources.getStore()?.assess;
|
|
227
|
+
|
|
228
|
+
if (isLocked(NOSQL_INJECTION_MONGO) || instrumentation.isLocked() || !sourceCtx) {
|
|
229
|
+
return next();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const argIdx = 0;
|
|
233
|
+
try {
|
|
234
|
+
const { vulnInfo, reportSafe } = instr.getVulnerabilityInfo(origArgs[argIdx]);
|
|
235
|
+
|
|
236
|
+
if (!vulnInfo) {
|
|
237
|
+
const tags = reportSafe?.path ? getAdjustedQueryTags(reportSafe.path, reportSafe.strInfo, inspect(origArgs[argIdx])) : reportSafe?.strInfo?.tags;
|
|
238
|
+
reportSafe && config.assess.safe_positives.enable && reportSafePositive({
|
|
239
|
+
name,
|
|
240
|
+
ruleId: NOSQL_INJECTION_MONGO,
|
|
241
|
+
safeTags: filterSafeTags(querySafeTags, reportSafe.strInfo),
|
|
242
|
+
strInfo: {
|
|
243
|
+
value: inspect(origArgs[argIdx]),
|
|
244
|
+
tags
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
return method === 'eval' ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { path, strInfo } = vulnInfo;
|
|
251
|
+
const objName = getObjectName(obj, 'Db');
|
|
252
|
+
const args = origArgs.map((arg, idx) => ({
|
|
253
|
+
value: isString(arg) ? arg : inspect(arg),
|
|
254
|
+
tracked: idx === argIdx,
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
const tags = path ? getAdjustedQueryTags(path, strInfo, args[argIdx].value) : strInfo?.tags;
|
|
258
|
+
const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
|
|
259
|
+
const sinkEvent = createSinkEvent({
|
|
260
|
+
args,
|
|
261
|
+
context: `${objName}.${method}(${args.map((a) => a.value)})`,
|
|
262
|
+
history: [strInfo],
|
|
263
|
+
object: {
|
|
264
|
+
tracked: false,
|
|
265
|
+
value: 'mongodb.Db',
|
|
266
|
+
},
|
|
267
|
+
name,
|
|
268
|
+
result: {
|
|
269
|
+
tracked: false,
|
|
270
|
+
value: resultVal,
|
|
271
|
+
},
|
|
272
|
+
source: `P${argIdx}`,
|
|
273
|
+
stacktraceOpts: {
|
|
274
|
+
constructorOpt: data.hooked,
|
|
275
|
+
},
|
|
276
|
+
tags,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (sinkEvent) {
|
|
280
|
+
reportFindings({ ruleId: NOSQL_INJECTION_MONGO, sinkEvent });
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
core.logger.error({ name, err }, 'assess sink analysis failed');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (method === 'eval') {
|
|
287
|
+
// `eval` will call `command` so don't analyze in nested call
|
|
288
|
+
return runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next());
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return next();
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getObjectName(obj, entity) {
|
|
298
|
+
let name = '';
|
|
299
|
+
name += obj.s?.namespace?.db || 'db';
|
|
300
|
+
|
|
301
|
+
if (entity !== 'Db') {
|
|
302
|
+
name += '.';
|
|
303
|
+
name += obj.s?.namespace?.collection || 'collection';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return name;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function getAdjustedQueryTags(path, strInfo, argString) {
|
|
310
|
+
const { tags } = strInfo;
|
|
311
|
+
let idx = -1;
|
|
312
|
+
for (const str of [...path, strInfo.value]) {
|
|
313
|
+
idx = argString.indexOf(str, idx);
|
|
314
|
+
if (idx == -1) {
|
|
315
|
+
idx = -1;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return idx > 0 ? utils.createAppendTags([], tags, idx) : strInfo.tags;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
@@ -15,15 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
|
|
20
|
+
Rule: { SQL_INJECTION },
|
|
21
|
+
isString
|
|
22
|
+
} = require('@contrast/common');
|
|
19
23
|
const { createModuleLabel } = require('../../propagation/common');
|
|
20
24
|
const { patchType } = require('../common');
|
|
21
25
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const safeTags = [
|
|
27
|
+
SQL_ENCODED,
|
|
28
|
+
LIMITED_CHARS,
|
|
29
|
+
CUSTOM_VALIDATED,
|
|
30
|
+
CUSTOM_ENCODED,
|
|
27
31
|
];
|
|
28
32
|
|
|
29
33
|
module.exports = function (core) {
|
|
@@ -34,7 +38,7 @@ module.exports = function (core) {
|
|
|
34
38
|
assess: {
|
|
35
39
|
dataflow: {
|
|
36
40
|
tracker,
|
|
37
|
-
sinks: { isVulnerable, reportFindings },
|
|
41
|
+
sinks: { isVulnerable, isLocked, reportFindings },
|
|
38
42
|
eventFactory: { createSinkEvent },
|
|
39
43
|
},
|
|
40
44
|
},
|
|
@@ -42,10 +46,15 @@ module.exports = function (core) {
|
|
|
42
46
|
|
|
43
47
|
const pre = (name, obj, version) => (data) => {
|
|
44
48
|
const store = sources.getStore()?.assess;
|
|
45
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
!store ||
|
|
51
|
+
!data.args[0] ||
|
|
52
|
+
!isString(data.args[0]) ||
|
|
53
|
+
isLocked(SQL_INJECTION)
|
|
54
|
+
) return;
|
|
46
55
|
|
|
47
56
|
const strInfo = tracker.getData(data.args[0]);
|
|
48
|
-
if (!strInfo || !isVulnerable(
|
|
57
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
49
58
|
return;
|
|
50
59
|
}
|
|
51
60
|
|
|
@@ -71,7 +80,7 @@ module.exports = function (core) {
|
|
|
71
80
|
|
|
72
81
|
if (event) {
|
|
73
82
|
reportFindings({
|
|
74
|
-
ruleId:
|
|
83
|
+
ruleId: SQL_INJECTION,
|
|
75
84
|
sinkEvent: event,
|
|
76
85
|
});
|
|
77
86
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { patchType } = require('../common');
|
|
19
|
+
const {
|
|
20
|
+
Rule: { SQL_INJECTION },
|
|
21
|
+
isString,
|
|
22
|
+
DataflowTag: {
|
|
23
|
+
CUSTOM_ENCODED_SQL_INJECTION,
|
|
24
|
+
CUSTOM_ENCODED,
|
|
25
|
+
CUSTOM_VALIDATED_SQL_INJECTION,
|
|
26
|
+
CUSTOM_VALIDATED,
|
|
27
|
+
SQL_ENCODED,
|
|
28
|
+
LIMITED_CHARS,
|
|
29
|
+
UNTRUSTED
|
|
30
|
+
},
|
|
31
|
+
} = require('@contrast/common');
|
|
32
|
+
|
|
33
|
+
const safeTags = [
|
|
34
|
+
CUSTOM_ENCODED_SQL_INJECTION,
|
|
35
|
+
CUSTOM_ENCODED,
|
|
36
|
+
CUSTOM_VALIDATED_SQL_INJECTION,
|
|
37
|
+
CUSTOM_VALIDATED,
|
|
38
|
+
SQL_ENCODED,
|
|
39
|
+
LIMITED_CHARS,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
module.exports = function (core) {
|
|
43
|
+
const {
|
|
44
|
+
depHooks,
|
|
45
|
+
patcher,
|
|
46
|
+
scopes: { sources },
|
|
47
|
+
assess: {
|
|
48
|
+
dataflow: {
|
|
49
|
+
tracker,
|
|
50
|
+
sinks: { isVulnerable, isLocked, reportFindings },
|
|
51
|
+
eventFactory: { createSinkEvent },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
} = core;
|
|
55
|
+
|
|
56
|
+
function getValueFromArgs([value]) {
|
|
57
|
+
if (isString(value)) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isString(value.sql)) {
|
|
62
|
+
return value.sql;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const pre = (module, file, obj) => (data) => {
|
|
67
|
+
const store = sources.getStore()?.assess;
|
|
68
|
+
if (
|
|
69
|
+
!store ||
|
|
70
|
+
!data.args[0] ||
|
|
71
|
+
isLocked(SQL_INJECTION)
|
|
72
|
+
) return;
|
|
73
|
+
|
|
74
|
+
const val = getValueFromArgs(data.args);
|
|
75
|
+
if (!val) return;
|
|
76
|
+
|
|
77
|
+
const strInfo = tracker.getData(val);
|
|
78
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const event = createSinkEvent({
|
|
83
|
+
name: `${module}/${file}`,
|
|
84
|
+
history: [strInfo],
|
|
85
|
+
object: {
|
|
86
|
+
value: `${module}.${obj}`,
|
|
87
|
+
tracked: false,
|
|
88
|
+
},
|
|
89
|
+
args: [
|
|
90
|
+
{
|
|
91
|
+
value: strInfo.value,
|
|
92
|
+
tracked: true,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
tags: strInfo.tags,
|
|
96
|
+
source: 'P0',
|
|
97
|
+
stacktraceOpts: {
|
|
98
|
+
contructorOpt: data.hooked,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (event) {
|
|
103
|
+
reportFindings({
|
|
104
|
+
ruleId: SQL_INJECTION,
|
|
105
|
+
sinkEvent: event,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
core.assess.dataflow.sinks.mysql = {
|
|
111
|
+
install() {
|
|
112
|
+
depHooks.resolve(
|
|
113
|
+
{ name: 'mysql', file: 'lib/Connection' },
|
|
114
|
+
(Connection) => {
|
|
115
|
+
patcher.patch(Connection.prototype, 'query', {
|
|
116
|
+
name: 'Connection.prototype.query',
|
|
117
|
+
patchType,
|
|
118
|
+
pre: pre('mysql', 'lib/Connection.query', 'Connection')
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
depHooks.resolve(
|
|
123
|
+
{ name: 'mysql2', file: 'lib/connection' },
|
|
124
|
+
(connection) => {
|
|
125
|
+
['query', 'execute'].forEach((method) => {
|
|
126
|
+
patcher.patch(connection.prototype, `${method}`, {
|
|
127
|
+
name: `connection.prototype.${method}`,
|
|
128
|
+
patchType,
|
|
129
|
+
pre: pre('mysql2', `lib/connection.Connection.${method}`, 'connection')
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return core.assess.dataflow.sinks.mysql;
|
|
138
|
+
};
|