@contrast/protect 1.8.0 → 1.8.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.
@@ -24,7 +24,8 @@ module.exports = function(core) {
24
24
  protect: {
25
25
  hardening,
26
26
  throwSecurityException,
27
- }
27
+ },
28
+ captureStacktrace,
28
29
  } = core;
29
30
 
30
31
  function getResults(sourceContext, ruleId) {
@@ -38,7 +39,7 @@ module.exports = function(core) {
38
39
  hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
39
40
  const ruleId = 'untrusted-deserialization';
40
41
  const mode = sourceContext.policy[ruleId];
41
- const { name, value } = sinkContext;
42
+ const { name, value, stacktraceData } = sinkContext;
42
43
 
43
44
  if (mode === 'off') return;
44
45
 
@@ -48,6 +49,7 @@ module.exports = function(core) {
48
49
  const blocked = BLOCKING_MODES.includes(mode);
49
50
  const results = getResults(sourceContext, ruleId);
50
51
 
52
+ captureStacktrace(sinkContext, stacktraceData);
51
53
  results.push({
52
54
  blocked,
53
55
  findings: { deserializer: name, command: false },
@@ -21,7 +21,6 @@ module.exports = function(core) {
21
21
  const {
22
22
  depHooks,
23
23
  patcher,
24
- captureStacktrace,
25
24
  protect,
26
25
  protect: {
27
26
  hardening
@@ -43,10 +42,11 @@ module.exports = function(core) {
43
42
 
44
43
  if (!sourceContext || !value) return;
45
44
 
46
- const sinkContext = captureStacktrace(
47
- { name: `${name}.${method}`, value },
48
- { constructorOpt: hooked, prependFrames: [orig] },
49
- );
45
+ const sinkContext = {
46
+ name: `${name}.${method}`,
47
+ value,
48
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
49
+ };
50
50
  hardening.handleUntrustedDeserialization(sourceContext, sinkContext);
51
51
  },
52
52
  });
@@ -214,12 +214,6 @@ class HttpInstrumentation {
214
214
  method: reqData.method,
215
215
  };
216
216
 
217
- // only add queries if it's known that 'qs' or equivalent won't be used.
218
- /* c8 ignore next 3 */
219
- if (reqData.standardUrlParsing) {
220
- connectInputs.queries = reqData.queries;
221
- }
222
-
223
217
  if (inputAnalysis.virtualPatchesEvaluators?.length) {
224
218
  store.protect.virtualPatchesEvaluators.push(...inputAnalysis.virtualPatchesEvaluators.map((e) => new Map(e)));
225
219
  }
@@ -24,9 +24,18 @@ const {
24
24
  } = require('@contrast/common');
25
25
 
26
26
  module.exports = function(core) {
27
- const { protect: { agentLib, inputTracing, throwSecurityException } } = core;
27
+ const {
28
+ protect: {
29
+ agentLib,
30
+ inputTracing,
31
+ throwSecurityException
32
+ },
33
+ captureStacktrace,
34
+ } = core;
28
35
 
29
36
  function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
37
+ const { stacktraceData } = sinkContext;
38
+ captureStacktrace(sinkContext, stacktraceData);
30
39
  result.details.push({ sinkContext, findings });
31
40
 
32
41
  const mode = sourceContext.policy[ruleId];
@@ -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;
@@ -43,10 +42,11 @@ module.exports = function(core) {
43
42
 
44
43
  if (!sourceContext || !value || !isString(value)) return;
45
44
 
46
- const sinkContext = captureStacktrace(
47
- { name, value },
48
- { constructorOpt: hooked, prependFrames: [orig] }
49
- );
45
+ const sinkContext = {
46
+ name,
47
+ value,
48
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
49
+ };
50
50
 
51
51
  inputTracing.handleCommandInjection(sourceContext, sinkContext);
52
52
  // To evade code duplication we are using these INPUT TRACING instrumentation
@@ -23,7 +23,6 @@ module.exports = function(core) {
23
23
  logger,
24
24
  scopes: { instrumentation },
25
25
  patcher,
26
- captureStacktrace,
27
26
  protect,
28
27
  protect: { inputTracing }
29
28
  } = core;
@@ -45,10 +44,11 @@ module.exports = function(core) {
45
44
 
46
45
  if (!sourceContext || !value || !isString(value)) return;
47
46
 
48
- const sinkContext = captureStacktrace(
49
- { name: 'eval', value },
50
- { constructorOpt: hooked, prependFrames: [orig] }
51
- );
47
+ const sinkContext = {
48
+ name: 'eval',
49
+ value,
50
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
51
+ };
52
52
  inputTracing.ssjsInjection(sourceContext, sinkContext);
53
53
  }
54
54
  });
@@ -64,7 +64,6 @@ module.exports = function(core) {
64
64
  scopes: { instrumentation },
65
65
  patcher,
66
66
  depHooks,
67
- captureStacktrace,
68
67
  protect,
69
68
  protect: { inputTracing }
70
69
  } = core;
@@ -101,10 +100,11 @@ module.exports = function(core) {
101
100
  // don't need to necessarily need to lock it here - there are no
102
101
  // lower-level calls that we instrument
103
102
  for (const value of values) {
104
- const sinkContext = captureStacktrace(
105
- { name, value },
106
- { constructorOpt: hooked, prependFrames: [orig] }
107
- );
103
+ const sinkContext = {
104
+ name,
105
+ value,
106
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
107
+ };
108
108
  inputTracing.handlePathTraversal(sourceContext, sinkContext);
109
109
  core.protect.semanticAnalysis.handlePathTraversalFileSecurityBypass(sourceContext, sinkContext);
110
110
  }
@@ -23,7 +23,6 @@ module.exports = function(core) {
23
23
  logger,
24
24
  scopes: { instrumentation },
25
25
  patcher,
26
- captureStacktrace,
27
26
  protect,
28
27
  protect: { inputTracing }
29
28
  } = core;
@@ -46,10 +45,12 @@ module.exports = function(core) {
46
45
 
47
46
  if (!sourceContext || !fnBody || !isString(fnBody)) return;
48
47
 
49
- const sinkContext = captureStacktrace(
50
- { name: 'Function', value: fnBody },
51
- { constructorOpt: hooked, prependFrames: [orig] }
52
- );
48
+ const sinkContext = {
49
+ name: 'Function',
50
+ value: fnBody,
51
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
52
+ };
53
+ console.log({ sinkContext: sinkContext.stacktraceData });
53
54
  inputTracing.ssjsInjection(sourceContext, sinkContext);
54
55
  }
55
56
  })
@@ -22,7 +22,6 @@ module.exports = function(core) {
22
22
  scopes: { instrumentation },
23
23
  patcher,
24
24
  depHooks,
25
- captureStacktrace,
26
25
  protect,
27
26
  protect: { inputTracing }
28
27
  } = core;
@@ -44,10 +43,11 @@ module.exports = function(core) {
44
43
  const value = data.args[0]?.toString();
45
44
  if (!value) return;
46
45
 
47
- const sinkContext = captureStacktrace(
48
- { name, value },
49
- { constructorOpt: data.hooked }
50
- );
46
+ const sinkContext = {
47
+ name,
48
+ value,
49
+ stacktraceData: { constructorOpt: data.hooked },
50
+ };
51
51
  inputTracing.handleReflectedXss(sourceContext, sinkContext);
52
52
  }
53
53
  });
@@ -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;
@@ -67,10 +66,11 @@ module.exports = function (core) {
67
66
 
68
67
  if (!sourceContext || !value) return;
69
68
 
70
- const sinkContext = captureStacktrace(
71
- { name, value },
72
- { constructorOpt: hooked, prependFrames: [orig] }
73
- );
69
+ const sinkContext = {
70
+ name,
71
+ value,
72
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
73
+ };
74
74
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
75
75
  }
76
76
  });
@@ -91,10 +91,11 @@ module.exports = function (core) {
91
91
  for (const op of ops) {
92
92
  const value = op && getOpQueryData(op);
93
93
  if (value) {
94
- const sinkContext = captureStacktrace(
95
- { name, value },
96
- { constructorOpt: hooked, prependFrames: [orig] }
97
- );
94
+ const sinkContext = {
95
+ name,
96
+ value,
97
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
98
+ };
98
99
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
99
100
  }
100
101
  }
@@ -132,10 +133,11 @@ module.exports = function (core) {
132
133
 
133
134
  if (!sourceContext || !value) return;
134
135
 
135
- const sinkContext = captureStacktrace(
136
- { name, value },
137
- { constructorOpt: hooked, prependFrames: [orig] }
138
- );
136
+ const sinkContext = {
137
+ name,
138
+ value,
139
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
140
+ };
139
141
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
140
142
  },
141
143
  });
