@contrast/assess 1.28.0 → 1.29.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.
- package/lib/crypto-analysis/install/crypto.js +3 -3
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +5 -5
- package/lib/dataflow/propagation/install/JSON/parse.js +3 -3
- package/lib/dataflow/propagation/install/JSON/stringify.js +24 -17
- package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
- package/lib/dataflow/propagation/install/buffer.js +60 -2
- package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -3
- package/lib/dataflow/propagation/install/ejs/template.js +3 -3
- package/lib/dataflow/propagation/install/joi/boolean.js +1 -1
- package/lib/dataflow/propagation/install/joi/expression.js +1 -1
- package/lib/dataflow/propagation/install/joi/index.js +1 -1
- package/lib/dataflow/propagation/install/joi/keys.js +5 -4
- package/lib/dataflow/propagation/install/joi/number.js +1 -1
- package/lib/dataflow/propagation/install/joi/string-schema.js +3 -2
- package/lib/dataflow/propagation/install/joi/utils.js +9 -5
- package/lib/dataflow/propagation/install/joi/values.js +4 -3
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +2 -2
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +2 -2
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -2
- package/lib/dataflow/propagation/install/path/basename.js +2 -2
- package/lib/dataflow/propagation/install/path/common.js +5 -5
- package/lib/dataflow/propagation/install/path/format.js +7 -4
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -2
- package/lib/dataflow/propagation/install/path/parse.js +4 -5
- package/lib/dataflow/propagation/install/querystring/escape.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +8 -8
- package/lib/dataflow/propagation/install/querystring/stringify.js +1 -1
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
- package/lib/dataflow/propagation/install/send.js +2 -2
- package/lib/dataflow/propagation/install/string/concat.js +19 -19
- package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/index.js +4 -3
- package/lib/dataflow/propagation/install/string/match-all.js +3 -9
- package/lib/dataflow/propagation/install/string/match.js +6 -5
- package/lib/dataflow/propagation/install/string/replace.js +23 -17
- package/lib/dataflow/propagation/install/string/slice.js +5 -5
- package/lib/dataflow/propagation/install/string/split.js +13 -11
- package/lib/dataflow/propagation/install/string/substring.js +6 -5
- package/lib/dataflow/propagation/install/url/parse.js +1 -1
- package/lib/dataflow/propagation/install/url/searchParams.js +2 -1
- package/lib/dataflow/propagation/install/url/url.js +1 -1
- package/lib/dataflow/sinks/index.js +1 -0
- package/lib/dataflow/sinks/install/child-process.js +4 -4
- package/lib/dataflow/sinks/install/express/reflected-xss.js +7 -5
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +1 -2
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +1 -3
- package/lib/dataflow/sinks/install/fs.js +3 -3
- package/lib/dataflow/sinks/install/function.js +3 -3
- package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +1 -2
- package/lib/dataflow/sinks/install/http/request.js +6 -5
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
- package/lib/dataflow/sinks/install/libxmljs.js +1 -1
- package/lib/dataflow/sinks/install/marsdb.js +1 -2
- package/lib/dataflow/sinks/install/mongodb.js +1 -1
- package/lib/dataflow/sinks/install/mysql.js +1 -1
- package/lib/dataflow/sinks/install/postgres.js +1 -3
- package/lib/dataflow/sinks/install/restify.js +208 -0
- package/lib/dataflow/sinks/install/sequelize.js +1 -2
- package/lib/dataflow/sinks/install/vm.js +5 -5
- package/lib/dataflow/sources/handler.js +2 -2
- package/lib/dataflow/sources/index.js +1 -0
- package/lib/dataflow/sources/install/http.js +4 -4
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +85 -0
- package/lib/dataflow/sources/install/restify/index.js +32 -0
- package/lib/dataflow/sources/install/restify/jsonBodyParser.js +109 -0
- package/lib/dataflow/sources/install/restify/router.js +77 -0
- package/lib/dataflow/tag-utils.js +20 -4
- package/lib/dataflow/tracker.js +1 -0
- package/lib/event-factory.js +3 -3
- package/lib/get-policy.js +2 -2
- package/lib/index.d.ts +18 -0
- package/lib/index.js +13 -0
- package/lib/make-source-context.js +2 -2
- package/lib/response-scanning/handlers/index.js +10 -10
- package/lib/response-scanning/handlers/utils.js +19 -12
- package/lib/response-scanning/install/http.js +9 -59
- package/lib/session-configuration/install/express-session.js +3 -5
- package/lib/session-configuration/install/fastify-cookie.js +3 -3
- package/lib/session-configuration/install/hapi.js +1 -3
- package/lib/session-configuration/install/koa.js +1 -1
- package/package.json +4 -4
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { patchType } = require('../../common');
|
|
19
|
-
const {
|
|
19
|
+
const { isString } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
module.exports = function(core) {
|
|
22
22
|
const {
|
|
@@ -24,6 +24,7 @@ module.exports = function(core) {
|
|
|
24
24
|
patcher,
|
|
25
25
|
depHooks,
|
|
26
26
|
assess: {
|
|
27
|
+
inspect, // todo: remove
|
|
27
28
|
eventFactory: { createPropagationEvent },
|
|
28
29
|
dataflow: { tracker }
|
|
29
30
|
}
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { inspect } = require('@contrast/common');
|
|
19
18
|
const { patchType } = require('../../common');
|
|
20
19
|
|
|
21
20
|
module.exports = function(core) {
|
|
@@ -24,6 +23,7 @@ module.exports = function(core) {
|
|
|
24
23
|
patcher,
|
|
25
24
|
depHooks,
|
|
26
25
|
assess: {
|
|
26
|
+
inspect, // todo: remove
|
|
27
27
|
eventFactory: { createPropagationEvent },
|
|
28
28
|
dataflow: { tracker }
|
|
29
29
|
}
|
|
@@ -82,6 +82,7 @@ module.exports = function (core) {
|
|
|
82
82
|
require('./install/mysql')(core);
|
|
83
83
|
require('./install/node-serialize')(core);
|
|
84
84
|
require('./install/postgres')(core);
|
|
85
|
+
require('./install/restify')(core);
|
|
85
86
|
require('./install/sequelize')(core);
|
|
86
87
|
require('./install/sqlite3')(core);
|
|
87
88
|
require('./install/vm')(core);
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
const {
|
|
18
18
|
DataflowTag: { UNTRUSTED },
|
|
19
|
-
|
|
19
|
+
ArrayPrototypeJoin,
|
|
20
20
|
Rule: { CMD_INJECTION: ruleId },
|
|
21
21
|
isString,
|
|
22
|
-
inspect,
|
|
23
22
|
} = require('@contrast/common');
|
|
24
23
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
25
24
|
const { patchType } = require('../common');
|
|
@@ -35,6 +34,7 @@ module.exports = function(core) {
|
|
|
35
34
|
depHooks,
|
|
36
35
|
patcher,
|
|
37
36
|
assess: {
|
|
37
|
+
inspect, // todo: remove
|
|
38
38
|
getSourceContext,
|
|
39
39
|
eventFactory: { createSinkEvent },
|
|
40
40
|
dataflow: {
|
|
@@ -81,7 +81,7 @@ module.exports = function(core) {
|
|
|
81
81
|
name,
|
|
82
82
|
moduleName: 'child_process',
|
|
83
83
|
methodName: method,
|
|
84
|
-
context: `child_process.${method}(${
|
|
84
|
+
context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
|
|
85
85
|
history: [strInfo],
|
|
86
86
|
object: {
|
|
87
87
|
value: 'child_process',
|
|
@@ -167,7 +167,7 @@ module.exports = function(core) {
|
|
|
167
167
|
name,
|
|
168
168
|
moduleName: 'child_process',
|
|
169
169
|
methodName: method,
|
|
170
|
-
context: `child_process.${method}(${
|
|
170
|
+
context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
|
|
171
171
|
history: [trackedArgs[vulnerableArgIdx]],
|
|
172
172
|
object: {
|
|
173
173
|
value: 'child_process',
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
Rule: { REFLECTED_XSS: ruleId },
|
|
21
20
|
DataflowTag: {
|
|
@@ -52,14 +51,13 @@ module.exports = function(core) {
|
|
|
52
51
|
eventFactory: { createSinkEvent },
|
|
53
52
|
dataflow: {
|
|
54
53
|
tracker,
|
|
55
|
-
sinks: { isVulnerable, reportFindings, reportSafePositive }
|
|
54
|
+
sinks: { isVulnerable, reportFindings, reportSafePositive, isSafeContentType }
|
|
56
55
|
},
|
|
57
56
|
ruleScopes,
|
|
58
57
|
},
|
|
59
58
|
} = core;
|
|
60
59
|
|
|
61
60
|
const reflectedXss = core.assess.dataflow.sinks.express.reflectedXss = {};
|
|
62
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
63
61
|
|
|
64
62
|
const safeTags = [
|
|
65
63
|
`excluded:${ruleId}`,
|
|
@@ -81,7 +79,11 @@ module.exports = function(core) {
|
|
|
81
79
|
name,
|
|
82
80
|
patchType,
|
|
83
81
|
around: (next, data) => {
|
|
84
|
-
|
|
82
|
+
const sourceContext = getSourceContext(RULE, ruleId);
|
|
83
|
+
if (!sourceContext) return next();
|
|
84
|
+
|
|
85
|
+
const { contentType } = sourceContext.responseData;
|
|
86
|
+
if (contentType && isSafeContentType(contentType)) return next();
|
|
85
87
|
|
|
86
88
|
const [str] = data.args;
|
|
87
89
|
|
|
@@ -96,7 +98,7 @@ module.exports = function(core) {
|
|
|
96
98
|
tracked: true,
|
|
97
99
|
value: strInfo.value,
|
|
98
100
|
}],
|
|
99
|
-
context: `response.${method}(${
|
|
101
|
+
context: `response.${method}('${strInfo.value}')`,
|
|
100
102
|
history: [strInfo],
|
|
101
103
|
name,
|
|
102
104
|
moduleName: 'express',
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
Rule: { UNVALIDATED_REDIRECT: ruleId },
|
|
21
20
|
DataflowTag: {
|
|
@@ -46,6 +45,7 @@ module.exports = function(core) {
|
|
|
46
45
|
patcher,
|
|
47
46
|
config,
|
|
48
47
|
assess: {
|
|
48
|
+
inspect, // todo: remove
|
|
49
49
|
getSourceContext,
|
|
50
50
|
eventFactory: { createSinkEvent },
|
|
51
51
|
dataflow: {
|
|
@@ -56,7 +56,6 @@ module.exports = function(core) {
|
|
|
56
56
|
} = core;
|
|
57
57
|
|
|
58
58
|
const unvalidatedRedirect = core.assess.dataflow.sinks.express.unvalidatedRedirect = {};
|
|
59
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
60
59
|
|
|
61
60
|
const safeTags = [
|
|
62
61
|
`excluded:${ruleId}`,
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
Rule: { UNVALIDATED_REDIRECT: ruleId },
|
|
21
20
|
DataflowTag: {
|
|
@@ -65,6 +64,7 @@ module.exports = function(core) {
|
|
|
65
64
|
depHooks,
|
|
66
65
|
patcher,
|
|
67
66
|
assess: {
|
|
67
|
+
inspect, // todo: remove
|
|
68
68
|
getSourceContext,
|
|
69
69
|
eventFactory: { createSinkEvent },
|
|
70
70
|
dataflow: {
|
|
@@ -76,8 +76,6 @@ module.exports = function(core) {
|
|
|
76
76
|
const unvalidatedRedirect =
|
|
77
77
|
(core.assess.dataflow.sinks.fastify.unvalidatedRedirect = {});
|
|
78
78
|
|
|
79
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
80
|
-
|
|
81
79
|
const safeTags = [
|
|
82
80
|
`excluded:${ruleId}`,
|
|
83
81
|
CUSTOM_ENCODED,
|
|
@@ -25,9 +25,8 @@ const {
|
|
|
25
25
|
},
|
|
26
26
|
FS_METHODS,
|
|
27
27
|
Rule: { PATH_TRAVERSAL: ruleId },
|
|
28
|
-
inspect,
|
|
29
28
|
isString,
|
|
30
|
-
|
|
29
|
+
ArrayPrototypeJoin,
|
|
31
30
|
} = require('@contrast/common');
|
|
32
31
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
33
32
|
|
|
@@ -36,6 +35,7 @@ module.exports = function(core) {
|
|
|
36
35
|
depHooks,
|
|
37
36
|
patcher,
|
|
38
37
|
assess: {
|
|
38
|
+
inspect, // todo: remove
|
|
39
39
|
getSourceContext,
|
|
40
40
|
eventFactory: { createSinkEvent },
|
|
41
41
|
dataflow: {
|
|
@@ -87,7 +87,7 @@ module.exports = function(core) {
|
|
|
87
87
|
name,
|
|
88
88
|
moduleName,
|
|
89
89
|
methodName: fullMethodName || methodName,
|
|
90
|
-
context: `${name}(${
|
|
90
|
+
context: `${name}(${ArrayPrototypeJoin.call(
|
|
91
91
|
args.map((a) => inspect(a.value)),
|
|
92
92
|
', '
|
|
93
93
|
)})`,
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
19
|
isString,
|
|
20
|
-
|
|
21
|
-
join,
|
|
20
|
+
ArrayPrototypeJoin,
|
|
22
21
|
DataflowTag: {
|
|
23
22
|
UNTRUSTED,
|
|
24
23
|
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
@@ -47,6 +46,7 @@ module.exports = function (core) {
|
|
|
47
46
|
logger,
|
|
48
47
|
patcher,
|
|
49
48
|
assess: {
|
|
49
|
+
inspect, // todo: remove
|
|
50
50
|
getSourceContext,
|
|
51
51
|
eventFactory: { createSinkEvent },
|
|
52
52
|
dataflow: {
|
|
@@ -114,7 +114,7 @@ module.exports = function (core) {
|
|
|
114
114
|
});
|
|
115
115
|
const event = createSinkEvent({
|
|
116
116
|
name,
|
|
117
|
-
context: `${name}(${
|
|
117
|
+
context: `${name}(${ArrayPrototypeJoin.call(
|
|
118
118
|
args.map((a) => a.inspectedValue),
|
|
119
119
|
', '
|
|
120
120
|
)})`,
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
Rule: { UNVALIDATED_REDIRECT: ruleId },
|
|
21
20
|
DataflowTag: {
|
|
@@ -46,6 +45,7 @@ module.exports = function(core) {
|
|
|
46
45
|
patcher,
|
|
47
46
|
config,
|
|
48
47
|
assess: {
|
|
48
|
+
inspect, // todo: remove
|
|
49
49
|
getSourceContext,
|
|
50
50
|
eventFactory: { createSinkEvent },
|
|
51
51
|
dataflow: {
|
|
@@ -56,7 +56,6 @@ module.exports = function(core) {
|
|
|
56
56
|
} = core;
|
|
57
57
|
|
|
58
58
|
const unvalidatedRedirect = core.assess.dataflow.sinks.hapi.unvalidatedRedirect = {};
|
|
59
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
60
59
|
|
|
61
60
|
const safeTags = [
|
|
62
61
|
`excluded:${ruleId}`,
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
const Url = require('url');
|
|
19
19
|
const {
|
|
20
|
-
inspect,
|
|
21
20
|
isString,
|
|
22
21
|
DataflowTag: {
|
|
23
22
|
UNTRUSTED,
|
|
@@ -46,6 +45,7 @@ module.exports = function(core) {
|
|
|
46
45
|
depHooks,
|
|
47
46
|
patcher,
|
|
48
47
|
assess: {
|
|
48
|
+
inspect, // todo: remove
|
|
49
49
|
getSourceContext,
|
|
50
50
|
eventFactory: { createSinkEvent },
|
|
51
51
|
dataflow: {
|
|
@@ -104,11 +104,12 @@ module.exports = function(core) {
|
|
|
104
104
|
pre(data) {
|
|
105
105
|
if (!getSourceContext(RULE, ruleId)) return;
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// url <string> |<URL>
|
|
108
|
+
const [urlArg] = data.args;
|
|
109
|
+
if (!urlArg) return;
|
|
109
110
|
|
|
110
111
|
['host', 'hostname', 'localAddress', 'protocol'].forEach((key) => {
|
|
111
|
-
const value = getValueFromReq(
|
|
112
|
+
const value = getValueFromReq(urlArg, key);
|
|
112
113
|
if (!value) return;
|
|
113
114
|
|
|
114
115
|
const strInfo = tracker.getData(value);
|
|
@@ -116,7 +117,7 @@ module.exports = function(core) {
|
|
|
116
117
|
|
|
117
118
|
if (containsTrustedLib(strInfo.stack)) return;
|
|
118
119
|
|
|
119
|
-
const arg0 = isString(
|
|
120
|
+
const arg0 = isString(urlArg) ? urlArg : inspect(urlArg);
|
|
120
121
|
const idx = arg0.indexOf(value);
|
|
121
122
|
const urlTags = createAppendTags({}, strInfo.tags, idx);
|
|
122
123
|
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
DataflowTag: {
|
|
21
20
|
UNTRUSTED,
|
|
@@ -47,6 +46,7 @@ module.exports = function(core) {
|
|
|
47
46
|
patcher,
|
|
48
47
|
config,
|
|
49
48
|
assess: {
|
|
49
|
+
inspect, // todo: remove
|
|
50
50
|
getSourceContext,
|
|
51
51
|
eventFactory: { createSinkEvent },
|
|
52
52
|
dataflow: {
|
|
@@ -56,7 +56,7 @@ module.exports = function(core) {
|
|
|
56
56
|
},
|
|
57
57
|
} = core;
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
const safeTags = [
|
|
61
61
|
`excluded:${ruleId}`,
|
|
62
62
|
CUSTOM_ENCODED,
|
|
@@ -23,7 +23,6 @@ const {
|
|
|
23
23
|
ALPHANUM_SPACE_HYPHEN,
|
|
24
24
|
LIMITED_CHARS,
|
|
25
25
|
},
|
|
26
|
-
inspect
|
|
27
26
|
} = require('@contrast/common');
|
|
28
27
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
29
28
|
const { patchType } = require('../common');
|
|
@@ -48,6 +47,7 @@ module.exports = function(core) {
|
|
|
48
47
|
depHooks,
|
|
49
48
|
patcher,
|
|
50
49
|
assess: {
|
|
50
|
+
inspect, // todo: remove
|
|
51
51
|
getSourceContext,
|
|
52
52
|
eventFactory: { createSinkEvent },
|
|
53
53
|
dataflow: {
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const util = require('util');
|
|
18
17
|
const {
|
|
19
18
|
traverseValues,
|
|
20
19
|
Rule: { NOSQL_INJECTION_MONGO: ruleId },
|
|
@@ -51,6 +50,7 @@ module.exports = function (core) {
|
|
|
51
50
|
logger,
|
|
52
51
|
patcher,
|
|
53
52
|
assess: {
|
|
53
|
+
inspect, // todo: remove
|
|
54
54
|
getSourceContext,
|
|
55
55
|
eventFactory: { createSinkEvent },
|
|
56
56
|
dataflow: {
|
|
@@ -61,7 +61,6 @@ module.exports = function (core) {
|
|
|
61
61
|
} = core;
|
|
62
62
|
|
|
63
63
|
const instr = core.assess.dataflow.sinks.marsdb = {};
|
|
64
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
65
64
|
|
|
66
65
|
function getVulnerabilityInfo(query) {
|
|
67
66
|
let vulnInfo = null;
|
|
@@ -28,7 +28,6 @@ const {
|
|
|
28
28
|
isNonEmptyObject,
|
|
29
29
|
traverseValues,
|
|
30
30
|
isString,
|
|
31
|
-
inspect
|
|
32
31
|
} = require('@contrast/common');
|
|
33
32
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
34
33
|
const utils = require('../../tag-utils');
|
|
@@ -83,6 +82,7 @@ module.exports = function (core) {
|
|
|
83
82
|
logger,
|
|
84
83
|
patcher,
|
|
85
84
|
assess: {
|
|
85
|
+
inspect, // todo: remove
|
|
86
86
|
getSourceContext,
|
|
87
87
|
eventFactory: { createSinkEvent },
|
|
88
88
|
dataflow: {
|
|
@@ -28,7 +28,6 @@ const {
|
|
|
28
28
|
UNTRUSTED
|
|
29
29
|
},
|
|
30
30
|
isString,
|
|
31
|
-
inspect,
|
|
32
31
|
} = require('@contrast/common');
|
|
33
32
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
34
33
|
|
|
@@ -54,6 +53,7 @@ module.exports = function(core) {
|
|
|
54
53
|
depHooks,
|
|
55
54
|
patcher,
|
|
56
55
|
assess: {
|
|
56
|
+
inspect, // todo: remove
|
|
57
57
|
getSourceContext,
|
|
58
58
|
eventFactory: { createSinkEvent },
|
|
59
59
|
dataflow: {
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
DataflowTag: {
|
|
21
20
|
CUSTOM_VALIDATED,
|
|
@@ -43,6 +42,7 @@ module.exports = function(core) {
|
|
|
43
42
|
depHooks,
|
|
44
43
|
patcher,
|
|
45
44
|
assess: {
|
|
45
|
+
inspect, // todo: remove
|
|
46
46
|
getSourceContext,
|
|
47
47
|
eventFactory: { createSinkEvent },
|
|
48
48
|
dataflow: {
|
|
@@ -60,8 +60,6 @@ module.exports = function(core) {
|
|
|
60
60
|
CUSTOM_ENCODED,
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
64
|
-
|
|
65
63
|
const postgres = core.assess.dataflow.sinks.postgres = {};
|
|
66
64
|
|
|
67
65
|
const preHook = (methodSignature) => (data) => {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 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 {
|
|
19
|
+
Rule: { UNVALIDATED_REDIRECT: ruleId },
|
|
20
|
+
DataflowTag: {
|
|
21
|
+
UNTRUSTED,
|
|
22
|
+
CUSTOM_ENCODED,
|
|
23
|
+
CUSTOM_VALIDATED,
|
|
24
|
+
HTML_ENCODED,
|
|
25
|
+
LIMITED_CHARS,
|
|
26
|
+
URL_ENCODED,
|
|
27
|
+
},
|
|
28
|
+
isString,
|
|
29
|
+
ArrayPrototypeJoin,
|
|
30
|
+
} = require('@contrast/common');
|
|
31
|
+
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
32
|
+
const { createAppendTags } = require('../../tag-utils');
|
|
33
|
+
const { patchType } = require('../common');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {{
|
|
37
|
+
* assess: import('@contrast/assess').Assess,
|
|
38
|
+
* config: import('@contrast/config').Config,
|
|
39
|
+
* logger: import('@contrast/logger').Logger,
|
|
40
|
+
* }} core
|
|
41
|
+
* @returns {import('@contrast/common').Installable}
|
|
42
|
+
*/
|
|
43
|
+
module.exports = function(core) {
|
|
44
|
+
const {
|
|
45
|
+
depHooks,
|
|
46
|
+
patcher,
|
|
47
|
+
assess: {
|
|
48
|
+
getSourceContext,
|
|
49
|
+
eventFactory: { createSinkEvent },
|
|
50
|
+
dataflow: {
|
|
51
|
+
tracker,
|
|
52
|
+
sinks: { isVulnerable, reportFindings /*reportSafePositive*/ }
|
|
53
|
+
},
|
|
54
|
+
inspect,
|
|
55
|
+
},
|
|
56
|
+
} = core;
|
|
57
|
+
|
|
58
|
+
const OPTION_FIELDS = ['hostname', 'pathname'];
|
|
59
|
+
const SAFE_TAGS = [
|
|
60
|
+
`excluded:${ruleId}`,
|
|
61
|
+
CUSTOM_ENCODED,
|
|
62
|
+
CUSTOM_VALIDATED,
|
|
63
|
+
HTML_ENCODED,
|
|
64
|
+
LIMITED_CHARS,
|
|
65
|
+
URL_ENCODED,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function createCommonEventData(data) {
|
|
69
|
+
return {
|
|
70
|
+
name: 'restify.Response.redirect',
|
|
71
|
+
moduleName: 'restify',
|
|
72
|
+
methodName: 'Response.redirect',
|
|
73
|
+
object: {
|
|
74
|
+
tracked: false,
|
|
75
|
+
value: 'restify.Response',
|
|
76
|
+
},
|
|
77
|
+
result: {
|
|
78
|
+
tracked: false,
|
|
79
|
+
value: undefined,
|
|
80
|
+
},
|
|
81
|
+
stacktraceOpts: {
|
|
82
|
+
constructorOpt: data.hooked,
|
|
83
|
+
prependFrames: [data.orig]
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Called when tracked values are on options object argument.
|
|
90
|
+
* Will return the args coerced to strings values, and the adjusted ranges.
|
|
91
|
+
* @returns {}
|
|
92
|
+
*/
|
|
93
|
+
function getAdjustedValues(origArgs, vulns, vulnArgIdx) {
|
|
94
|
+
let result = '{...';
|
|
95
|
+
let tags;
|
|
96
|
+
|
|
97
|
+
vulns.forEach((vuln, i) => {
|
|
98
|
+
const sep = i == 0 ? '' : ',';
|
|
99
|
+
result += `${sep}'${vuln.path[vuln.path.length - 1]}':'`;
|
|
100
|
+
tags = createAppendTags(tags, vuln.strInfo.tags, result.length);
|
|
101
|
+
result += `${vuln.strInfo.value}'`;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
result += '...}';
|
|
105
|
+
|
|
106
|
+
const args = origArgs.map((a, i) => i == vulnArgIdx ?
|
|
107
|
+
{ tracked: true, value: result } :
|
|
108
|
+
{ tracked: false, value: a?.constructor?.name || typeof a });
|
|
109
|
+
|
|
110
|
+
return { args, tags };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return core.assess.dataflow.sinks.restify = {
|
|
114
|
+
install() {
|
|
115
|
+
// restify adds functionality to the built-in response via this patch function.
|
|
116
|
+
// once it returns the request, it'll have been decorated with redirect() method.
|
|
117
|
+
depHooks.resolve({ name: 'restify', file: 'lib/response.js' }, (responsePatch) => patcher.patch(responsePatch, {
|
|
118
|
+
name: 'restify.response.patch',
|
|
119
|
+
patchType,
|
|
120
|
+
post(data) {
|
|
121
|
+
patcher.patch(data.args[0].prototype, 'redirect', {
|
|
122
|
+
patchType,
|
|
123
|
+
name: 'restify.Response.redirect',
|
|
124
|
+
pre(data) {
|
|
125
|
+
if (!getSourceContext(RULE, ruleId)) return;
|
|
126
|
+
|
|
127
|
+
let vulnArgIdx;
|
|
128
|
+
let vulnArgIsString = true;
|
|
129
|
+
|
|
130
|
+
if (isString(data.args[0])) {
|
|
131
|
+
vulnArgIdx = 0; // res.redirect(url, next)
|
|
132
|
+
} else if (isString(data.args[1])) {
|
|
133
|
+
vulnArgIdx = 1; // res.redirect(code, url, next)
|
|
134
|
+
} else if (data.args[0] && typeof data.args[0] === 'object') {
|
|
135
|
+
vulnArgIdx = 0; // res.redirect(options, next)
|
|
136
|
+
vulnArgIsString = false;
|
|
137
|
+
} else {
|
|
138
|
+
return; // unknown call signature
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (vulnArgIsString) {
|
|
142
|
+
const strInfo = tracker.getData(data.args[vulnArgIdx]);
|
|
143
|
+
if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
|
|
144
|
+
const args = data.args.map((arg, idx) => ({
|
|
145
|
+
tracked: idx === vulnArgIdx,
|
|
146
|
+
value: idx === vulnArgIdx ?
|
|
147
|
+
inspect(strInfo.value) :
|
|
148
|
+
(arg?.constructor?.name ?? typeof strInfo.value)
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
const sinkEvent = createSinkEvent({
|
|
152
|
+
args,
|
|
153
|
+
context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
|
|
154
|
+
history: [strInfo],
|
|
155
|
+
tags: createAppendTags(null, strInfo.tags, 1), // offset by 1 for formatting as string
|
|
156
|
+
source: `P${vulnArgIdx}`,
|
|
157
|
+
...createCommonEventData(data),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (sinkEvent) {
|
|
161
|
+
reportFindings({ ruleId, sinkEvent });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const options = data.args[vulnArgIdx];
|
|
166
|
+
const vulns = [];
|
|
167
|
+
|
|
168
|
+
for (const option of OPTION_FIELDS) {
|
|
169
|
+
const strInfo = tracker.getData(options?.[option]);
|
|
170
|
+
if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
|
|
171
|
+
vulns.push({ path: [option], strInfo });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof options.query == 'object') {
|
|
176
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
177
|
+
const strInfo = tracker.getData(value);
|
|
178
|
+
if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
|
|
179
|
+
vulns.push({ path: ['query', key], strInfo });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (vulns.length) {
|
|
185
|
+
// so events are not duplicated
|
|
186
|
+
const history = Array.from(new Set(vulns.map((v) => v.strInfo)));
|
|
187
|
+
const { tags, args } = getAdjustedValues(data.args, vulns, vulnArgIdx);
|
|
188
|
+
const sinkEvent = createSinkEvent({
|
|
189
|
+
args,
|
|
190
|
+
context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
|
|
191
|
+
history,
|
|
192
|
+
tags,
|
|
193
|
+
source: 'P0',
|
|
194
|
+
...createCommonEventData(data),
|
|
195
|
+
|
|
196
|
+
});
|
|
197
|
+
if (sinkEvent) {
|
|
198
|
+
reportFindings({ ruleId, sinkEvent });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
};
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const util = require('util');
|
|
19
18
|
const {
|
|
20
19
|
Rule: { SQL_INJECTION: ruleId },
|
|
21
20
|
DataflowTag: {
|
|
@@ -42,6 +41,7 @@ module.exports = function (core) {
|
|
|
42
41
|
patcher,
|
|
43
42
|
config,
|
|
44
43
|
assess: {
|
|
44
|
+
inspect, // todo: remove
|
|
45
45
|
getSourceContext,
|
|
46
46
|
eventFactory: { createSinkEvent },
|
|
47
47
|
dataflow: {
|
|
@@ -59,7 +59,6 @@ module.exports = function (core) {
|
|
|
59
59
|
CUSTOM_ENCODED
|
|
60
60
|
];
|
|
61
61
|
const requiredTag = UNTRUSTED;
|
|
62
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
63
62
|
|
|
64
63
|
const sequelize = (core.assess.dataflow.sinks.sequelize = {});
|
|
65
64
|
|
|
@@ -25,11 +25,10 @@ const {
|
|
|
25
25
|
LIMITED_CHARS,
|
|
26
26
|
},
|
|
27
27
|
Rule: { UNSAFE_CODE_EXECUTION: ruleId },
|
|
28
|
-
inspect,
|
|
29
28
|
isNonEmptyObject,
|
|
30
29
|
isString,
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
ArrayPrototypeJoin,
|
|
31
|
+
StringPrototypeSplit,
|
|
33
32
|
traverseValues,
|
|
34
33
|
} = require('@contrast/common');
|
|
35
34
|
const { InstrumentationType: { RULE } } = require('../../../constants');
|
|
@@ -56,6 +55,7 @@ module.exports = function (core) {
|
|
|
56
55
|
depHooks,
|
|
57
56
|
patcher,
|
|
58
57
|
assess: {
|
|
58
|
+
inspect, // todo: remove
|
|
59
59
|
getSourceContext,
|
|
60
60
|
eventFactory: { createSinkEvent },
|
|
61
61
|
dataflow: {
|
|
@@ -144,7 +144,7 @@ module.exports = function (core) {
|
|
|
144
144
|
function around(next, { args: origArgs, hooked, orig, name }) {
|
|
145
145
|
if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return next();
|
|
146
146
|
|
|
147
|
-
const methodPath =
|
|
147
|
+
const methodPath = StringPrototypeSplit.call(name, '.');
|
|
148
148
|
const method = methodPath[methodPath.length - 1];
|
|
149
149
|
const idxsToCheck = method === 'runInNewContext' ? [0, 1] : [0];
|
|
150
150
|
const argsInfo = accumulateArgsInfo(origArgs, idxsToCheck);
|
|
@@ -204,7 +204,7 @@ module.exports = function (core) {
|
|
|
204
204
|
if (vulnerableArg) {
|
|
205
205
|
const event = createSinkEvent({
|
|
206
206
|
name,
|
|
207
|
-
context: `${name}(${
|
|
207
|
+
context: `${name}(${ArrayPrototypeJoin.call(
|
|
208
208
|
argsInfo.map((a) => a.ctxValue),
|
|
209
209
|
', '
|
|
210
210
|
)})`,
|