@contrast/assess 1.4.0 → 1.6.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 (59) hide show
  1. package/lib/dataflow/event-factory.js +41 -83
  2. package/lib/dataflow/index.js +0 -1
  3. package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
  4. package/lib/dataflow/propagation/install/contrast-methods/add.js +23 -16
  5. package/lib/dataflow/propagation/install/contrast-methods/tag.js +30 -22
  6. package/lib/dataflow/propagation/install/decode-uri-component.js +8 -5
  7. package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -5
  8. package/lib/dataflow/propagation/install/encode-uri-component.js +3 -3
  9. package/lib/dataflow/propagation/install/escape-html.js +10 -7
  10. package/lib/dataflow/propagation/install/escape.js +8 -5
  11. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +8 -5
  12. package/lib/dataflow/propagation/install/mysql-connection-escape.js +8 -5
  13. package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -3
  14. package/lib/dataflow/propagation/install/querystring/parse.js +11 -6
  15. package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
  16. package/lib/dataflow/propagation/install/string/concat.js +4 -4
  17. package/lib/dataflow/propagation/install/string/format-methods.js +2 -2
  18. package/lib/dataflow/propagation/install/string/html-methods.js +5 -5
  19. package/lib/dataflow/propagation/install/string/index.js +1 -0
  20. package/lib/dataflow/propagation/install/string/match.js +3 -3
  21. package/lib/dataflow/propagation/install/string/replace.js +9 -5
  22. package/lib/dataflow/propagation/install/string/slice.js +104 -0
  23. package/lib/dataflow/propagation/install/string/split.js +4 -4
  24. package/lib/dataflow/propagation/install/string/substring.js +6 -4
  25. package/lib/dataflow/propagation/install/string/trim.js +2 -2
  26. package/lib/dataflow/propagation/install/unescape.js +8 -5
  27. package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -3
  28. package/lib/dataflow/propagation/install/validator/hooks.js +2 -2
  29. package/lib/dataflow/propagation/install/validator/methods.js +60 -51
  30. package/lib/dataflow/sinks/index.js +15 -2
  31. package/lib/dataflow/sinks/install/child-process.js +224 -0
  32. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +47 -23
  33. package/lib/dataflow/sinks/install/fs.js +136 -0
  34. package/lib/dataflow/sinks/install/http.js +48 -32
  35. package/lib/dataflow/sinks/install/koa/index.js +30 -0
  36. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +122 -0
  37. package/lib/dataflow/sinks/install/marsdb.js +135 -0
  38. package/lib/dataflow/sinks/install/mongodb.js +205 -0
  39. package/lib/dataflow/sinks/install/mssql.js +19 -13
  40. package/lib/dataflow/sinks/install/mysql.js +122 -0
  41. package/lib/dataflow/sinks/install/postgres.js +40 -29
  42. package/lib/dataflow/sinks/install/sqlite3.js +99 -0
  43. package/lib/dataflow/sources/handler.js +19 -15
  44. package/lib/dataflow/sources/index.js +9 -0
  45. package/lib/dataflow/sources/install/busboy1.js +112 -0
  46. package/lib/dataflow/sources/install/fastify/fastify.js +23 -29
  47. package/lib/dataflow/sources/install/fastify/index.js +4 -5
  48. package/lib/dataflow/sources/install/formidable1.js +91 -0
  49. package/lib/dataflow/sources/install/http.js +35 -14
  50. package/lib/dataflow/sources/install/koa/index.js +32 -0
  51. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +92 -0
  52. package/lib/dataflow/sources/install/koa/koa-routers.js +84 -0
  53. package/lib/dataflow/sources/install/koa/koa2.js +103 -0
  54. package/lib/dataflow/sources/install/qs6.js +84 -0
  55. package/lib/dataflow/utils/is-vulnerable.js +1 -1
  56. package/package.json +2 -2
  57. package/lib/dataflow/signatures/index.js +0 -2006
  58. package/lib/dataflow/signatures/mssql.js +0 -49
  59. package/lib/dataflow/sources/install/fastify/cookie.js +0 -61