@@ -150,10 +152,11 @@ module.exports = function (core) {
150
152
 
151
153
  if (!sourceContext || !value) return;
152
154
 
153
- const sinkContext = captureStacktrace(
154
- { name, value },
155
- { constructorOpt: hooked, prependFrames: [orig] }
156
- );
155
+ const sinkContext = {
156
+ name,
157
+ value,
158
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
159
+ };
157
160
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
158
161
  }
159
162
  });
@@ -174,10 +177,11 @@ module.exports = function (core) {
174
177
 
175
178
  if (!sourceContext || !value) return;
176
179
 
177
- const sinkContext = captureStacktrace(
178
- { name, value },
179
- { constructorOpt: hooked, prependFrames: [orig] }
180
- );
180
+ const sinkContext = {
181
+ name,
182
+ value,
183
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
184
+ };
181
185
 
182
186
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
183
187
  }
@@ -193,10 +197,11 @@ module.exports = function (core) {
193
197
 
194
198
  if (!sourceContext || !value) return;
195
199
 
196
- const sinkContext = captureStacktrace(
197
- { name, value },
198
- { constructorOpt: hooked, prependFrames: [orig] }
199
- );
200
+ const sinkContext = {
201
+ name,
202
+ value,
203
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
204
+ };
200
205
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
201
206
  }
