@contrast/assess 1.6.0 → 1.7.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 (27) hide show
  1. package/lib/dataflow/propagation/index.js +1 -0
  2. package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
  3. package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
  4. package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
  5. package/lib/dataflow/propagation/install/sequelize.js +310 -0
  6. package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
  7. package/lib/dataflow/propagation/install/string/match.js +2 -2
  8. package/lib/dataflow/propagation/install/string/replace.js +9 -4
  9. package/lib/dataflow/sinks/common.js +10 -1
  10. package/lib/dataflow/sinks/index.js +30 -1
  11. package/lib/dataflow/sinks/install/express/index.js +29 -0
  12. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
  13. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +96 -69
  14. package/lib/dataflow/sinks/install/http.js +20 -5
  15. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +33 -9
  16. package/lib/dataflow/sinks/install/mongodb.js +171 -54
  17. package/lib/dataflow/sinks/install/mssql.js +9 -4
  18. package/lib/dataflow/sinks/install/mysql.js +20 -4
  19. package/lib/dataflow/sinks/install/postgres.js +25 -12
  20. package/lib/dataflow/sinks/install/sequelize.js +142 -0
  21. package/lib/dataflow/sinks/install/sqlite3.js +9 -4
  22. package/lib/dataflow/sources/handler.js +4 -0
  23. package/lib/dataflow/sources/index.js +4 -1
  24. package/lib/dataflow/sources/install/body-parser1.js +120 -0
  25. package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
  26. package/lib/dataflow/sources/install/express/index.js +28 -0
  27. package/package.json +3 -3
@@ -18,7 +18,7 @@
18
18
  const { patchType } = require('../common');
19
19
  const {
20
20
  DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
- Rule,
21
+ Rule: { SQL_INJECTION },
22
22
  isString
23
23
  } = require('@contrast/common');
24
24
 