@@ -0,0 +1,122 @@
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 util = require('util');
19
+ const {
20
+ DataflowTag: {
21
+ UNTRUSTED,
22
+ CUSTOM_ENCODED,
23
+ CUSTOM_VALIDATED,
24
+ HTML_ENCODED,
25
+ LIMITED_CHARS,
26
+ URL_ENCODED,
27
+ },
28
+ isString
29
+ } = require('@contrast/common');
30
+ const { patchType } = require('../../common');
31
+ const { createSubsetTags } = require('../../../tag-utils');
32
+
33
+ module.exports = function (core) {
34
+ const {
35
+ depHooks,
36
+ patcher,
37
+ scopes: { sources },
38
+ assess: {
39
+ dataflow: {
40
+ tracker,
41
+ sinks: { isVulnerable, reportFindings },
42
+ eventFactory: { createSinkEvent },
43
+ },
44
+ },
45
+ } = core;
46
+ const unvalidatedRedirect =
47
+ (core.assess.dataflow.sinks.koa.unvalidatedRedirect = {});
48
+
49
+ const inspect = patcher.unwrap(util.inspect);
50
+
51
+ const safeTags = [
52
+ CUSTOM_ENCODED,
53
+ CUSTOM_VALIDATED,
54
+ HTML_ENCODED,
55
+ LIMITED_CHARS,
56
+ URL_ENCODED,
57
+ ];
58
+
59
+ unvalidatedRedirect.install = function () {
60
+ depHooks.resolve({ name: 'koa', file: 'lib/response', version: '<2.9.0' }, (Response) => {
61
+ patcher.patch(Response, 'redirect', {
62
+ name: 'Koa.Response.redirect',
63
+ patchType,
64
+ pre(data) {
65
+ const assessStore = sources.getStore()?.assess;
66
+ if (!assessStore) return;
67
+
68
+ let [url] = data.args;
69
+
70
+ if (url === 'back') {
71
+ url = data.obj.ctx.get('Referrer') || data.args[1];
72
+ }
73
+
74
+ if (!url || !isString(url)) return;
75
+
76
+ const strInfo = tracker.getData(url);
77
+ if (!strInfo) return;
78
+
79
+ let urlPathTags = strInfo.tags;
80
+
81
+ if (url.indexOf('?') > -1) {
82
+ urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?'));
83
+ }
84
+
85
+ if (urlPathTags && isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
86
+ const event = createSinkEvent({
87
+ args: [{
88
+ tracked: true,
89
+ value: strInfo.value,
90
+ }],
91
+ context: `response.redirect(${inspect(strInfo.value)})`,
92
+ history: [strInfo],
93
+ name: 'Koa.Response.redirect',
94
+ object: {
95
+ tracked: false,
96
+ value: 'Koa.Response',
97
+ },
98
+ result: {
99
+ tracked: false,
100
+ value: undefined,
101
+ },
102
+ tags: urlPathTags,
103
+ source: 'P0',
104
+ stacktraceOpts: {
105
+ constructorOpt: data.hooked,
106
+ },
107
+ });
108
+
109
+ if (event) {
110
+ reportFindings({
111
+ ruleId: 'unvalidated-redirect',
112
+ sinkEvent: event,
113
+ });
114
+ }
115
+ }
116
+ }
117
+ });
118
+ });
119
+ };
120
+
121
+ return unvalidatedRedirect;
122
+ };
@@ -0,0 +1,135 @@
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
+ 'use strict';
16
+
17
+ const util = require('util');
18
+ const { patchType } = require('../common');
19
+ const {
20
+ traverseValues,
21
+ Rule,
22
+ DataflowTag: {
23
+ ALPHANUM_SPACE_HYPHEN,
24
+ LIMITED_CHARS,
25
+ UNTRUSTED,
26
+ STRING_TYPE_CHECKED,
27
+ CUSTOM_VALIDATED_NOSQL_INJECTION,
28
+ },
29
+ } = require('@contrast/common');
30
+
31
+ const collectionMethods = ['find', 'findOne', 'update', 'remove'];
32
+ const querySafeTags = [
33
+ LIMITED_CHARS,
34
+ ALPHANUM_SPACE_HYPHEN,
35
+ STRING_TYPE_CHECKED,
36
+ CUSTOM_VALIDATED_NOSQL_INJECTION,
37
+ ];
38
+
39
+ module.exports = function(core) {
40
+ const {
41
+ depHooks,
42
+ logger,
43
+ patcher,
44
+ scopes: { sources, instrumentation },
45
+ assess: {
46
+ dataflow: {
47
+ tracker,
48
+ sinks: { isVulnerable, reportFindings },
49
+ eventFactory: { createSinkEvent },
50
+ },
51
+ },
52
+ } = core;
53
+
54
+ const instr = core.assess.dataflow.sinks.marsdb = {};
55
+ const inspect = patcher.unwrap(util.inspect);
56
+
57
+ function getVulnerabilityInfo(query) {
58
+ let vulnInfo = null;
59
+ if (!query) return vulnInfo;
60
+
61
+ traverseValues(query, (path, type, value) => {
62
+ const strInfo = tracker.getData(value);
63
+ if (strInfo && isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
64
+ vulnInfo = { path, strInfo };
65
+ return true;
66
+ }
67
+ });
68
+
69
+ return vulnInfo;
70
+ }
71
+
72
+ function patchCollection(marsdb, method) {
73
+ const proto = marsdb.Collection.prototype;
74
+ const name = `marsdb.Collection.prototype.${method}`;
75
+
76
+ if (!proto[method]) {
77
+ logger.trace({ name }, `marsdb method ${method} not found!`);
78
+ return;
79
+ }
80
+
81
+ patcher.patch(proto, method, {
82
+ name,
83
+ patchType,
84
+ around(next, data) {
85
+ const sourceCtx = sources.getStore()?.assess;
86
+ if (!sourceCtx || instrumentation.isLocked()) {
87
+ return next();
88
+ }
89
+
90
+ const argIdx = 0;
91
+ const result = getVulnerabilityInfo(data.args[argIdx]);
92
+ if (!result) {
93
+ return next();
94
+ }
95
+
96
+ const { strInfo } = result;
97
+ const args = data.args.map((arg, idx) => ({
98
+ value: inspect(arg),
99
+ tracked: idx === argIdx,
100
+ }));
101
+
102
+ const sinkEvent = createSinkEvent({
103
+ args,
104
+ context: `marsdb.Collection.${method}(${args.map((a) => a.value)})`,
105
+ history: [strInfo],
106
+ object: {
107
+ tracked: false,
108
+ value: 'marsdb.Collection',
109
+ },
110
+ name,
111
+ result: strInfo.result,
112
+ source: `P${argIdx}`,
113
+ stacktraceOpts: {
114
+ constructorOpt: data.hooked,
115
+ },
116
+ tags: strInfo.tags,
117
+ });
118
+
119
+ if (sinkEvent) {
120
+ reportFindings({ ruleId: Rule.NOSQL_INJECTION_MONGO, sinkEvent });
121
+ }
122
+
123
+ return next();
124
+ },
125
+ });
126
+ }
127
+
128
+ instr.install = function() {
129
+ depHooks.resolve({ name: 'marsdb' }, (marsdb) => {
130
+ collectionMethods.forEach((method) => patchCollection(marsdb, method));
131
+ });
132
+ };
133
+
134
+ return instr;
135
+ };
@@ -0,0 +1,205 @@
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 util = require('util');
19
+ const {
20
+ DataflowTag: {
21
+ UNTRUSTED,
22
+ ALPHANUM_SPACE_HYPHEN,
23
+ CUSTOM_VALIDATED_NOSQL_INJECTION,
24
+ LIMITED_CHARS,
25
+ STRING_TYPE_CHECKED,
26
+ },
27
+ Rule,
28
+ isNonEmptyObject,
29
+ traverseValues
30
+ } = require('@contrast/common');
31
+ const utils = require('../../tag-utils');
32
+ const { patchType } = require('../common');
33
+
34
+ const collectionMethods = [
35
+ 'find',
36
+ 'findOne',
37
+ 'findAndModify',
38
+ 'findOneAndDelete',
39
+ 'findOneAndReplace',
40
+ 'findOneAndUpdate',
41
+ 'remove',
42
+ 'replaceOne',
43
+ 'update',
44
+ 'updateOne',
45
+ 'updateMany',
46
+ 'deleteOne',
47
+ 'deleteMany',
48
+ ];
49
+
50
+ const querySafeTags = [
51
+ ALPHANUM_SPACE_HYPHEN,
52
+ CUSTOM_VALIDATED_NOSQL_INJECTION,
53
+ LIMITED_CHARS,
54
+ STRING_TYPE_CHECKED,
55
+ ];
56
+
57
+ module.exports = function(core) {
58
+ const {
59
+ depHooks,
60
+ logger,
61
+ patcher,
62
+ scopes: { sources, instrumentation },
63
+ assess: {
64
+ dataflow: {
65
+ tracker,
66
+ sinks: { isVulnerable, reportFindings },
67
+ eventFactory: { createSinkEvent }
68
+ }
69
+ }
70
+ } = core;
71
+
72
+ const inspect = patcher.unwrap(util.inspect);
73
+ const instr = core.assess.dataflow.sinks.mongodb = {};
74
+
75
+ instr.getVulnerabilityInfo = function getVulnerabilityInfo(query) {
76
+ let vulnInfo = null;
77
+
78
+ if (!isNonEmptyObject(query)) return vulnInfo;
79
+
80
+ traverseValues(query, (path, type, value) => {
81
+ const strInfo = tracker.getData(value);
82
+ if (strInfo && isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
83
+ vulnInfo = { path, strInfo };
84
+ return true; // halts traversal
85
+ }
86
+ });
87
+
88
+ return vulnInfo;
89
+ };
90
+
91
+ instr.install = function() {
92
+ depHooks.resolve({ name: 'mongodb' }, (mongodb, version) => {
93
+ patchCollection(mongodb, version);
94
+ patchDatabase(mongodb, version);
95
+ });
96
+ };
97
+
98
+ return instr;
99
+
100
+ function patchCollection(mongodb, version) {
101
+ for (const method of collectionMethods) {
102
+
103
+ const proto = mongodb.Collection.prototype;
104
+ const name = `mongodb.Collection.prototype.${method}`;
105
+
106
+ if (!proto[method]) {
107
+
108
+ logger.trace({ name, version }, 'method not found - skipping instrumentation');
109
+ continue;
110
+ }
111
+
112
+ patcher.patch(proto, method, {
113
+ name,
114
+ patchType,
115
+ around(next, data) {
116
+ const { obj, args } = data;
117
+ const sourceCtx = sources.getStore()?.assess;
118
+
119
+ if (instrumentation.isLocked() || !sourceCtx) {
120
+ return next();
121
+ }
122
+
123
+ const argIdx = 0;
124
+ try {
125
+ const vulnInfo = instr.getVulnerabilityInfo(args[argIdx]);
126
+ if (vulnInfo) {
127
+ const { path, strInfo } = vulnInfo;
128
+ const objName = getObjectName(obj);
129
+ const args = data.args.map((arg, idx) => ({
130
+ value: inspect(arg),
131
+ tracked: idx === argIdx,
132
+ }));
133
+
134
+ const tags = getAdjustedQueryTags(path, strInfo, args[argIdx].value);
135
+ const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
136
+ const sinkEvent = createSinkEvent({
137
+ args,
138
+ context: `${objName}.${method}(${args.map((a) => a.value)})`,
139
+ history: [strInfo],
140
+ object: {
141
+ tracked: false,
142
+ value: 'mongodb.Collection',
143
+ },
144
+ name,
145
+ result: {
146
+ tracked: false,
147
+ value: resultVal,
148
+ },
149
+ source: `P${argIdx}`,
150
+ stacktraceOpts: {
151
+ constructorOpt: data.hooked,
152
+ },
153
+ tags,
154
+ });
155
+
156
+ if (sinkEvent) {
157
+ reportFindings({ ruleId: Rule.NOSQL_INJECTION_MONGO, sinkEvent });
158
+ }
159
+ }
160
+ } catch (err) {
161
+ core.logger.error({ name, err }, 'assess sink analysis failed');
162
+ }
163
+
164
+ if (method === 'findOne') {
165
+ // `findOne` will call `find` so don't analyze in nested call
166
+ const store = { name, lock: true };
167
+ const ret = instrumentation.run(store, next);
168
+ // but unlock for when callback args run or returned Promises resolve/reject
169
+ store.lock = false;
170
+ return ret;
171
+ }
172
+
173
+ return next();
174
+ },
175
+ });
176
+ }
177
+ }
178
+
179
+
180
+ function patchDatabase(mongodb, version) {
181
+ // todo
182
+ }
183
+
184
+ function getObjectName(obj) {
185
+ let name = '';
186
+ name += obj.s?.namespace?.db || 'db';
187
+ name += '.';
188
+ name += obj.s?.namespace?.collection || 'collection';
189
+ return name;
190
+ }
191
+
192
+ function getAdjustedQueryTags(path, strInfo, argString) {
193
+ const { tags } = strInfo;
194
+ let idx = -1;
195
+ for (const str of [...path, strInfo.value]) {
196
+ idx = argString.indexOf(str, idx);
197
+ if (idx == -1) {
198
+ idx = -1;
199
+ break;
200
+ }
201
+ }
202
+
203
+ return idx > 0 ? utils.createAppendTags([], tags, idx) : strInfo.tags;
204
+ }
205
+ };
@@ -15,15 +15,19 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { Rule, isString } = require('@contrast/common');
18
+ const {
19
+ DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
20
+ Rule,
21
+ isString
22
+ } = require('@contrast/common');
19
23
  const { createModuleLabel } = require('../../propagation/common');
