@contrast/protect 1.8.0 → 1.9.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.
@@ -14,17 +14,14 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
-
18
- const { patchType } = require('../constants');
17
+ const moduleName = 'mongodb';
19
18
  const semver = require('semver');
19
+ const { patchType } = require('../constants');
20
20
 
21
21
  module.exports = function (core) {
22
22
  const {
23
- depHooks,
24
- patcher,
25
- captureStacktrace,
26
- protect,
27
- protect: { inputTracing },
23
+ protect: { getSourceContext, inputTracing },
24
+ instrumentation: { instrument }
28
25
  } = core;
29
26
 
30
27
  function getCursorQueryData(args, version) {
@@ -57,172 +54,163 @@ module.exports = function (core) {
57
54
  return op.q;
58
55
  }
59
56
 
60
- function hookV3CommandAndCursor(obj, method, patchName, version) {
61
- patcher.patch(obj, method, {
62
- name: patchName,
63
- patchType,
64
- pre: ({ args, hooked, name, orig }) => {
65
- const value = getCursorQueryData(args, version);
66
- const sourceContext = protect.getSourceContext(patchName);
67
-
68
- if (!sourceContext || !value) return;
69
-
70
- const sinkContext = captureStacktrace(
71
- { name, value },
72
- { constructorOpt: hooked, prependFrames: [orig] }
73
- );
74
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
75
- }
76
- });
57
+ function preHook(value, { name, hooked, orig }) {
58
+ const sourceContext = getSourceContext(name);
59
+
60
+ if (!sourceContext || !value) return;
61
+
62
+ const sinkContext = {
63
+ name,
64
+ value,
65
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
66
+ };
67
+ inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
77
68
  }
78
69
 
79
- function hookV3UpdateAndRemove(obj, method, patchName) {
80
- patcher.patch(obj, method, {
81
- name: patchName,
82
- patchType,
83
- pre: ({ args, hooked, name, orig }) => {
84
- const sourceContext = protect.getSourceContext(patchName);
85
-
86
- if (!sourceContext) return;
87
-
88
- const ops = Array.isArray(args[1])
89
- ? args[1]
90
- : [args[1]];
91
- for (const op of ops) {
92
- const value = op && getOpQueryData(op);
93
- if (value) {
94
- const sinkContext = captureStacktrace(
95
- { name, value },
96
- { constructorOpt: hooked, prependFrames: [orig] }
97
- );
98
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
99
- }
100
- }
101
- },
102
- });
70
+ function v4CollectionVal({ args, ...ctx }) {
71
+ const value = typeof args[0] == 'function' ? null : args[0];
72
+ preHook(value, ctx);
73
+ }
74
+
75
+ function v4DbVal({ args, ...ctx }) {
76
+ const value = args[0]?.filter;
77
+ preHook(value, ctx);
78
+ }
79
+
80
+ function v4CursorVal({ args, ...ctx }) {
81
+ const value = args[2];
82
+ preHook(value, ctx);
83
+ }
84
+
85
+ function v3CursorVal({ args, ...ctx }, version) {
86
+ const value = getCursorQueryData(args, version);
87
+ preHook(value, ctx);
88
+ }
89
+
90
+ function v3DbVal({ args, ...ctx }) {
91
+ const value = args[0];
92
+ preHook(value, ctx);
93
+ }
94
+
95
+ function v3TopologyVal({ args, ...ctx }) {
96
+ const ops = Array.isArray(args[1]) ? args[1] : [args[1]];
97
+ for (const op of ops) {
98
+ const value = op && getOpQueryData(op);
99
+ if (value) {
100
+ preHook(value, ctx);
101
+ }
102
+ }
103
103
  }
104
104
 
105
105
  function install() {
106
- const v4MethodsWithFilter = [
107
- 'updateOne',
108
- 'replaceOne',
109
- 'updateMany',
110
- 'deleteOne',
111
- 'deleteMany',
112
- 'findOneAndDelete',
113
- 'findOneAndReplace',
114
- 'findOneAndUpdate',
115
- 'countDocuments',
116
- 'count',
117
- 'distinct',
118
- ];
119
-
120
- depHooks.resolve(
106
+ [
121
107
  {
122
- name: 'mongodb', version: '>=4.0.0'
108
+ moduleName,
109
+ version: '>=4.0.0',
110
+ patchObjects: [
111
+ {
112
+ name: 'Collection.prototype',
113
+ methods: [
114
+ 'updateOne',
115
+ 'replaceOne',
116
+ 'updateMany',
117
+ 'deleteOne',
118
+ 'deleteMany',
119
+ 'findOneAndDelete',
120
+ 'findOneAndReplace',
121
+ 'findOneAndUpdate',
122
+ 'countDocuments',
123
+ 'count',
124
+ 'distinct',
125
+ ],
126
+ patchType,
127
+ preHookFn: v4CollectionVal
128
+ },
129
+ {
130
+ name: 'Db.prototype',
131
+ methods: ['command'],
132
+ patchType,
133
+ preHookFn: v4DbVal
134
+ }
135
+ ]
123
136
  },
124
- (mongodb) => {
125
- v4MethodsWithFilter.forEach((method) => {
126
- patcher.patch(mongodb.Collection.prototype, method, {
127
- name: `mongodb.Collection.prototype.${method}`,
137
+ {
138
+ moduleName,
139
+ version: '>=4.0.0',
140
+ file: 'lib/cursor/find_cursor',
141
+ patchObjects: [
142
+ {
143
+ methods: ['FindCursor'],
128
144
  patchType,
129
- pre: ({ args, hooked, name, orig }) => {
130
- const value = typeof args[0] == 'function' ? null : args[0];
131
- const sourceContext = protect.getSourceContext(`mongodb.Collection.prototype.${method}`);
132
-
133
- if (!sourceContext || !value) return;
134
-
135
- const sinkContext = captureStacktrace(
136
- { name, value },
137
- { constructorOpt: hooked, prependFrames: [orig] }
138
- );
139
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
140
- },
141
- });
142
- });
143
-
144
- patcher.patch(mongodb.Db.prototype, 'command', {
145
- name: 'mongodb.Db.prototype.command',
146
- patchType,
147
- pre: ({ args, hooked, name, orig }) => {
148
- const value = args[0]?.filter;
149
- const sourceContext = protect.getSourceContext('mongodb.Collection.prototype.command');
150
-
151
- if (!sourceContext || !value) return;
152
-
153
- const sinkContext = captureStacktrace(
154
- { name, value },
155
- { constructorOpt: hooked, prependFrames: [orig] }
156
- );
157
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
145
+ preHookFn: v4CursorVal
158
146
  }
159
- });
160
- });
161
-
162
- depHooks.resolve(
147
+ ]
148
+ },
163
149
  {
164
- name: 'mongodb', version: '<4.0.0'
165
- }, (mongodb, { version }) => {
166
- hookV3CommandAndCursor(mongodb.CoreServer.prototype, 'cursor', 'mongodb.CoreServer.prototype.cursor', version);
167
-
168
- patcher.patch(mongodb.Db.prototype, 'eval', {
169
- name: 'mongodb.Db.prototype.eval',
170
- patchType,
171
- pre: ({ args, hooked, name, orig }) => {
172
- const value = args[0];
173
- const sourceContext = protect.getSourceContext('mongodb.Db.prototype.eval');
174
-
175
- if (!sourceContext || !value) return;
176
-
177
- const sinkContext = captureStacktrace(
178
- { name, value },
179
- { constructorOpt: hooked, prependFrames: [orig] }
180
- );
181
-
182
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
150
+ moduleName,
151
+ version: '<4.0.0',
152
+ patchObjects: [
153
+ {
154
+ name: 'CoreServer.prototype',
155
+ methods: ['cursor'],
156
+ patchType,
157
+ preHookFn: v3CursorVal
158
+ },
159
+ {
160
+ name: 'Db.prototype',
161
+ methods: ['eval'],
162
+ patchType,
163
+ preHookFn: v3DbVal
183
164
  }
184
- });
185
- });
186
-
187
- depHooks.resolve({ name: 'mongodb', file: 'lib/cursor/find_cursor', version: '>=4.0.0' }, (cursor) => patcher.patch(cursor, 'FindCursor', {
188
- name: 'mongodb.FindCursor',
189
- patchType,
190
- pre: ({ args, hooked, name, orig }) => {
191
- const value = args[2];
192
- const sourceContext = protect.getSourceContext('mongodb.FindCursor');
193
-
194
- if (!sourceContext || !value) return;
195
-
196
- const sinkContext = captureStacktrace(
197
- { name, value },
198
- { constructorOpt: hooked, prependFrames: [orig] }
199
- );
200
- inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
165
+ ]
166
+ },
167
+ {
168
+ moduleName,
169
+ file: 'lib/topologies/topology_base.js',
170
+ version: '<4.0.0',
171
+ patchObjects: [
172
+ {
173
+ name: 'TopologyBase.prototype',
174
+ methods: ['update', 'remove'],
175
+ patchType,
176
+ preHookFn: v3TopologyVal
177
+ },
178
+ {
179
+ name: 'TopologyBase.prototype',
180
+ methods: ['command'],
181
+ patchType,
182
+ preHookFn: v3CursorVal
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ moduleName,
188
+ file: 'lib/topologies/native_topology.js',
189
+ version: '<4.0.0',
190
+ patchObjects: [
191
+ {
192
+ name: 'NativeTopology.prototype',
193
+ patchName: 'prototype',
194
+ methods: ['update', 'remove'],
195
+ patchType,
196
+ preHookFn: v3TopologyVal
197
+ },
198
+ {
199
+ name: 'NativeTopology.prototype',
200
+ patchName: 'prototype',
201
+ methods: ['command'],
202
+ patchType,
203
+ preHookFn: v3CursorVal
204
+ }
205
+ ]
201
206
  }
202
- }));
203
-
204
- const mongoDBTopologiesMethods = ['update', 'remove'];
205
-
206
- depHooks.resolve({
207
- name: 'mongodb',
208
- file: 'lib/topologies/topology_base.js',
209
- version: '<4.0.0'
210
- }, (tpl, { version }) => {
211
- mongoDBTopologiesMethods.forEach((method) => {
212
- hookV3UpdateAndRemove(tpl.TopologyBase.prototype, method, `mongodb.TopologyBase.prototype.${method}`);
213
- });
214
- hookV3CommandAndCursor(tpl.TopologyBase.prototype, 'command', 'mongodb.TopologyBase.prototype.command', version);
215
- });
216
-
217
- depHooks.resolve({
218
- name: 'mongodb',
219
- file: 'lib/topologies/native_topology.js',
220
- version: '<4.0.0'
221
- }, (NativeTopology, { version }) => {
222
- mongoDBTopologiesMethods.forEach((method) => {
223
- hookV3UpdateAndRemove(NativeTopology.prototype, method, `mongodb.NativeTopology.prototype.${method}`);
207
+ ].forEach(({ moduleName, file, version, patchObjects }) => {
208
+ instrument({
209
+ moduleName,
210
+ file,
211
+ version,
212
+ patchObjects
224
213
  });
225
- hookV3CommandAndCursor(NativeTopology.prototype, 'command', 'mongodb.NativeTopology.prototype.command', version);
226
214
  });
227
215
  }
228
216
  const mongodbInstr = (core.protect.inputTracing.mongodbInstrumentation = {
@@ -22,7 +22,6 @@ module.exports = function(core) {
22
22
  const {
23
23
  depHooks,
24
24
  patcher,
25
- captureStacktrace,
26
25
  protect,
27
26
  protect: { inputTracing }
28
27
  } = core;
@@ -56,10 +55,11 @@ module.exports = function(core) {
56
55
  const value = mysqlInstr.getValueFromArgs(args);
57
56
  if (!value) return;
58
57
 
59
- const sinkContext = captureStacktrace(
60
- { name, value },
61
- { constructorOpt: hooked, prependFrames: [orig] }
62
- );
58
+ const sinkContext = {
59
+ name,
60
+ value,
61
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
62
+ };
63
63
 
64
64
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
65
65
  }
@@ -22,7 +22,6 @@ module.exports = function(core) {
22
22
  const {
23
23
  depHooks,
24
24
  patcher,
25
- captureStacktrace,
26
25
  protect,
27
26
  protect: { inputTracing }
28
27
  } = core;
@@ -40,10 +39,11 @@ module.exports = function(core) {
40
39
  const value = getQueryFromArgs(args);
41
40
  if (!value) return;
42
41
 
43
- const sinkContext = captureStacktrace(
44
- { name, value },
45
- { constructorOpt: hooked, prependFrames: [orig] }
46
- );
42
+ const sinkContext = {
43
+ name,
44
+ value,
45
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
46
+ };
47
47
 
48
48
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
49
49
  }
@@ -23,7 +23,6 @@ module.exports = function(core) {
23
23
  scopes: { instrumentation },
24
24
  patcher,
25
25
  depHooks,
26
- captureStacktrace,
27
26
  protect,
28
27
  protect: { inputTracing }
29
28
  } = core;
@@ -49,10 +48,12 @@ module.exports = function(core) {
49
48
  const value = getQueryFromArgs(args);
50
49
  if (!value) return;
51
50
 
52
- const sinkContext = captureStacktrace(
53
- { name, value },
54
- { constructorOpt: hooked, prependFrames: [orig] }
55
- );
51
+ const sinkContext = {
52
+ name,
53
+ value,
54
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
55
+ };
56
+
56
57
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
57
58
  }
58
59
  });
@@ -23,7 +23,6 @@ module.exports = function(core) {
23
23
  scopes: { instrumentation },
24
24
  patcher,
25
25
  depHooks,
26
- captureStacktrace,
27
26
  protect,
28
27
  protect: { inputTracing }
29
28
  } = core;
@@ -44,10 +43,11 @@ module.exports = function(core) {
44
43
  const value = args[0];
45
44
  if (!value || !isString(value)) return;
46
45
 
47
- const sinkContext = captureStacktrace(
48
- { name, value },
49
- { constructorOpt: hooked, prependFrames: [orig] }
50
- );
46
+ const sinkContext = {
47
+ name,
48
+ value,
49
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
50
+ };
51
51
 
52
52
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
53
53
  }
@@ -23,7 +23,6 @@ module.exports = function(core) {
23
23
  scopes: { instrumentation },
24
24
  patcher,
25
25
  depHooks,
26
- captureStacktrace,
27
26
  protect,
28
27
  protect: { inputTracing }
29
28
  } = core;
@@ -46,10 +45,11 @@ module.exports = function(core) {
46
45
  const codeString = args[0];
47
46
  if (!codeString || !isString(codeString)) return;
48
47
 
49
- const sinkContext = captureStacktrace(
50
- { name, value: codeString },
51
- { constructorOpt: hooked, prependFrames: [orig] }
52
- );
48
+ const sinkContext = {
49
+ name,
50
+ value: codeString,
51
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
52
+ };
53
53
  inputTracing.ssjsInjection(sourceContext, sinkContext);
54
54
  }
55
55
  });
@@ -70,14 +70,16 @@ module.exports = function(core) {
70
70
 
71
71
  if ((!codeString || !isString(codeString)) && (!isNonEmptyObject(envObj))) return;
72
72
 
73
- const codeStringSinkContext = (codeString && isString(codeString)) ? captureStacktrace(
74
- { name: 'vm.runInNewContext', value: codeString },
75
- { constructorOpt: hooked, prependFrames: [orig] }
76
- ) : null;
77
- const envObjSinkContext = isNonEmptyObject(envObj) ? captureStacktrace(
78
- { name: 'vm.runInNewContext', value: envObj },
79
- { constructorOpt: hooked, prependFrames: [orig] }
80
- ) : null;
73
+ const codeStringSinkContext = (codeString && isString(codeString)) ? {
74
+ name: 'vm.runInNewContext',
75
+ value: codeString,
76
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] }
77
+ } : null;
78
+ const envObjSinkContext = isNonEmptyObject(envObj) ? {
79
+ name: 'vm.runInNewContext',
80
+ value: envObj,
81
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] }
82
+ } : null;
81
83
 
82
84
  codeStringSinkContext && inputTracing.ssjsInjection(sourceContext, codeStringSinkContext);
83
85
  envObjSinkContext && inputTracing.ssjsInjection(sourceContext, envObjSinkContext);
@@ -96,10 +98,11 @@ module.exports = function(core) {
96
98
  const envObj = args[0];
97
99
  if (!isNonEmptyObject(envObj)) return;
98
100
 
99
- const sinkContext = captureStacktrace(
100
- { name: 'vm.createContext', value: envObj },
101
- { constructorOpt: hooked, prependFrames: [orig] }
102
- );
101
+ const sinkContext = {
102
+ name: 'vm.createContext',
103
+ value: envObj,
104
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
105
+ };
103
106
  inputTracing.ssjsInjection(sourceContext, sinkContext);
104
107
  }
105
108
  });
@@ -116,10 +119,11 @@ module.exports = function(core) {
116
119
  const envObj = args[0];
117
120
  if (!isNonEmptyObject(envObj)) return;
118
121
 
119
- const sinkContext = captureStacktrace(
120
- { name: 'vm.Script.prototype.runInNewContext', value: envObj },
121
- { constructorOpt: hooked, prependFrames: [orig] }
122
- );
122
+ const sinkContext = {
123
+ name: 'vm.Script.prototype.runInNewContext',
124
+ value: envObj,
125
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
126
+ };
123
127
  inputTracing.ssjsInjection(sourceContext, sinkContext);
124
128
  }
125
129
  });
@@ -60,15 +60,6 @@ module.exports = function(core) {
60
60
  }
61
61
  }
62
62
 
63
- // if it can be determined that qs-type parsing is not being done then set
64
- // standardUrlParsing to true. if it is true, then the query params and bodies
65
- // that are form-url-encoded will be parsed by agent-lib and will not need to
66
- // be parsed separately.
67
- //
68
- // the code that scans the dependencies is probably the best place to make the
69
- // determination.
70
- const standardUrlParsing = false;
71
-
72
63
  // contains request data and information derived from request data. it's
73
64
  // possible for any derived information to be derived later, but doing
74
65
  // so here is typically better; it makes clear what information is used to
@@ -81,7 +72,6 @@ module.exports = function(core) {
81
72
  uriPath,
82
73
  queries,
83
74
  contentType,
84
- standardUrlParsing,
85
75
  };
86
76
 
87
77
  //
@@ -97,19 +87,12 @@ module.exports = function(core) {
97
87
  exclusions: [],
98
88
  virtualPatchesEvaluators: [],
99
89
 
100
- // maybe better as result, findings... but my bad naming choice is
101
- // past the point of return.
102
- findings: {
103
- trackRequest: false,
104
- securityException: undefined,
105
- // bodyType is set to a body type if handlers.parseRawBody() parsed it
106
- // successfully.
107
- bodyType: undefined,
108
- resultsMap: Object.create(null),
109
- hardeningResultsMap: Object.create(null),
110
- semanticResultsMap: Object.create(null),
111
- serverFeaturesResultsMap: Object.create(null)
112
- },
90
+ trackRequest: false,
91
+ securityException: undefined,
92
+ // bodyType is set to a body type if handlers.parseRawBody() parsed it
93
+ // successfully.
94
+ bodyType: undefined,
95
+ resultsMap: Object.create(null),
113
96
  };
114
97
 
115
98
  return protectStore;
package/lib/policy.js CHANGED
@@ -36,14 +36,18 @@ module.exports = function(core) {
36
36
  protect: { agentLib }
37
37
  } = core;
38
38
 
39
- const compiled = {
40
- url: [],
41
- querystring: [],
42
- header: [],
43
- body: [],
44
- cookie: [],
45
- parameter: [],
46
- };
39
+ function initCompiled() {
40
+ return {
41
+ url: [],
42
+ querystring: [],
43
+ header: [],
44
+ body: [],
45
+ cookie: [],
46
+ parameter: [],
47
+ };
48
+ }
49
+
50
+ let compiled = initCompiled();
47
51
 
48
52
  const policy = protect.policy = {
49
53
  exclusions: compiled
@@ -274,6 +278,7 @@ module.exports = function(core) {
274
278
  ].filter((exclusion) => exclusion.modes.includes('defend'));
275
279
 
276
280
  if (!exclusions.length) return;
281
+ compiled = initCompiled();
277
282
 
278
283
  for (const exclusionDtm of exclusions) {
279
284
  exclusionDtm.inputType = exclusionDtm.inputType || 'URL';