@@ -37,7 +37,7 @@ module.exports = function (core) {
37
37
  assess: {
38
38
  dataflow: {
39
39
  tracker,
40
- sinks: { isVulnerable, reportFindings },
40
+ sinks: { isVulnerable, isLocked, reportFindings },
41
41
  eventFactory: { createSinkEvent },
42
42
  },
43
43
  },
@@ -45,7 +45,12 @@ module.exports = function (core) {
45
45
 
46
46
  const pre = (name) => (data) => {
47
47
  const store = sources.getStore()?.assess;
48
- if (!store || !data.args[0] || !isString(data.args[0])) return;
48
+ if (
49
+ !store ||
50
+ !data.args[0] ||
51
+ !isString(data.args[0]) ||
52
+ isLocked(SQL_INJECTION)
53
+ ) return;
49
54
 
50
55
  const strInfo = tracker.getData(data.args[0]);
51
56
  if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
@@ -74,7 +79,7 @@ module.exports = function (core) {
74
79
 
75
80
  if (event) {
76
81
  reportFindings({
77
- ruleId: Rule.SQL_INJECTION,
82
+ ruleId: SQL_INJECTION,
78
83
  sinkEvent: event,
79
84
  });
80
85
  }
@@ -72,6 +72,10 @@ module.exports = function(core) {
72
72
  return null;
73
73
  }
74
74
 
75
+ if (!context) {
76
+ context = inputType;
77
+ }
78
+
75
79
  let stack;
76
80
 
77
81
  traverseValues(data, (path, type, value, obj) => {
@@ -17,7 +17,7 @@
17
17
 
18
18
  const { callChildComponentMethodsSync } = require('@contrast/common');
19
19
 
20
- module.exports = function(core) {
20
+ module.exports = function (core) {
21
21
  const sources = core.assess.dataflow.sources = {};
22
22
 
23
23
  // API
@@ -27,11 +27,14 @@ module.exports = function(core) {
27
27
  require('./install/http')(core);
28
28
 
29
29
  // frameworks and frameworks specific libraries
30
+ require('./install/express')(core);
30
31
  require('./install/fastify')(core);
31
32
  require('./install/koa')(core);
32
33
 
33
34
  // libraries
35
+ require('./install/body-parser1')(core);
34
36
  require('./install/busboy1')(core);
37
+ require('./install/cookie-parser1')(core);
35
38
  require('./install/formidable1')(core);
36
39
  require('./install/qs6')(core);
37
40
 
@@ -0,0 +1,120 @@
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
+
18
+ const { InputType } = require('@contrast/common');
19
+ const { patchType } = require('../common');
20
+
21
+ const METHODS = ['json', 'raw', 'text', 'urlencoded'];
22
+ const INPUT_TYPES = {
23
+ 'body-parser.json.jsonParser': InputType.JSON_VALUE,
24
+ 'body-parser.raw.rawParser': InputType.BODY,
25
+ 'body-parser.text.textParser': InputType.BODY,
26
+ 'body-parser.urlencoded.urlencodedParser': InputType.PARAMETER_VALUE,
27
+ };
28
+
29
+ module.exports = function init(core) {
30
+ const { assess, depHooks, logger, patcher, scopes } = core;
31
+
32
+ const createPreHook = (name) => (data) => {
33
+ const [req, , next] = data.args;
34
+ data.args[2] = function contrastNext(...args) {
35
+ const sourceContext = scopes.sources.getStore()?.assess;
36
+
37
+ if (!sourceContext) {
38
+ logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
39
+ return;
40
+ }
41
+
42
+ if (sourceContext.parsedBody) {
43
+ logger.trace({ name }, 'values already tracked');
44
+ return;
45
+ }
46
+
47
+ // when using a specific parser, we know the input type.
48
+ let inputType = INPUT_TYPES[name];
49
+ // when using `bodyParser()`, determine input type by content type.
50
+ if (!inputType) {
51
+ inputType = req.headers?.['content-type']?.includes('json')
52
+ ? InputType.JSON_VALUE
53
+ : req.headers?.['content-type']?.includes('urlencoded')
54
+ ? InputType.PARAMETER_VALUE
55
+ : InputType.BODY;
56
+ }
57
+
58
+ try {
59
+ assess.dataflow.sources.handle({
60
+ context: 'req.body',
61
+ name,
62
+ inputType,
63
+ stacktraceOpts: {
64
+ constructorOpt: contrastNext
65
+ },
66
+ data: req.body,
67
+ sourceContext,
68
+ });
69
+
70
+ sourceContext.parsedBody = !!Object.keys(req.body).length;
71
+ } catch (err) {
72
+ logger.error({ name, err }, 'unable to handle source');
73
+ }
74
+
75
+ return next(...args);
76
+ };
77
+ };
78
+
79
+ assess.dataflow.sources.bodyParser1Instrumentation = {
80
+ install() {
81
+ depHooks.resolve(
82
+ { name: 'body-parser', version: '>=1.0.0' },
83
+ /** @param {import('body-parser').BodyParser} bodyParser */
84
+ (bodyParser) => {
85
+ bodyParser = patcher.patch(bodyParser, {
86
+ name: 'body-parser',
87
+ patchType,
88
+ post(data) {
89
+ const name = 'body-parser.bodyParser';
90
+ data.result = patcher.patch(data.result, {
91
+ name,
92
+ patchType,
93
+ pre: createPreHook(name),
94
+ });
95
+ },
96
+ });
97
+
98
+ METHODS.forEach((method) => {
99
+ patcher.patch(bodyParser, method, {
100
+ name: `body-parser.${method}`,
101
+ patchType,
102
+ post(data) {
103
+ const name = `body-parser.${method}.${method}Parser`;
104
+ data.result = patcher.patch(data.result, {
105
+ name,
106
+ patchType,
107
+ pre: createPreHook(name)
108
+ });
109
+ },
110
+ });
111
+ });
112
+
113
+ return bodyParser;
114
+ }
115
+ );
116
+ }
117
+ };
118
+
119
+ return assess.dataflow.sources.bodyParser1Instrumentation;
120
+ };
@@ -0,0 +1,101 @@
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
+
18
+ const { InputType } = require('@contrast/common');
19
+ const { patchType } = require('../common');
20
+
21
+ module.exports = function init(core) {
22
+ const { assess, depHooks, logger, patcher, scopes } = core;
23
+
24
+ assess.dataflow.sources.cookieParser1Instrumentation = {
25
+ install() {
26
+ depHooks.resolve(
27
+ { name: 'cookie-parser', version: '>=1.0.0' },
28
+ /** @param {import('cookie-parser')} cookieParser */
29
+ (cookieParser) =>
30
+ patcher.patch(cookieParser, {
31
+ name: 'cookie-parser',
32
+ patchType,
33
+ post(data) {
34
+ const name = 'cookie-parser.cookieParser';
35
+ data.result = patcher.patch(data.result, {
36
+ name,
37
+ patchType,
38
+ pre(data) {
39
+ const [req, , next] = data.args;
40
+ data.args[2] = function contrastNext(...args) {
41
+ const sourceContext = scopes.sources.getStore()?.assess;
42
+
43
+ if (!sourceContext) {
44
+ logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
45
+ return;
46
+ }
47
+
48
+ if (sourceContext.parsedCookies) {
49
+ logger.trace({ name }, 'cookies already tracked');
50
+ } else if (req.cookies) {
51
+ try {
52
+ assess.dataflow.sources.handle({
53
+ context: 'req.cookies',
54
+ name,
55
+ inputType: InputType.COOKIE_VALUE,
56
+ stacktraceOpts: {
57
+ constructorOpt: contrastNext
58
+ },
59
+ data: req.cookies,
60
+ sourceContext,
61
+ });
62
+
63
+ sourceContext.parsedCookies = !!Object.keys(req.cookies).length;
64
+ } catch (err) {
65
+ logger.error({ name, err }, 'unable to handle source');
66
+ }
67
+ }
68
+
69
+ if (sourceContext.parsedSignedCookies) {
70
+ logger.trace({ name }, 'signedCookies already tracked');
71
+ } else if (req.signedCookies) {
72
+ try {
73
+ assess.dataflow.sources.handle({
74
+ context: 'req.signedCookies',
75
+ name,
76
+ inputType: InputType.COOKIE_VALUE,
77
+ stacktraceOpts: {
78
+ constructorOpt: contrastNext
79
+ },
80
+ data: req.signedCookies,
81
+ sourceContext,
82
+ });
83
+
84
+ sourceContext.parsedSignedCookies = !!Object.keys(req.signedCookies).length;
85
+ } catch (err) {
86
+ logger.error({ name, err }, 'unable to handle source');
87
+ }
88
+ }
89
+
90
+ return next(...args);
91
+ };
92
+ },
93
+ });
94
+ },
95
+ })
96
+ );
97
+ }
98
+ };
99
+
100
+ return assess.dataflow.sources.cookieParser1Instrumentation;
101
+ };
@@ -0,0 +1,28 @@
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
+
18
+ const { callChildComponentMethodsSync } = require('@contrast/common');
19
+
20
+ module.exports = function init(core) {
21
+ core.assess.dataflow.sources.expressInstrumentation = {
22
+ install() {
23
+ callChildComponentMethodsSync(this, 'install');
24
+ }
25
+ };
26
+
27
+ return core.assess.dataflow.sources.expressInstrumentation;
28
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@contrast/distringuish": "^4.1.0",
17
- "@contrast/scopes": "1.3.0",
18
- "@contrast/common": "1.9.0",
17
+ "@contrast/scopes": "1.4.0",
18
+ "@contrast/common": "1.10.0",
19
19
  "parseurl": "^1.3.3"
20
20
  }
21
21
  }