20
24
  const { patchType } = require('../common');
21
25
 
22
- const SAFE_TAGS = [
23
- 'sql-encoded',
24
- 'limited-chars',
25
- 'custom-validated',
26
- 'custom-encoded',
26
+ const safeTags = [
27
+ SQL_ENCODED,
28
+ LIMITED_CHARS,
29
+ CUSTOM_VALIDATED,
30
+ CUSTOM_ENCODED,
27
31
  ];
28
32
 
29
33
  module.exports = function (core) {
@@ -45,7 +49,7 @@ module.exports = function (core) {
45
49
  if (!store || !data.args[0] || !isString(data.args[0])) return;
46
50
 
47
51
  const strInfo = tracker.getData(data.args[0]);
48
- if (!strInfo || !isVulnerable('untrusted', SAFE_TAGS, strInfo.tags)) {
52
+ if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
49
53
  return;
50
54
  }
51
55
 
@@ -54,12 +58,12 @@ module.exports = function (core) {
54
58
  history: [strInfo],
55
59
  object: {
56
60
  value: `[${createModuleLabel('mssql', version)}].${obj}`,
57
- isTracked: false,
61
+ tracked: false,
58
62
  },
59
63
  args: [
60
64
  {
61
65
  value: strInfo.value,
62
- isTracked: true,
66
+ tracked: true,
63
67
  },
64
68
  ],
65
69
  tags: strInfo.tags,
@@ -69,10 +73,12 @@ module.exports = function (core) {
69
73
  },
70
74
  });
71
75
 
72
- reportFindings({
73
- ruleId: Rule.SQL_INJECTION,
74
- metadata: event,
75
- });
76
+ if (event) {
77
+ reportFindings({
78
+ ruleId: Rule.SQL_INJECTION,
79
+ sinkEvent: event,
80
+ });
81
+ }
76
82
  };
77
83
 
78
84
  core.assess.dataflow.sinks.mssql = {
@@ -0,0 +1,122 @@
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 { patchType } = require('../common');
19
+ const { Rule, isString, DataflowTag: { CUSTOM_ENCODED_SQL_INJECTION, CUSTOM_ENCODED, CUSTOM_VALIDATED_SQL_INJECTION, CUSTOM_VALIDATED, SQL_ENCODED, LIMITED_CHARS, UNTRUSTED } } = require('@contrast/common');
20
+
21
+ const safeTags = [
22
+ CUSTOM_ENCODED_SQL_INJECTION,
23
+ CUSTOM_ENCODED,
24
+ CUSTOM_VALIDATED_SQL_INJECTION,
25
+ CUSTOM_VALIDATED,
26
+ SQL_ENCODED,
27
+ LIMITED_CHARS,
28
+ ];
29
+
30
+ module.exports = function (core) {
31
+ const {
32
+ depHooks,
33
+ patcher,
34
+ scopes: { sources },
35
+ assess: {
36
+ dataflow: {
37
+ tracker,
38
+ sinks: { isVulnerable, reportFindings },
39
+ eventFactory: { createSinkEvent },
40
+ },
41
+ },
42
+ } = core;
43
+
44
+ function getValueFromArgs([value]) {
45
+ if (isString(value)) {
46
+ return value;
47
+ }
48
+
49
+ if (isString(value.sql)) {
50
+ return value.sql;
51
+ }
52
+ }
53
+
54
+ const pre = (module, file, obj) => (data) => {
55
+ const store = sources.getStore()?.assess;
56
+ if (!store || !data.args[0]) return;
57
+
58
+ const val = getValueFromArgs(data.args);
59
+ if (!val) return;
60
+
61
+ const strInfo = tracker.getData(val);
62
+ if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
63
+ return;
64
+ }
65
+
66
+ const event = createSinkEvent({
67
+ name: `${module}/${file}`,
68
+ history: [strInfo],
69
+ object: {
70
+ value: `${module}.${obj}`,
71
+ tracked: false,
72
+ },
73
+ args: [
74
+ {
75
+ value: strInfo.value,
76
+ tracked: true,
77
+ },
78
+ ],
79
+ tags: strInfo.tags,
80
+ source: 'P0',
81
+ stacktraceOpts: {
82
+ contructorOpt: data.hooked,
83
+ },
84
+ });
85
+
86
+ if (event) {
87
+ reportFindings({
88
+ ruleId: Rule.SQL_INJECTION,
89
+ sinkEvent: event,
90
+ });
91
+ }
92
+ };
93
+
94
+ core.assess.dataflow.sinks.mysql = {
95
+ install() {
96
+ depHooks.resolve(
97
+ { name: 'mysql', file: 'lib/Connection' },
98
+ (Connection) => {
99
+ patcher.patch(Connection.prototype, 'query', {
100
+ name: 'Connection.prototype.query',
101
+ patchType,
102
+ pre: pre('mysql', 'lib/Connection.query', 'Connection')
103
+ });
104
+ },
105
+ );
106
+ depHooks.resolve(
107
+ { name: 'mysql2', file: 'lib/connection' },
108
+ (connection) => {
109
+ ['query', 'execute'].forEach((method) => {
110
+ patcher.patch(connection.prototype, `${method}`, {
111
+ name: `connection.prototype.${method}`,
112
+ patchType,
113
+ pre: pre('mysql2', `lib/connection.Connection.${method}`, 'connection')
114
+ });
115
+ });
116
+ },
117
+ );
118
+ },
119
+ };
120
+
121
+ return core.assess.dataflow.sinks.mysql;
122
+ };