202
207
  }));
@@ -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
  //
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';
@@ -38,12 +38,14 @@ const getRuleResults = function(obj, prop) {
38
38
  // See files in protect/lib/input-tracing/install/.
39
39
 
40
40
  module.exports = function(core) {
41
- const { protect: { agentLib, semanticAnalysis, throwSecurityException } } = core;
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
46
  const result = {
45
47
  blocked: false,
46
- findings: { command: sinkContext.value },
48
+ findings: { command: value },
47
49
  sinkContext,
48
50
  ...finding
49
51
  };
@@ -26,7 +26,6 @@ module.exports = function(core) {
26
26
  logger,
27
27
  protect: { semanticAnalysis },
28
28
  protect,
29
- captureStacktrace
30
29
  } = core;
31
30
 
32
31
  function install() {
@@ -60,10 +59,11 @@ module.exports = function(core) {
60
59
  // see: https://help.semmle.com/wiki/display/JS/XML+external+entity+expansion
61
60
  if (!sourceContext || !value || !isString(value) || !args[1].noent) return;
62
61
 
63
- const sinkContext = captureStacktrace(
64
- { name: 'libxmljs.parseXmlString', value },
65
- { constructorOpt: hooked, prependFrames: [orig] }
66
- );
62
+ const sinkContext = {
63
+ name: 'libxmljs.parseXmlString',
64
+ value,
65
+ stacktraceData: { constructorOpt: hooked, prependFrames: [orig] },
66
+ };
67
67
 
68
68
  try {
69
69
  semanticAnalysis.handleXXE(sourceContext, sinkContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.8.0",
3
+ "version": "1.8.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)",
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^5.1.0",
21
- "@contrast/common": "1.1.4",
22
- "@contrast/core": "1.7.0",
23
- "@contrast/esm-hooks": "1.3.0",
21
+ "@contrast/common": "1.1.5",
22
+ "@contrast/core": "1.7.1",
23
+ "@contrast/esm-hooks": "1.3.1",
24
24
  "@contrast/scopes": "1.2.0",
25
25
  "ipaddr.js": "^2.0.1",
26
26
  "semver": "^7.3.7"