@contrast/assess 1.31.0 → 1.32.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.
Files changed (154) hide show
  1. package/lib/crypto-analysis/install/crypto.test.js +146 -0
  2. package/lib/crypto-analysis/install/math.test.js +65 -0
  3. package/lib/dataflow/index.test.js +36 -0
  4. package/lib/dataflow/propagation/index.test.js +103 -0
  5. package/lib/dataflow/propagation/install/JSON/index.test.js +50 -0
  6. package/lib/dataflow/propagation/install/JSON/parse-fn.test.js +232 -0
  7. package/lib/dataflow/propagation/install/JSON/parse.test.js +968 -0
  8. package/lib/dataflow/propagation/install/JSON/stringify.test.js +265 -0
  9. package/lib/dataflow/propagation/install/array-prototype-join.test.js +106 -0
  10. package/lib/dataflow/propagation/install/buffer.test.js +109 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/add.test.js +94 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/index.test.js +49 -0
  13. package/lib/dataflow/propagation/install/contrast-methods/number.test.js +50 -0
  14. package/lib/dataflow/propagation/install/contrast-methods/string.test.js +148 -0
  15. package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +145 -0
  16. package/lib/dataflow/propagation/install/decode-uri-component.test.js +78 -0
  17. package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +69 -0
  18. package/lib/dataflow/propagation/install/ejs/template.test.js +62 -0
  19. package/lib/dataflow/propagation/install/encode-uri.test.js +83 -0
  20. package/lib/dataflow/propagation/install/escape-html.test.js +71 -0
  21. package/lib/dataflow/propagation/install/escape.test.js +73 -0
  22. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +71 -0
  23. package/lib/dataflow/propagation/install/isnumeric-0.test.js +58 -0
  24. package/lib/dataflow/propagation/install/joi/any.test.js +270 -0
  25. package/lib/dataflow/propagation/install/joi/array.test.js +912 -0
  26. package/lib/dataflow/propagation/install/joi/boolean.test.js +103 -0
  27. package/lib/dataflow/propagation/install/joi/expression.test.js +76 -0
  28. package/lib/dataflow/propagation/install/joi/index.test.js +39 -0
  29. package/lib/dataflow/propagation/install/joi/number.test.js +103 -0
  30. package/lib/dataflow/propagation/install/joi/object.test.js +119 -0
  31. package/lib/dataflow/propagation/install/joi/ref.test.js +607 -0
  32. package/lib/dataflow/propagation/install/joi/string-schema.test.js +513 -0
  33. package/lib/dataflow/propagation/install/mongoose/index.test.js +42 -0
  34. package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +348 -0
  35. package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +512 -0
  36. package/lib/dataflow/propagation/install/mongoose/schema-string.test.js +160 -0
  37. package/lib/dataflow/propagation/install/mustache-escape.test.js +62 -0
  38. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +74 -0
  39. package/lib/dataflow/propagation/install/parse-int.test.js +48 -0
  40. package/lib/dataflow/propagation/install/path/basename.test.js +143 -0
  41. package/lib/dataflow/propagation/install/path/dirname.test.js +167 -0
  42. package/lib/dataflow/propagation/install/path/extname.test.js +141 -0
  43. package/lib/dataflow/propagation/install/path/format.test.js +250 -0
  44. package/lib/dataflow/propagation/install/path/index.test.js +45 -0
  45. package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +485 -0
  46. package/lib/dataflow/propagation/install/path/normalize.test.js +176 -0
  47. package/lib/dataflow/propagation/install/path/parse.test.js +238 -0
  48. package/lib/dataflow/propagation/install/path/relative.test.js +239 -0
  49. package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +158 -0
  50. package/lib/dataflow/propagation/install/pug/index.test.js +55 -0
  51. package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +69 -0
  52. package/lib/dataflow/propagation/install/querystring/escape.test.js +63 -0
  53. package/lib/dataflow/propagation/install/querystring/index.test.js +40 -0
  54. package/lib/dataflow/propagation/install/querystring/parse.test.js +272 -0
  55. package/lib/dataflow/propagation/install/querystring/stringify.test.js +301 -0
  56. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +281 -0
  57. package/lib/dataflow/propagation/install/send.test.js +63 -0
  58. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +73 -0
  59. package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +130 -0
  60. package/lib/dataflow/propagation/install/sql-template-strings.test.js +100 -0
  61. package/lib/dataflow/propagation/install/string/concat.test.js +132 -0
  62. package/lib/dataflow/propagation/install/string/format-methods.test.js +61 -0
  63. package/lib/dataflow/propagation/install/string/html-methods.test.js +164 -0
  64. package/lib/dataflow/propagation/install/string/index.test.js +103 -0
  65. package/lib/dataflow/propagation/install/string/match-all.test.js +399 -0
  66. package/lib/dataflow/propagation/install/string/match.test.js +361 -0
  67. package/lib/dataflow/propagation/install/string/replace.test.js +588 -0
  68. package/lib/dataflow/propagation/install/string/slice.test.js +265 -0
  69. package/lib/dataflow/propagation/install/string/split.test.js +500 -0
  70. package/lib/dataflow/propagation/install/string/substring.test.js +238 -0
  71. package/lib/dataflow/propagation/install/string/trim.test.js +122 -0
  72. package/lib/dataflow/propagation/install/unescape.test.js +78 -0
  73. package/lib/dataflow/propagation/install/url/domain-parsers.test.js +63 -0
  74. package/lib/dataflow/propagation/install/url/parse.test.js +391 -0
  75. package/lib/dataflow/propagation/install/url/searchParams.test.js +538 -0
  76. package/lib/dataflow/propagation/install/url/url.test.js +466 -0
  77. package/lib/dataflow/propagation/install/util-format.test.js +336 -0
  78. package/lib/dataflow/propagation/install/validator/hooks.test.js +211 -0
  79. package/lib/dataflow/sinks/index.test.js +78 -0
  80. package/lib/dataflow/sinks/install/child-process.test.js +338 -0
  81. package/lib/dataflow/sinks/install/eval.test.js +95 -0
  82. package/lib/dataflow/sinks/install/express/index.test.js +33 -0
  83. package/lib/dataflow/sinks/install/express/reflected-xss.js +55 -57
  84. package/lib/dataflow/sinks/install/express/reflected-xss.test.js +109 -0
  85. package/lib/dataflow/sinks/install/express/unvalidated-redirect.test.js +144 -0
  86. package/lib/dataflow/sinks/install/fastify/index.test.js +32 -0
  87. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.test.js +130 -0
  88. package/lib/dataflow/sinks/install/fs.test.js +138 -0
  89. package/lib/dataflow/sinks/install/function.test.js +103 -0
  90. package/lib/dataflow/sinks/install/hapi/index.test.js +32 -0
  91. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.test.js +130 -0
  92. package/lib/dataflow/sinks/install/http/index.test.js +33 -0
  93. package/lib/dataflow/sinks/install/http/request.test.js +184 -0
  94. package/lib/dataflow/sinks/install/http/server-response.test.js +162 -0
  95. package/lib/dataflow/sinks/install/koa/index.test.js +32 -0
  96. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.test.js +200 -0
  97. package/lib/dataflow/sinks/install/libxmljs.test.js +158 -0
  98. package/lib/dataflow/sinks/install/marsdb.test.js +166 -0
  99. package/lib/dataflow/sinks/install/mongodb.test.js +621 -0
  100. package/lib/dataflow/sinks/install/mssql.test.js +136 -0
  101. package/lib/dataflow/sinks/install/mysql.test.js +233 -0
  102. package/lib/dataflow/sinks/install/node-serialize.test.js +85 -0
  103. package/lib/dataflow/sinks/install/postgres.test.js +158 -0
  104. package/lib/dataflow/sinks/install/restify.test.js +142 -0
  105. package/lib/dataflow/sinks/install/sequelize.test.js +100 -0
  106. package/lib/dataflow/sinks/install/sqlite3.test.js +118 -0
  107. package/lib/dataflow/sinks/install/vm.test.js +326 -0
  108. package/lib/dataflow/sources/handler.test.js +463 -0
  109. package/lib/dataflow/sources/index.test.js +58 -0
  110. package/lib/dataflow/sources/install/body-parser1.test.js +248 -0
  111. package/lib/dataflow/sources/install/busboy.test.js +152 -0
  112. package/lib/dataflow/sources/install/cookie-parser1.test.js +143 -0
  113. package/lib/dataflow/sources/install/express/params.test.js +105 -0
  114. package/lib/dataflow/sources/install/express/parsedUrl.test.js +65 -0
  115. package/lib/dataflow/sources/install/fastify/fastify.test.js +210 -0
  116. package/lib/dataflow/sources/install/fastify/index.test.js +33 -0
  117. package/lib/dataflow/sources/install/formidable1.test.js +119 -0
  118. package/lib/dataflow/sources/install/hapi/hapi.test.js +172 -0
  119. package/lib/dataflow/sources/install/hapi/index.test.js +33 -0
  120. package/lib/dataflow/sources/install/http.test.js +155 -0
  121. package/lib/dataflow/sources/install/koa/index.test.js +40 -0
  122. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +161 -0
  123. package/lib/dataflow/sources/install/koa/koa-multer.test.js +197 -0
  124. package/lib/dataflow/sources/install/koa/koa-routers.test.js +146 -0
  125. package/lib/dataflow/sources/install/koa/koa2.test.js +145 -0
  126. package/lib/dataflow/sources/install/multer1.test.js +145 -0
  127. package/lib/dataflow/sources/install/qs6.test.js +131 -0
  128. package/lib/dataflow/sources/install/querystring.test.js +82 -0
  129. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +88 -0
  130. package/lib/dataflow/sources/install/restify/index.test.js +38 -0
  131. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +144 -0
  132. package/lib/dataflow/sources/install/restify/router.test.js +83 -0
  133. package/lib/dataflow/tag-utils-complete.test.js +27 -0
  134. package/lib/dataflow/tag-utils.test.js +192 -0
  135. package/lib/dataflow/tracker.test.js +216 -0
  136. package/lib/dataflow/utils/is-safe-content-type.test.js +16 -0
  137. package/lib/dataflow/utils/is-vulnerable.test.js +115 -0
  138. package/lib/event-factory.test.js +321 -0
  139. package/lib/get-policy.test.js +194 -0
  140. package/lib/get-source-context.test.js +108 -0
  141. package/lib/index.test.js +41 -0
  142. package/lib/make-source-context.test.js +50 -0
  143. package/lib/response-scanning/handlers/index.test.js +425 -0
  144. package/lib/response-scanning/handlers/utils.test.js +391 -0
  145. package/lib/response-scanning/index.test.js +41 -0
  146. package/lib/response-scanning/install/http.test.js +175 -0
  147. package/lib/rule-scopes.test.js +27 -0
  148. package/lib/session-configuration/handlers.test.js +84 -0
  149. package/lib/session-configuration/index.test.js +36 -0
  150. package/lib/session-configuration/install/express-session.test.js +220 -0
  151. package/lib/session-configuration/install/fastify-cookie.test.js +65 -0
  152. package/lib/session-configuration/install/hapi.test.js +269 -0
  153. package/lib/session-configuration/install/koa.test.js +92 -0
  154. package/package.json +2 -2
