@contrast/protect 1.11.0 → 1.12.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.
@@ -39,7 +39,7 @@ module.exports = function(core) {
39
39
  hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
40
40
  const ruleId = 'untrusted-deserialization';
41
41
  const mode = sourceContext.policy[ruleId];
42
- const { name, value, stacktraceData } = sinkContext;
42
+ const { name, value, stacktraceOpts } = sinkContext;
43
43
 
44
44
  if (mode === 'off') return;
45
45
 
@@ -49,7 +49,7 @@ module.exports = function(core) {
49
49
  const blocked = BLOCKING_MODES.includes(mode);
50
50
  const results = getResults(sourceContext, ruleId);
51
51
 
52
- captureStacktrace(sinkContext, stacktraceData);
52
+ captureStacktrace(sinkContext, stacktraceOpts);
53
53
  results.push({
54
54
  value: sinkContext.value,
55
55
  blocked,
@@ -45,7 +45,7 @@ module.exports = function(core) {
45
45
  const sinkContext = {
46
46
  name: `${name}.${method}`,
47
47
  value,
48
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
48
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
49
49
  };
50
50
  hardening.handleUntrustedDeserialization(sourceContext, sinkContext);
51
51
  },
@@ -35,8 +35,8 @@ module.exports = function(core) {
35
35
  } = core;
36
36
 
37
37
  function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
38
- const { stacktraceData } = sinkContext;
39
- captureStacktrace(sinkContext, stacktraceData);
38
+ const { stacktraceOpts } = sinkContext;
39
+ captureStacktrace(sinkContext, stacktraceOpts);
40
40
  result.exploitMetadata.push({ sinkContext, findings });
41
41
 
42
42
  const mode = sourceContext.policy[ruleId];
@@ -142,17 +142,33 @@ module.exports = function(core) {
142
142
  }
143
143
  }
144
144
 
145
+
145
146
  if (stringInjectionResults) {
146
147
  let stringFindings = null;
147
148
 
148
149
  for (const result of stringInjectionResults) {
149
150
  if (typeof sinkContext.value === 'object') {
150
- traverseKeysAndValues(sinkContext.value, function(path, type, value) {
151
+ traverseKeysAndValues(sinkContext.value, function(path, type, value, obj) {
151
152
  if (type !== 'Key' && !agentLib.isMongoQueryType(value)) return;
152
- const cmdVal = sinkContext.value[value];
153
- stringFindings = handleStringValue(result, cmdVal?.['$function']?.body || cmdVal, agentLib);
154
- // halt traversal
155
- return true;
153
+
154
+ if (!stringFindings && type == 'Key' && value == '$accumulator') {
155
+ stringFindings =
156
+ handleStringValue(result, obj[value]?.['init'], agentLib) ||
157
+ handleStringValue(result, obj[value]?.['merge'], agentLib) ||
158
+ handleStringValue(result, obj[value]?.['finalize'], agentLib) ||
159
+ handleStringValue(result, obj[value]?.['accumulate'], agentLib);
160
+ }
161
+
162
+ if (!stringFindings && type == 'Key' && value == '$function') {
163
+ stringFindings =
164
+ handleStringValue(result, obj['$function']?.body, agentLib);
165
+ }
166
+
167
+ if (!stringFindings) {
168
+ stringFindings = handleStringValue(result, obj[value], agentLib);
169
+ }
170
+
171
+ if (stringFindings) return true;
156
172
  });
157
173
  } else if (typeof sinkContext.value === 'string') {
158
174
  stringFindings = handleStringValue(result, sinkContext.value, agentLib);
@@ -253,7 +269,10 @@ function getResultsByRuleId(ruleId, context) {
253
269
  if (context.policy[ruleId] === OFF) {
254
270
  return;
255
271
  }
256
- return context.resultsMap[ruleId];
272
+ // because agent-lib stores all nosql-injection results under nosql-injection-mongo
273
+ const resultsMapRuleId = ruleId === 'nosql-injection' ? 'nosql-injection-mongo' : ruleId;
274
+
275
+ return context.resultsMap[resultsMapRuleId];
257
276
  }
258
277
 
259
278
  function handleObjectValue(result, object) {
@@ -27,6 +27,7 @@ module.exports = function(core) {
27
27
  require('./install/child-process')(core);
28
28
  require('./install/fs')(core);
29
29
  require('./install/mongodb')(core);
30
+ require('./install/marsdb')(core);
30
31
  require('./install/mysql')(core);
31
32
  require('./install/postgres')(core);
32
33
  require('./install/sequelize')(core);
@@ -37,7 +37,7 @@ module.exports = function(core) {
37
37
  const sinkContext = {
38
38
  name,
39
39
  value,
40
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
40
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
41
41
  };
42
42
 
43
43
  inputTracing.handleCommandInjection(sourceContext, sinkContext);
@@ -47,7 +47,7 @@ module.exports = function(core) {
47
47
  const sinkContext = {
48
48
  name: 'eval',
49
49
  value,
50
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
50
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
51
51
  };
52
52
  inputTracing.ssjsInjection(sourceContext, sinkContext);
53
53
  }
@@ -103,7 +103,7 @@ module.exports = function(core) {
103
103
  const sinkContext = {
104
104
  name,
105
105
  value,
106
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
106
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
107
107
  };
108
108
  inputTracing.handlePathTraversal(sourceContext, sinkContext);
109
109
  core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
@@ -48,7 +48,7 @@ module.exports = function(core) {
48
48
  const sinkContext = {
49
49
  name: 'Function',
50
50
  value: fnBody,
51
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
51
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
52
52
  };
53
53
 
54
54
  inputTracing.ssjsInjection(sourceContext, sinkContext);
@@ -46,7 +46,7 @@ module.exports = function(core) {
46
46
  const sinkContext = {
47
47
  name,
48
48
  value,
49
- stacktraceData: { constructorOpt: data.hooked },
49
+ stacktraceOpts: { constructorOpt: data.hooked },
50
50
  };
51
51
  inputTracing.handleReflectedXss(sourceContext, sinkContext);
52
52
  }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+ const { patchType } = require('../constants');
18
+
19
+ module.exports = function (core) {
20
+ const {
21
+ depHooks,
22
+ patcher,
23
+ protect: { getSourceContext, inputTracing },
24
+ } = core;
25
+
26
+ const methods = ['find', 'findOne', 'update', 'insert', 'remove', 'ids', 'count'];
27
+
28
+ function getCursorQueryData(args) {
29
+ const query = args[0];
30
+ if (!query) {
31
+ return;
32
+ }
33
+
34
+ if (Object.prototype.toString.call(query) === '[object String]') {
35
+ return query.toString();
36
+ }
37
+
38
+ if (query['$where']) {
39
+ return query['$where'].toString();
40
+ }
41
+
42
+ return query;
43
+ }
44
+
45
+ function install() {
46
+ depHooks.resolve({ name: 'marsdb' }, marsdb => {
47
+ methods.forEach((method) => {
48
+ const name = `marsdb.Collection.prototype.${method}`;
49
+
50
+ patcher.patch(marsdb.Collection.prototype, method, {
51
+ name,
52
+ patchType,
53
+ pre({ args, hooked, orig }) {
54
+ const value = getCursorQueryData(args);
55
+ const sourceContext = getSourceContext(name);
56
+
57
+ if (
58
+ !sourceContext ||
59
+ !value ||
60
+ !Object.keys(value).length
61
+ ) return;
62
+
63
+ const sinkContext = {
64
+ name,
65
+ value,
66
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
67
+ };
68
+ inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
69
+ }
70
+ });
71
+ });
72
+ });
73
+ }
74
+
75
+ const marsdbInstr = (core.protect.inputTracing.marsdbInstrumentation = {
76
+ install,
77
+ });
78
+
79
+ return marsdbInstr;
80
+ };
@@ -62,7 +62,7 @@ module.exports = function (core) {
62
62
  const sinkContext = {
63
63
  name,
64
64
  value,
65
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
65
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
66
66
  };
67
67
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
68
68
  }
@@ -122,6 +122,7 @@ module.exports = function (core) {
122
122
  'countDocuments',
123
123
  'count',
124
124
  'distinct',
125
+ 'aggregate'
125
126
  ],
126
127
  patchType,
127
128
  preHookFn: v4CollectionVal
@@ -58,7 +58,7 @@ module.exports = function(core) {
58
58
  const sinkContext = {
59
59
  name,
60
60
  value,
61
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
61
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
62
62
  };
63
63
 
64
64
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -42,7 +42,7 @@ module.exports = function(core) {
42
42
  const sinkContext = {
43
43
  name,
44
44
  value,
45
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
45
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
46
46
  };
47
47
 
48
48
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -51,7 +51,7 @@ module.exports = function(core) {
51
51
  const sinkContext = {
52
52
  name,
53
53
  value,
54
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
54
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
55
55
  };
56
56
 
57
57
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -46,7 +46,7 @@ module.exports = function(core) {
46
46
  const sinkContext = {
47
47
  name,
48
48
  value,
49
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
49
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
50
50
  };
51
51
 
52
52
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -40,7 +40,7 @@ module.exports = function(core) {
40
40
  const sinkContext = {
41
41
  name,
42
42
  value: arg,
43
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
43
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
44
44
  };
45
45
  inputTracing.ssjsInjection(sourceContext, sinkContext);
46
46
  }
@@ -41,8 +41,8 @@ module.exports = function(core) {
41
41
  const { protect: { agentLib, semanticAnalysis, throwSecurityException }, captureStacktrace } = core;
42
42
 
43
43
  function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
44
- const { value, stacktraceData } = sinkContext;
45
- captureStacktrace(sinkContext, stacktraceData);
44
+ const { value, stacktraceOpts } = sinkContext;
45
+ captureStacktrace(sinkContext, stacktraceOpts);
46
46
  const result = {
47
47
  blocked: false,
48
48
  ruleId,
@@ -62,7 +62,7 @@ module.exports = function(core) {
62
62
  const sinkContext = {
63
63
  name: 'libxmljs.parseXmlString',
64
64
  value,
65
- stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
65
+ stacktraceOpts: { constructorOpt: hooked, prependFrames: [orig] },
66
66
  };
67
67
 
68
68
  try {
@@ -40,7 +40,7 @@ const ENTITY_TYPES = {
40
40
  // We only use this against lowercase strings; removed A-Z for speed
41
41
  const FILE_PATTERN_WINDOWS = /^[\\\\]*[a-z]{1,3}:.*/;
42
42
 
43
- const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+"(?<uri1>.*?)"\s*("(?<uri2>.*?)"\s*)?>/g;
43
+ const entityRegex = /<!ENTITY\s+(?<name>[a-zA-Z0-f]+)\s+(?<type>SYSTEM|PUBLIC)\s+['"](?<uri1>.*?)['"]\s*(['"](?<uri2>.*?)['"]\s*)?>/g;
44
44
 
45
45
  // Helper Functions
46
46
  const indicators = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -17,10 +17,10 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/agent-lib": "^5.1.0",
20
+ "@contrast/agent-lib": "^5.3.0",
21
21
  "@contrast/common": "1.3.1",
22
- "@contrast/core": "1.10.0",
23
- "@contrast/esm-hooks": "1.6.0",
22
+ "@contrast/core": "1.10.1",
23
+ "@contrast/esm-hooks": "1.6.1",
24
24
  "@contrast/scopes": "1.2.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"