@contrast/protect 1.11.0 → 1.12.1

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
  },
@@ -220,7 +220,7 @@ module.exports = function(core) {
220
220
  key: path.pop(), // there should always be at least the param name
221
221
  value,
222
222
  score: item.score,
223
- idsList: [],
223
+ idsList: item.idsList
224
224
  });
225
225
  }
226
226
  });
@@ -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];
@@ -147,12 +147,27 @@ module.exports = function(core) {
147
147
 
148
148
  for (const result of stringInjectionResults) {
149
149
  if (typeof sinkContext.value === 'object') {
150
- traverseKeysAndValues(sinkContext.value, function(path, type, value) {
150
+ traverseKeysAndValues(sinkContext.value, function(path, type, value, obj) {
151
151
  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;
152
+
153
+ if (!stringFindings && type == 'Key' && value == '$accumulator') {
154
+ stringFindings =
155
+ handleStringValue(result, obj[value]?.['init'], agentLib) ||
156
+ handleStringValue(result, obj[value]?.['merge'], agentLib) ||
157
+ handleStringValue(result, obj[value]?.['finalize'], agentLib) ||
158
+ handleStringValue(result, obj[value]?.['accumulate'], agentLib);
159
+ }
160
+
161
+ if (!stringFindings && type == 'Key' && value == '$function') {
162
+ stringFindings =
163
+ handleStringValue(result, obj['$function']?.body, agentLib);
164
+ }
165
+
166
+ if (!stringFindings) {
167
+ stringFindings = handleStringValue(result, obj[value], agentLib);
168
+ }
169
+
170
+ if (stringFindings) return true;
156
171
  });
157
172
  } else if (typeof sinkContext.value === 'string') {
158
173
  stringFindings = handleStringValue(result, sinkContext.value, agentLib);
@@ -253,7 +268,10 @@ function getResultsByRuleId(ruleId, context) {
253
268
  if (context.policy[ruleId] === OFF) {
254
269
  return;
255
270
  }
256
- return context.resultsMap[ruleId];
271
+ // because agent-lib stores all nosql-injection results under nosql-injection-mongo
272
+ const resultsMapRuleId = ruleId === 'nosql-injection' ? 'nosql-injection-mongo' : ruleId;
273
+
274
+ return context.resultsMap[resultsMapRuleId];
257
275
  }
258
276
 
259
277
  function handleObjectValue(result, object) {
@@ -306,7 +324,10 @@ function handleStringValue(result, cmd, agentLib) {
306
324
  inputBoundaryIndex: 0,
307
325
  };
308
326
  } else {
309
- const isAttack = agentLib.checkSsjsInjectionSink(cmd, inputIndex, result.value.length);
327
+ // This is a temporary workaround, while `agent-lib` fixes
328
+ // the `checkSsjsInjectionSink` so it can detect the "TRUE-CLAUSE-1" correctly
329
+ // TODO: NODE-2897
330
+ const isAttack = result.idsList.includes('TRUE-CLAUSE-1') || agentLib.checkSsjsInjectionSink(cmd, inputIndex, result.value.length);
310
331
  if (!isAttack) return findings;
311
332
 
312
333
  findings = {
@@ -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.1",
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",
21
- "@contrast/common": "1.3.1",
22
- "@contrast/core": "1.10.0",
23
- "@contrast/esm-hooks": "1.6.0",
20
+ "@contrast/agent-lib": "^5.3.1",
21
+ "@contrast/common": "1.3.2",
22
+ "@contrast/core": "1.10.2",
23
+ "@contrast/esm-hooks": "1.6.2",
24
24
  "@contrast/scopes": "1.2.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"