@@ -0,0 +1,621 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const sinon = require('sinon');
5
+ const {
6
+ DataflowTag: { UNTRUSTED, LIMITED_CHARS },
7
+ Rule,
8
+ } = require('@contrast/common');
9
+ const { initAssessFixture } = require('@contrast/test/fixtures');
10
+
11
+ describe('assess dataflow sinks mongodb-v4', function () {
12
+ let core, simulateRequestScope, trackString, reportFindings, instr, getQueryVulnInfo, getAggregateVulnInfo, getMapReduceVulnInfo, getGroupVulnInfo;
13
+
14
+ class Db {
15
+ command() { }
16
+ eval() { }
17
+ aggregate() { }
18
+ }
19
+
20
+ class Collection {
21
+ find() { }
22
+ findOne() { }
23
+ findAndModify() { }
24
+ findOneAndDelete() { }
25
+ findOneAndReplace() { }
26
+ findOneAndUpdate() { }
27
+ remove() { }
28
+ removeOne() { }
29
+ replaceOne() { }
30
+ removeMany() { }
31
+ rename() { }
32
+ update() { }
33
+ updateOne() { }
34
+ updateMany() { }
35
+ deleteOne() { }
36
+ deleteMany() { }
37
+ aggregate() { }
38
+ mapReduce() { }
39
+ group() { }
40
+ }
41
+
42
+ beforeEach(function () {
43
+ ({ core, simulateRequestScope, trackString } = initAssessFixture());
44
+ reportFindings = sinon.stub(core.assess.dataflow.sinks, 'reportFindings');
45
+ core.depHooks.resolve
46
+ .withArgs({ name: 'mongodb' })
47
+ .yields({ Collection, Db });
48
+
49
+ // while the Assess fixture will have already composed the module, we re-require (which
50
+ // just reassigns the component) so we're able to inject the spied-upon functions above
51
+ instr = require('./mongodb')(core);
52
+ getQueryVulnInfo = sinon.spy(instr, 'getQueryVulnerabilityInfo');
53
+ getAggregateVulnInfo = sinon.spy(instr, 'getAggregateVulnerabilityInfo');
54
+ getMapReduceVulnInfo = sinon.spy(instr, 'getMapReduceVulnerabilityInfo');
55
+ getGroupVulnInfo = sinon.spy(instr, 'getGroupVulnerabilityInfo');
56
+ instr.install();
57
+ });
58
+
59
+ [
60
+ { subject: Collection, method: 'find' },
61
+ { subject: Collection, method: 'findOne' },
62
+ { subject: Collection, method: 'findAndModify' },
63
+ { subject: Collection, method: 'findOneAndDelete' },
64
+ { subject: Collection, method: 'findOneAndReplace' },
65
+ { subject: Collection, method: 'findOneAndUpdate' },
66
+ { subject: Collection, method: 'remove' },
67
+ { subject: Collection, method: 'replaceOne' },
68
+ { subject: Collection, method: 'update' },
69
+ { subject: Collection, method: 'updateOne' },
70
+ { subject: Collection, method: 'updateMany' },
71
+ { subject: Collection, method: 'deleteOne' },
72
+ { subject: Collection, method: 'deleteMany' },
73
+ { subject: Db, method: 'command' },
74
+ ].forEach(({ subject, method }) => {
75
+ describe(`${subject.name}.prototype.${method}()`, function () {
76
+ it('skips instrumentation if assess store is missing', function () {
77
+ const tracked = trackString('foo');
78
+ (new subject())[method]({ tracked });
79
+ expect(getQueryVulnInfo).not.to.have.been.called;
80
+ });
81
+
82
+ it('skips instrumentation if the argument is not a string', function () {
83
+ simulateRequestScope(function () {
84
+ (new subject())[method]({});
85
+ expect(reportFindings).not.to.have.been.called;
86
+ });
87
+ });
88
+
89
+ it('does not report when query argument has no tracked values', function () {
90
+ simulateRequestScope(function () {
91
+ (new subject())[method]({ foo: 'bar' });
92
+ expect(reportFindings).not.to.have.been.called;
93
+ });
94
+ });
95
+
96
+ it('reports safe event when the input is sanitized', function () {
97
+ simulateRequestScope(function () {
98
+ const tracked = trackString('foo', {
99
+ tags: {
100
+ [LIMITED_CHARS]: [0, 2],
101
+ [UNTRUSTED]: [0, 2],
102
+ },
103
+ });
104
+ const strInfo = core.assess.dataflow.tracker.getData(tracked);
105
+ (new subject())[method]({ tracked });
106
+ expect(getQueryVulnInfo.getCall(0).returnValue).to.deep.equal({
107
+ vulnInfo: null,
108
+ reportSafe: [{ path: ['tracked'], strInfo }]
109
+ });
110
+ expect(reportFindings).not.to.have.been.called;
111
+ });
112
+ });
113
+
114
+ it('reports a nosql-injection', function () {
115
+ simulateRequestScope(function () {
116
+ const tracked = trackString('foo');
117
+ (new subject())[method]({ tracked });
118
+
119
+ expect(reportFindings).to.have.been.calledWithMatch({
120
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
121
+ sinkEvent: {
122
+ args: [{ value: "{...'tracked':'foo'...}", tracked: true }],
123
+ name: sinon.match(method),
124
+ context: `${subject.name === 'Collection' ? 'db.collection' : 'db'}.${method}({...'tracked':'foo'...})`,
125
+ moduleName: 'mongodb',
126
+ methodName: `${subject.name}.prototype.${method}`,
127
+ object: {
128
+ value: sinon.match(subject.name),
129
+ tracked: false,
130
+ },
131
+ result: { value: 'Promise', tracked: false },
132
+ source: 'P0',
133
+ tags: { [UNTRUSTED]: [15, 17] },
134
+ },
135
+ });
136
+ });
137
+ });
138
+ });
139
+ });
140
+
141
+ describe('Db.prototype.aggregate and Collection.prototype.aggregate', function () {
142
+ it('report nosql-injection from user input in the $function operator', function () {
143
+ simulateRequestScope(function () {
144
+ const tracked = trackString('foo');
145
+ (new Collection()).aggregate([
146
+ {
147
+ $function: {
148
+ body: tracked
149
+ }
150
+ }
151
+ ]);
152
+ (new Db()).aggregate([
153
+ {
154
+ $function: {
155
+ body: tracked
156
+ }
157
+ }
158
+ ]);
159
+
160
+ expect(reportFindings).to.have.been.calledTwice;
161
+ expect(reportFindings).to.have.been.calledWithMatch({
162
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
163
+ sinkEvent: {
164
+ args: [{ value: "[...'body':'foo'...]", tracked: true }],
165
+ name: 'mongodb.Collection.prototype.aggregate',
166
+ object: {
167
+ value: sinon.match('Collection'),
168
+ tracked: false,
169
+ },
170
+ result: { value: 'Promise', tracked: false },
171
+ source: 'P0',
172
+ tags: { [UNTRUSTED]: [12, 14] },
173
+ },
174
+ });
175
+
176
+ expect(reportFindings).to.have.been.calledWithMatch({
177
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
178
+ sinkEvent: {
179
+ args: [{ value: "[...'body':'foo'...]", tracked: true }],
180
+ name: sinon.match('aggregate'),
181
+ object: {
182
+ value: sinon.match('Db'),
183
+ tracked: false,
184
+ },
185
+ result: { value: 'Promise', tracked: false },
186
+ source: 'P0',
187
+ tags: { [UNTRUSTED]: [12, 14] },
188
+ },
189
+ });
190
+ });
191
+ });
192
+
193
+ [
194
+ { prop: 'init', tagRange: [12, 14] },
195
+ { prop: 'merge', tagRange: [13, 15] },
196
+ { prop: 'accumulate', tagRange: [18, 20] },
197
+ { prop: 'finalize', tagRange: [16, 18] }
198
+ ].forEach(({ prop, tagRange }) => {
199
+ it(`report nosql-injection from user input in the $accumulator operator and ${prop} property`, function () {
200
+ simulateRequestScope(function () {
201
+ const tracked = trackString('foo');
202
+ (new Collection()).aggregate([
203
+ {
204
+ $accumulator: {
205
+ [prop]: tracked
206
+ }
207
+ }
208
+ ]);
209
+ expect(reportFindings).to.have.been.calledWithMatch({
210
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
211
+ sinkEvent: {
212
+ args: [
213
+ { value: `[...'${prop}':'foo'...]`, tracked: true }
214
+ ],
215
+ name: 'mongodb.Collection.prototype.aggregate',
216
+ object: {
217
+ value: sinon.match('Collection'),
218
+ tracked: false,
219
+ },
220
+ result: { value: 'Promise', tracked: false },
221
+ source: 'P0',
222
+ tags: { [UNTRUSTED]: tagRange },
223
+ },
224
+ });
225
+ });
226
+ });
227
+ });
228
+
229
+ it('reports safe event when the input is sanitized', function () {
230
+ simulateRequestScope(function () {
231
+ const tracked = trackString('foo', {
232
+ tags: {
233
+ [LIMITED_CHARS]: [0, 2],
234
+ [UNTRUSTED]: [0, 2],
235
+ },
236
+ });
237
+ const strInfo = core.assess.dataflow.tracker.getData(tracked);
238
+ (new Collection()).aggregate({
239
+ $function: {
240
+ body: tracked
241
+ }
242
+ });
243
+ expect(getAggregateVulnInfo.getCall(0).returnValue).to.deep.equal({
244
+ vulnInfo: null,
245
+ reportSafe: [{ path: ['$function', 'body'], strInfo }]
246
+ });
247
+ expect(reportFindings).not.to.have.been.called;
248
+ });
249
+ });
250
+ });
251
+
252
+ describe('Collection.prototype.mapReduce', function () {
253
+ it('report nosql-injection from user input in the first 2 arguments of `mapReduce` method', function () {
254
+ const options = { options: 'untracked' };
255
+ simulateRequestScope(function () {
256
+ const tracked = trackString('foo');
257
+ (new Collection()).mapReduce(
258
+ tracked,
259
+ 'untracked',
260
+ options
261
+ );
262
+ (new Collection()).mapReduce(
263
+ 'untracked',
264
+ tracked,
265
+ options
266
+ );
267
+
268
+ expect(reportFindings).to.have.been.calledTwice;
269
+ expect(reportFindings).to.have.been.calledWithMatch({
270
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
271
+ sinkEvent: {
272
+ args: [
273
+ { value: 'foo', tracked: true },
274
+ { value: 'String', tracked: false },
275
+ { value: 'Object', tracked: false },
276
+
277
+ ],
278
+ name: 'mongodb.Collection.prototype.mapReduce',
279
+ object: {
280
+ value: sinon.match('Collection'),
281
+ tracked: false,
282
+ },
283
+ result: { value: 'Promise', tracked: false },
284
+ source: 'P0',
285
+ tags: { [UNTRUSTED]: [0, 2] },
286
+ },
287
+ });
288
+ expect(reportFindings).to.have.been.calledWithMatch({
289
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
290
+ sinkEvent: {
291
+ args: [
292
+ { value: 'String', tracked: false },
293
+ { value: 'foo', tracked: true },
294
+ { value: 'Object', tracked: false },
295
+
296
+ ],
297
+ name: 'mongodb.Collection.prototype.mapReduce',
298
+ object: {
299
+ value: sinon.match('Collection'),
300
+ tracked: false,
301
+ },
302
+ result: { value: 'Promise', tracked: false },
303
+ source: 'P1',
304
+ tags: { [UNTRUSTED]: [0, 2] },
305
+ },
306
+ });
307
+ });
308
+ });
309
+
310
+ it('report nosql-injection from user input in the third argument of `mapReduce` method', function () {
311
+ simulateRequestScope(function () {
312
+ const tracked = trackString('foo');
313
+ const options1 = { query: tracked };
314
+ const options2 = { finalize: tracked };
315
+ (new Collection()).mapReduce(
316
+ 'untracked',
317
+ 'untracked',
318
+ options1
319
+ );
320
+ (new Collection()).mapReduce(
321
+ 'untracked',
322
+ 'untracked',
323
+ options2
324
+ );
325
+
326
+ expect(reportFindings).to.have.been.calledTwice;
327
+ expect(reportFindings).to.have.been.calledWithMatch({
328
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
329
+ sinkEvent: {
330
+ args: [
331
+ { value: 'String', tracked: false },
332
+ { value: 'String', tracked: false },
333
+ { value: '{...\'query\':\'foo\'...}', tracked: true },
334
+ ],
335
+ name: 'mongodb.Collection.prototype.mapReduce',
336
+ object: {
337
+ value: sinon.match('Collection'),
338
+ tracked: false,
339
+ },
340
+ result: { value: 'Promise', tracked: false },
341
+ source: 'P2',
342
+ tags: { [UNTRUSTED]: [13, 15] },
343
+ },
344
+ });
345
+ expect(reportFindings).to.have.been.calledWithMatch({
346
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
347
+ sinkEvent: {
348
+ args: [
349
+ { value: 'String', tracked: false },
350
+ { value: 'String', tracked: false },
351
+ { value: '{...\'query\':\'foo\'...}', tracked: true },
352
+
353
+ ],
354
+ name: 'mongodb.Collection.prototype.mapReduce',
355
+ object: {
356
+ value: sinon.match('Collection'),
357
+ tracked: false,
358
+ },
359
+ result: { value: 'Promise', tracked: false },
360
+ source: 'P2',
361
+ tags: { [UNTRUSTED]: [13, 15] },
362
+ },
363
+ });
364
+ });
365
+ });
366
+
367
+ it('reports safe event when the input is sanitized', function () {
368
+ simulateRequestScope(function () {
369
+ const tracked = trackString('foo', {
370
+ tags: {
371
+ [LIMITED_CHARS]: [0, 2],
372
+ [UNTRUSTED]: [0, 2],
373
+ },
374
+ });
375
+ const strInfo = core.assess.dataflow.tracker.getData(tracked);
376
+ (new Collection()).mapReduce(
377
+ tracked, // call 0
378
+ 'untracked', // call 1
379
+ { some: 'options' } // call 2
380
+ );
381
+ (new Collection()).mapReduce(
382
+ tracked, // call 3
383
+ 'untracked', // call 4
384
+ { query: tracked } // call 5
385
+ );
386
+
387
+ expect(getMapReduceVulnInfo.getCall(0).returnValue).to.deep.equal({
388
+ vulnInfo: null,
389
+ reportSafe: [{ strInfo }]
390
+ });
391
+ expect(getMapReduceVulnInfo.getCall(5).returnValue).to.deep.equal({
392
+ vulnInfo: null,
393
+ reportSafe: [{ path: ['query'], strInfo }]
394
+ });
395
+ expect(reportFindings).not.to.have.been.called;
396
+ });
397
+ });
398
+ });
399
+
400
+ describe('Collection.prototype.group', function () {
401
+ it('report nosql-injection from user input in the first and the fourth argument of `group` method', function () {
402
+ simulateRequestScope(function () {
403
+ const tracked = trackString('foo');
404
+ const condition = { some: 'condition' };
405
+ const initState = { init: 'state' };
406
+ (new Collection()).group(
407
+ tracked,
408
+ condition,
409
+ initState,
410
+ 'reduceFn'
411
+ );
412
+ (new Collection()).group(
413
+ 'someKeys',
414
+ condition,
415
+ initState,
416
+ tracked
417
+ );
418
+
419
+ expect(reportFindings).to.have.been.calledTwice;
420
+ expect(reportFindings).to.have.been.calledWithMatch({
421
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
422
+ sinkEvent: {
423
+ args: [
424
+ { value: 'foo', tracked: true },
425
+ { value: 'Object', tracked: false },
426
+ { value: 'Object', tracked: false },
427
+ { value: 'String', tracked: false },
428
+
429
+ ],
430
+ name: 'mongodb.Collection.prototype.group',
431
+ object: {
432
+ value: sinon.match('Collection'),
433
+ tracked: false,
434
+ },
435
+ result: { value: 'Promise', tracked: false },
436
+ source: 'P0',
437
+ tags: { [UNTRUSTED]: [0, 2] },
438
+ },
439
+ });
440
+ expect(reportFindings).to.have.been.calledWithMatch({
441
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
442
+ sinkEvent: {
443
+ args: [
444
+ { value: 'String', tracked: false },
445
+ { value: 'Object', tracked: false },
446
+ { value: 'Object', tracked: false },
447
+ { value: 'foo', tracked: true },
448
+ ],
449
+ name: 'mongodb.Collection.prototype.group',
450
+ object: {
451
+ value: sinon.match('Collection'),
452
+ tracked: false,
453
+ },
454
+ result: { value: 'Promise', tracked: false },
455
+ source: 'P3',
456
+ tags: { [UNTRUSTED]: [0, 2] },
457
+ },
458
+ });
459
+ });
460
+ });
461
+
462
+ it('report nosql-injection from user input in the second argument of `group` method', function () {
463
+ simulateRequestScope(function () {
464
+ const tracked = trackString('foo');
465
+ const condition = { condition: tracked };
466
+ const initState = { init: 'state' };
467
+ (new Collection()).group(
468
+ 'someKeys',
469
+ condition,
470
+ initState,
471
+ 'reduceFn'
472
+ );
473
+
474
+ expect(reportFindings).to.have.been.calledWithMatch({
475
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
476
+ sinkEvent: {
477
+ args: [
478
+ { value: 'String', tracked: false },
479
+ { value: '{...\'condition\':\'foo\'...}', tracked: true },
480
+ { value: 'Object', tracked: false },
481
+ { value: 'String', tracked: false },
482
+ ],
483
+ name: 'mongodb.Collection.prototype.group',
484
+ object: {
485
+ value: sinon.match('Collection'),
486
+ tracked: false,
487
+ },
488
+ result: { value: 'Promise', tracked: false },
489
+ source: 'P1',
490
+ tags: { [UNTRUSTED]: [17, 19] },
491
+ },
492
+ });
493
+ });
494
+ });
495
+
496
+ it('reports safe event when the input is sanitized', function () {
497
+ simulateRequestScope(function () {
498
+ const tracked = trackString('foo', {
499
+ tags: {
500
+ [LIMITED_CHARS]: [0, 2],
501
+ [UNTRUSTED]: [0, 2],
502
+ },
503
+ });
504
+ const strInfo = core.assess.dataflow.tracker.getData(tracked);
505
+ (new Collection()).group(
506
+ tracked, // call 0
507
+ { some: 'condition' }, // call 1
508
+ { init: 'state' },
509
+ 'reduceFn' // call 2
510
+ );
511
+ (new Collection()).group(
512
+ 'untracked', // call 3
513
+ { field: tracked }, // call 4
514
+ { init: 'state' },
515
+ 'reduceFn'
516
+ );
517
+
518
+ expect(getGroupVulnInfo.getCall(0).returnValue).to.deep.equal({
519
+ vulnInfo: null,
520
+ reportSafe: [{ strInfo }]
521
+ });
522
+ expect(getGroupVulnInfo.getCall(4).returnValue).to.deep.equal({
523
+ vulnInfo: null,
524
+ reportSafe: [{ path: ['field'], strInfo }]
525
+ });
526
+ expect(reportFindings).not.to.have.been.called;
527
+ });
528
+ });
529
+ });
530
+
531
+ describe('Db.prototype.eval() and other edge cases', function () {
532
+ it('skips patching if the method is missing', function () {
533
+ class Collection {
534
+ findOne() { }
535
+ }
536
+ class Db {
537
+ command() { }
538
+ }
539
+ core.depHooks.resolve
540
+ .withArgs({ name: 'mongodb' })
541
+ .yields({ Collection, Db }, 'v5.x.x');
542
+
543
+ const instr = require('./mongodb')(core);
544
+ instr.install();
545
+ expect(core.logger.trace).to.have.been.calledWith(
546
+ { version: 'v5.x.x' },
547
+ 'method %s not found - skipping instrumentation',
548
+ 'mongodb.Db.prototype.eval',
549
+ );
550
+ expect(core.logger.trace).to.have.been.calledWith(
551
+ { version: 'v5.x.x' },
552
+ 'method %s not found - skipping instrumentation',
553
+ 'mongodb.Collection.prototype.findOneAndDelete',
554
+ );
555
+ });
556
+
557
+ it('logs an error if such occurs during the evaluation', function () {
558
+ const err = new Error('test');
559
+
560
+ reportFindings.throws(err);
561
+
562
+ simulateRequestScope(function () {
563
+ const tracked = trackString('foo');
564
+ (new Collection()).find({ tracked });
565
+ (new Db()).command({ tracked });
566
+
567
+ expect(core.logger.error).to.have.been.calledWith(
568
+ { err, funcKey: 'assess-dataflow-sink:mongodb.Collection.prototype.find' },
569
+ 'assess sink analysis failed',
570
+ );
571
+ expect(core.logger.error).to.have.been.calledWith(
572
+ { err, funcKey: 'assess-dataflow-sink:mongodb.Db.prototype.command' },
573
+ 'assess sink analysis failed',
574
+ );
575
+ });
576
+ });
577
+
578
+ it('reports a nosql-injection from the eval methhod', function () {
579
+ simulateRequestScope(function () {
580
+ const tracked = trackString('foo');
581
+ (new Db()).eval(tracked);
582
+
583
+ expect(reportFindings).to.have.been.calledWithMatch({
584
+ ruleId: Rule.NOSQL_INJECTION_MONGO,
585
+ sinkEvent: {
586
+ args: [{ value: 'foo', tracked: true }],
587
+ name: 'mongodb.Db.prototype.eval',
588
+ moduleName: 'mongodb',
589
+ methodName: 'Db.prototype.eval',
590
+ context: 'db.eval(\'foo\')',
591
+ object: {
592
+ value: 'mongodb.Db',
593
+ tracked: false,
594
+ },
595
+ result: { value: 'Promise', tracked: false },
596
+ source: 'P0',
597
+ tags: { [UNTRUSTED]: [0, 2] },
598
+ },
599
+ });
600
+ });
601
+ });
602
+
603
+ it('reports safe event when the input is sanitized', function () {
604
+ simulateRequestScope(function () {
605
+ const tracked = trackString('foo', {
606
+ tags: {
607
+ [LIMITED_CHARS]: [0, 2],
608
+ [UNTRUSTED]: [0, 2],
609
+ },
610
+ });
611
+ const strInfo = core.assess.dataflow.tracker.getData(tracked);
612
+ (new Db()).eval(tracked);
613
+ expect(getQueryVulnInfo.getCall(0).returnValue).to.deep.equal({
614
+ vulnInfo: null,
615
+ reportSafe: [{ strInfo }]
616
+ });
617
+ expect(reportFindings).not.to.have.been.called;
618
+ });
619
+ });
620
+ });
621
+ });