@contrast/assess 1.9.0 → 1.10.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/dataflow/propagation/index.js +1 -0
- package/lib/dataflow/propagation/install/path/basename.js +124 -0
- package/lib/dataflow/propagation/install/path/common.js +176 -0
- package/lib/dataflow/propagation/install/path/index.js +32 -0
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
- package/lib/dataflow/propagation/install/path/normalize.js +123 -0
- package/lib/dataflow/propagation/install/querystring/parse.js +1 -1
- package/lib/dataflow/propagation/install/string/match.js +2 -2
- package/lib/dataflow/propagation/install/string/replace.js +1 -1
- package/lib/dataflow/propagation/install/string/slice.js +1 -1
- package/lib/dataflow/propagation/install/string/split.js +1 -1
- package/lib/dataflow/propagation/install/string/substring.js +2 -2
- package/lib/dataflow/propagation/install/string/trim.js +1 -1
- package/lib/dataflow/propagation/install/url/index.js +1 -0
- package/lib/dataflow/propagation/install/url/url.js +228 -0
- package/lib/dataflow/sinks/index.js +8 -4
- package/lib/dataflow/sinks/install/eval.js +138 -0
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +1 -1
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +2 -1
- package/lib/dataflow/sinks/install/fs.js +3 -3
- package/lib/dataflow/sinks/install/function.js +160 -0
- package/lib/dataflow/sinks/install/http/index.js +31 -0
- package/lib/dataflow/sinks/install/http/request.js +152 -0
- package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +2 -2
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +1 -1
- package/lib/dataflow/sinks/install/mongodb.js +4 -23
- package/lib/dataflow/sinks/install/mssql.js +44 -31
- package/lib/dataflow/sinks/install/vm.js +276 -0
- package/lib/dataflow/tag-utils.js +70 -1
- package/package.json +2 -2
|
@@ -0,0 +1,152 @@
|
|
|
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 {
|
|
19
|
+
inspect,
|
|
20
|
+
isString,
|
|
21
|
+
DataflowTag: {
|
|
22
|
+
UNTRUSTED,
|
|
23
|
+
CUSTOM_ENCODED_SSRF,
|
|
24
|
+
CUSTOM_ENCODED,
|
|
25
|
+
CUSTOM_VALIDATED_SSRF,
|
|
26
|
+
CUSTOM_VALIDATED,
|
|
27
|
+
LIMITED_CHARS
|
|
28
|
+
}
|
|
29
|
+
} = require('@contrast/common');
|
|
30
|
+
const Url = require('url');
|
|
31
|
+
const trustedLibs = [/^(?!.*(newrelic)).*http.*$/];
|
|
32
|
+
const { patchType } = require('../../common');
|
|
33
|
+
const { createAppendTags } = require('../../../tag-utils');
|
|
34
|
+
|
|
35
|
+
module.exports = function(core) {
|
|
36
|
+
const {
|
|
37
|
+
depHooks,
|
|
38
|
+
patcher,
|
|
39
|
+
scopes: { sources },
|
|
40
|
+
assess: {
|
|
41
|
+
dataflow: {
|
|
42
|
+
tracker,
|
|
43
|
+
sinks: {
|
|
44
|
+
isVulnerable,
|
|
45
|
+
reportFindings
|
|
46
|
+
},
|
|
47
|
+
eventFactory: { createSinkEvent },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
} = core;
|
|
51
|
+
const http = core.assess.dataflow.sinks.http.request = {};
|
|
52
|
+
|
|
53
|
+
const safeTags = [
|
|
54
|
+
CUSTOM_ENCODED_SSRF,
|
|
55
|
+
CUSTOM_ENCODED,
|
|
56
|
+
CUSTOM_VALIDATED_SSRF,
|
|
57
|
+
CUSTOM_VALIDATED,
|
|
58
|
+
LIMITED_CHARS
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function getValueFromReq(value, key) {
|
|
62
|
+
if (isString(value)) {
|
|
63
|
+
const url = new Url.URL(value);
|
|
64
|
+
if (isString(url[key])) {
|
|
65
|
+
return url[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isString(value[key])) {
|
|
70
|
+
return value[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function containsTrustedLib(stack) {
|
|
75
|
+
for (const { file } of stack) {
|
|
76
|
+
for (const trusted of trustedLibs) {
|
|
77
|
+
if (trusted.exec(file)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
http.install = function() {
|
|
87
|
+
['http', 'https'].forEach((moduleName) => {
|
|
88
|
+
depHooks.resolve({ name: moduleName }, (module) => {
|
|
89
|
+
const name = `${moduleName}.request`;
|
|
90
|
+
const methodName = 'request';
|
|
91
|
+
patcher.patch(module, methodName, {
|
|
92
|
+
name,
|
|
93
|
+
patchType,
|
|
94
|
+
pre(data) {
|
|
95
|
+
const sourceContext = sources.getStore()?.assess;
|
|
96
|
+
if (!sourceContext) return;
|
|
97
|
+
|
|
98
|
+
const [req] = data.args;
|
|
99
|
+
if (!req) return;
|
|
100
|
+
|
|
101
|
+
['host', 'hostname', 'localAddress', 'protocol'].forEach((key) => {
|
|
102
|
+
const value = getValueFromReq(req, key);
|
|
103
|
+
if (!value) return;
|
|
104
|
+
|
|
105
|
+
const strInfo = tracker.getData(value);
|
|
106
|
+
if (!strInfo) return;
|
|
107
|
+
|
|
108
|
+
if (containsTrustedLib(strInfo.stack)) return;
|
|
109
|
+
|
|
110
|
+
const arg0 = isString(req) ? req : inspect(req);
|
|
111
|
+
const idx = arg0.indexOf(value);
|
|
112
|
+
const urlTags = createAppendTags({}, strInfo.tags, idx);
|
|
113
|
+
|
|
114
|
+
if (urlTags && isVulnerable(UNTRUSTED, safeTags, urlTags)) {
|
|
115
|
+
const event = createSinkEvent({
|
|
116
|
+
name,
|
|
117
|
+
moduleName,
|
|
118
|
+
methodName: 'request',
|
|
119
|
+
context: `${moduleName}.request(${arg0})`,
|
|
120
|
+
history: [strInfo],
|
|
121
|
+
object: {
|
|
122
|
+
tracked: false,
|
|
123
|
+
value: moduleName
|
|
124
|
+
},
|
|
125
|
+
args: [{
|
|
126
|
+
value: arg0,
|
|
127
|
+
tracked: true
|
|
128
|
+
}],
|
|
129
|
+
source: 'P0',
|
|
130
|
+
stacktraceOpts: {
|
|
131
|
+
constructorOpt: data.hooked,
|
|
132
|
+
prependFrames: [data.orig]
|
|
133
|
+
},
|
|
134
|
+
tags: urlTags,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (event) {
|
|
138
|
+
reportFindings({
|
|
139
|
+
ruleId: 'ssrf',
|
|
140
|
+
sinkEvent: event
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return http;
|
|
152
|
+
};
|
|
@@ -31,7 +31,7 @@ const {
|
|
|
31
31
|
},
|
|
32
32
|
Rule: { REFLECTED_XSS: ruleId },
|
|
33
33
|
} = require('@contrast/common');
|
|
34
|
-
const { patchType, filterSafeTags } = require('
|
|
34
|
+
const { patchType, filterSafeTags } = require('../../common');
|
|
35
35
|
|
|
36
36
|
module.exports = function(core) {
|
|
37
37
|
const {
|
|
@@ -52,7 +52,7 @@ module.exports = function(core) {
|
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
} = core;
|
|
55
|
-
const http = core.assess.dataflow.sinks.http = {};
|
|
55
|
+
const http = core.assess.dataflow.sinks.http.serverResponse = {};
|
|
56
56
|
|
|
57
57
|
const safeTags = [
|
|
58
58
|
ALPHANUM_SPACE_HYPHEN,
|
|
@@ -84,7 +84,7 @@ module.exports = function(core) {
|
|
|
84
84
|
let urlPathTags = strInfo.tags;
|
|
85
85
|
|
|
86
86
|
if (url.indexOf('?') > -1) {
|
|
87
|
-
urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?'));
|
|
87
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?') + 1);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
if (urlPathTags && isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
|
|
@@ -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,
|
|
@@ -28,7 +27,8 @@ const {
|
|
|
28
27
|
Rule: { NOSQL_INJECTION_MONGO },
|
|
29
28
|
isNonEmptyObject,
|
|
30
29
|
traverseValues,
|
|
31
|
-
isString
|
|
30
|
+
isString,
|
|
31
|
+
inspect
|
|
32
32
|
} = require('@contrast/common');
|
|
33
33
|
const utils = require('../../tag-utils');
|
|
34
34
|
const { patchType, filterSafeTags } = require('../common');
|
|
@@ -82,7 +82,6 @@ module.exports = function(core) {
|
|
|
82
82
|
}
|
|
83
83
|
} = core;
|
|
84
84
|
|
|
85
|
-
const inspect = patcher.unwrap(util.inspect);
|
|
86
85
|
const instr = core.assess.dataflow.sinks.mongodb = {};
|
|
87
86
|
|
|
88
87
|
instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(query) {
|
|
@@ -256,7 +255,7 @@ module.exports = function(core) {
|
|
|
256
255
|
if (safeReports.length && config.assess.safe_positives.enable) {
|
|
257
256
|
const safeTags = safeReports.map((report) => filterSafeTags(querySafeTags, report.strInfo));
|
|
258
257
|
const strInfo = safeReports.map((report) => {
|
|
259
|
-
const tags = report.path ?
|
|
258
|
+
const tags = report.path ? utils.createAdjustedQueryTags(report.path, report.strInfo.tags, report.strInfo.value, inspect(origArgs[report.argIdx], { depth: 4 })) : report.strInfo?.tags;
|
|
260
259
|
|
|
261
260
|
return {
|
|
262
261
|
value: inspect(origArgs[report.argIdx], { depth: 4 }),
|
|
@@ -282,7 +281,7 @@ module.exports = function(core) {
|
|
|
282
281
|
tracked: idx === vulnArgIdx,
|
|
283
282
|
}));
|
|
284
283
|
|
|
285
|
-
const tags = path ?
|
|
284
|
+
const tags = path ? utils.createAdjustedQueryTags(path, strInfo.tags, strInfo.value, args[vulnArgIdx].value) : strInfo?.tags;
|
|
286
285
|
const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
|
|
287
286
|
const sinkEvent = createSinkEvent({
|
|
288
287
|
args,
|
|
@@ -401,22 +400,4 @@ module.exports = function(core) {
|
|
|
401
400
|
|
|
402
401
|
return name;
|
|
403
402
|
}
|
|
404
|
-
|
|
405
|
-
function getAdjustedQueryTags(path, strInfo, argString) {
|
|
406
|
-
const { tags } = strInfo;
|
|
407
|
-
let idx = -1;
|
|
408
|
-
for (const str of [...path, strInfo.value]) {
|
|
409
|
-
// This is the case with the `.aggregate` method
|
|
410
|
-
// where the argument is an array
|
|
411
|
-
if (str === 0) continue;
|
|
412
|
-
|
|
413
|
-
idx = argString.indexOf(str, idx);
|
|
414
|
-
if (idx == -1) {
|
|
415
|
-
idx = -1;
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return idx > 0 ? utils.createAppendTags([], tags, idx) : strInfo.tags;
|
|
421
|
-
}
|
|
422
403
|
};
|
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
isString
|
|
22
22
|
} = require('@contrast/common');
|
|
23
23
|
const { createModuleLabel } = require('../../propagation/common');
|
|
24
|
-
const { patchType } = require('../common');
|
|
24
|
+
const { patchType, filterSafeTags } = require('../common');
|
|
25
25
|
|
|
26
26
|
const safeTags = [
|
|
27
27
|
SQL_ENCODED,
|
|
@@ -30,15 +30,18 @@ const safeTags = [
|
|
|
30
30
|
CUSTOM_ENCODED,
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
+
const ruleId = SQL_INJECTION;
|
|
34
|
+
|
|
33
35
|
module.exports = function(core) {
|
|
34
36
|
const {
|
|
35
37
|
depHooks,
|
|
36
38
|
patcher,
|
|
39
|
+
config,
|
|
37
40
|
scopes: { sources },
|
|
38
41
|
assess: {
|
|
39
42
|
dataflow: {
|
|
40
43
|
tracker,
|
|
41
|
-
sinks: { isVulnerable, isLocked, reportFindings },
|
|
44
|
+
sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
|
|
42
45
|
eventFactory: { createSinkEvent },
|
|
43
46
|
},
|
|
44
47
|
},
|
|
@@ -54,38 +57,48 @@ module.exports = function(core) {
|
|
|
54
57
|
) return;
|
|
55
58
|
|
|
56
59
|
const strInfo = tracker.getData(data.args[0]);
|
|
57
|
-
if (!strInfo
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
+
if (!strInfo) return;
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
args: [
|
|
72
|
-
{
|
|
73
|
-
value: strInfo.value,
|
|
74
|
-
tracked: true,
|
|
62
|
+
if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
63
|
+
const event = createSinkEvent({
|
|
64
|
+
name,
|
|
65
|
+
moduleName: 'mssql',
|
|
66
|
+
methodName: `PreparedStatement.prototype.${method}`,
|
|
67
|
+
context: `mssql.PreparedStatement.${method}('${strInfo.value}')`,
|
|
68
|
+
history: [strInfo],
|
|
69
|
+
object: {
|
|
70
|
+
value: `[${createModuleLabel('mssql', version)}].${obj}`,
|
|
71
|
+
tracked: false,
|
|
75
72
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
prependFrames: [data.orig]
|
|
84
|
+
},
|
|
85
|
+
});
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (event) {
|
|
88
|
+
reportFindings({
|
|
89
|
+
ruleId: SQL_INJECTION,
|
|
90
|
+
sinkEvent: event,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
} else if (config.assess.safe_positives.enable) {
|
|
94
|
+
reportSafePositive({
|
|
95
|
+
name,
|
|
96
|
+
ruleId,
|
|
97
|
+
safeTags: filterSafeTags(safeTags, strInfo),
|
|
98
|
+
strInfo: {
|
|
99
|
+
tags: strInfo.tags,
|
|
100
|
+
value: strInfo.value,
|
|
101
|
+
}
|
|
89
102
|
});
|
|
90
103
|
}
|
|
91
104
|
};
|
|
@@ -0,0 +1,276 @@
|
|
|
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, filterSafeTags } = require('../common');
|
|
18
|
+
const {
|
|
19
|
+
isString,
|
|
20
|
+
DataflowTag: {
|
|
21
|
+
UNTRUSTED,
|
|
22
|
+
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
23
|
+
CUSTOM_ENCODED,
|
|
24
|
+
CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
|
|
25
|
+
CUSTOM_VALIDATED,
|
|
26
|
+
LIMITED_CHARS,
|
|
27
|
+
},
|
|
28
|
+
isNonEmptyObject,
|
|
29
|
+
inspect,
|
|
30
|
+
traverseValues,
|
|
31
|
+
join,
|
|
32
|
+
split,
|
|
33
|
+
} = require('@contrast/common');
|
|
34
|
+
const { createAdjustedQueryTags } = require('../../tag-utils');
|
|
35
|
+
const ruleId = 'unsafe-code-execution';
|
|
36
|
+
const safeTags = [
|
|
37
|
+
CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
|
|
38
|
+
CUSTOM_ENCODED,
|
|
39
|
+
CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
|
|
40
|
+
CUSTOM_VALIDATED,
|
|
41
|
+
LIMITED_CHARS,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
module.exports = function(core) {
|
|
45
|
+
const {
|
|
46
|
+
config,
|
|
47
|
+
depHooks,
|
|
48
|
+
patcher,
|
|
49
|
+
scopes: { sources, instrumentation },
|
|
50
|
+
assess: {
|
|
51
|
+
dataflow: {
|
|
52
|
+
tracker,
|
|
53
|
+
sinks: {
|
|
54
|
+
isVulnerable,
|
|
55
|
+
runInActiveSink,
|
|
56
|
+
isLocked,
|
|
57
|
+
reportFindings,
|
|
58
|
+
reportSafePositive,
|
|
59
|
+
},
|
|
60
|
+
eventFactory: { createSinkEvent },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} = core;
|
|
64
|
+
|
|
65
|
+
function accumulateArgsInfo(origArgs, idxsToCheck) {
|
|
66
|
+
const argsInfo = [];
|
|
67
|
+
for (let i = 0; i < origArgs.length; i++) {
|
|
68
|
+
const arg = origArgs[i];
|
|
69
|
+
const infoObj = {
|
|
70
|
+
isStringArg: isString(arg),
|
|
71
|
+
idx: i,
|
|
72
|
+
sanitizedStrInfos: [],
|
|
73
|
+
sanitizedStrPaths: []
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
!arg ||
|
|
78
|
+
!idxsToCheck.includes(i) ||
|
|
79
|
+
(!infoObj.isStringArg && !isNonEmptyObject(arg))
|
|
80
|
+
) {
|
|
81
|
+
const inspectedArg = inspect(origArgs[i]);
|
|
82
|
+
infoObj.argsValue = { value: inspectedArg, tracked: false };
|
|
83
|
+
infoObj.ctxValue = inspectedArg;
|
|
84
|
+
argsInfo.push(infoObj);
|
|
85
|
+
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (infoObj.isStringArg) {
|
|
90
|
+
const strInfo = tracker.getData(arg);
|
|
91
|
+
infoObj.strInfo = strInfo;
|
|
92
|
+
infoObj.argsValue = {
|
|
93
|
+
value: strInfo ? strInfo.value : arg,
|
|
94
|
+
tracked: !!strInfo,
|
|
95
|
+
};
|
|
96
|
+
infoObj.ctxValue = `"${strInfo?.value || arg}"`;
|
|
97
|
+
infoObj.isVulnerableArg =
|
|
98
|
+
strInfo && isVulnerable(UNTRUSTED, safeTags, strInfo.tags);
|
|
99
|
+
if (strInfo && !infoObj.isVulnerableArg) {
|
|
100
|
+
infoObj.sanitizedStrInfos.push(strInfo);
|
|
101
|
+
infoObj.isSanitizedArg = true;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
traverseValues(
|
|
105
|
+
arg,
|
|
106
|
+
(path, _type, value) => {
|
|
107
|
+
const strInfo = tracker.getData(value);
|
|
108
|
+
infoObj.strInfo = strInfo;
|
|
109
|
+
infoObj.isVulnerableArg =
|
|
110
|
+
strInfo && isVulnerable(UNTRUSTED, safeTags, strInfo.tags);
|
|
111
|
+
if (infoObj.isVulnerableArg) {
|
|
112
|
+
infoObj.vulnerableArgPath = [...path];
|
|
113
|
+
return true;
|
|
114
|
+
} else if (strInfo) {
|
|
115
|
+
infoObj.sanitizedStrInfos.push(strInfo);
|
|
116
|
+
infoObj.sanitizedStrPaths.push([...path]);
|
|
117
|
+
infoObj.isSanitizedArg = true;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
100
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const inspectedArg = inspect(arg);
|
|
124
|
+
|
|
125
|
+
infoObj.argsValue = { value: inspectedArg, tracked: false };
|
|
126
|
+
infoObj.ctxValue = inspectedArg;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
argsInfo.push(infoObj);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return argsInfo;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function around(next, { args: origArgs, hooked, orig, name }) {
|
|
136
|
+
const store = sources.getStore()?.assess;
|
|
137
|
+
if (
|
|
138
|
+
!store ||
|
|
139
|
+
instrumentation.isLocked() ||
|
|
140
|
+
isLocked('unsafe-code-execution')
|
|
141
|
+
) {
|
|
142
|
+
return next();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const methodPath = split(name, '.');
|
|
146
|
+
const method = methodPath[methodPath.length - 1];
|
|
147
|
+
const idxsToCheck = method === 'runInNewContext' ? [0, 1] : [0];
|
|
148
|
+
const argsInfo = accumulateArgsInfo(origArgs, idxsToCheck);
|
|
149
|
+
const vulnerableArg = argsInfo.find((arg) => arg.isVulnerableArg);
|
|
150
|
+
const sanitizedArgs = argsInfo.filter((arg) => arg.isSanitizedArg);
|
|
151
|
+
|
|
152
|
+
if (!vulnerableArg && sanitizedArgs.length && config.assess.safe_positives.enable) {
|
|
153
|
+
const foundSafeTags = sanitizedArgs.reduce((a, c) => {
|
|
154
|
+
const tags = [];
|
|
155
|
+
|
|
156
|
+
c.sanitizedStrInfos.forEach((i) => {
|
|
157
|
+
tags.push(filterSafeTags(safeTags, i));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
tags.forEach((tag) => a.add(...tag));
|
|
161
|
+
return a;
|
|
162
|
+
}, new Set());
|
|
163
|
+
const strInfo = sanitizedArgs.map((arg) => {
|
|
164
|
+
const value = inspect(origArgs[arg.idx], { depth: 4 });
|
|
165
|
+
const tags = [];
|
|
166
|
+
const strValues = [];
|
|
167
|
+
arg.sanitizedStrInfos.forEach((info, idx) => {
|
|
168
|
+
strValues.push(info.value);
|
|
169
|
+
if (arg.isStringArg) {
|
|
170
|
+
tags.push(info.tags);
|
|
171
|
+
} else {
|
|
172
|
+
tags.push(
|
|
173
|
+
createAdjustedQueryTags(
|
|
174
|
+
arg.sanitizedStrPaths[idx] || [],
|
|
175
|
+
info.tags,
|
|
176
|
+
info.value,
|
|
177
|
+
value
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
value,
|
|
185
|
+
strValues,
|
|
186
|
+
tags,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
reportSafePositive({
|
|
191
|
+
name,
|
|
192
|
+
ruleId,
|
|
193
|
+
safeTags: Array.from(foundSafeTags),
|
|
194
|
+
strInfo,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return name === 'vm.runInNewContext'
|
|
198
|
+
? runInActiveSink('unsafe-code-execution', () => next())
|
|
199
|
+
: next();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (vulnerableArg) {
|
|
203
|
+
const event = createSinkEvent({
|
|
204
|
+
name,
|
|
205
|
+
context: `${name}(${join(
|
|
206
|
+
argsInfo.map((a) => a.ctxValue),
|
|
207
|
+
', '
|
|
208
|
+
)})`,
|
|
209
|
+
history: [vulnerableArg.strInfo],
|
|
210
|
+
object: {
|
|
211
|
+
value: methodPath.includes('prototype')
|
|
212
|
+
? 'vm.Script.prototype'
|
|
213
|
+
: 'vm',
|
|
214
|
+
tracked: false,
|
|
215
|
+
},
|
|
216
|
+
moduleName: 'vm',
|
|
217
|
+
methodName: method,
|
|
218
|
+
args: argsInfo.map((a) => a.argsValue),
|
|
219
|
+
tags: vulnerableArg.vulnerableArgPath?.length
|
|
220
|
+
? createAdjustedQueryTags(
|
|
221
|
+
vulnerableArg.vulnerableArgPath,
|
|
222
|
+
vulnerableArg.strInfo.tags,
|
|
223
|
+
vulnerableArg.strInfo.value,
|
|
224
|
+
vulnerableArg.argsValue.value
|
|
225
|
+
)
|
|
226
|
+
: vulnerableArg.strInfo.tags,
|
|
227
|
+
source: `P${vulnerableArg.idx}`,
|
|
228
|
+
stacktraceOpts: {
|
|
229
|
+
contructorOpt: hooked,
|
|
230
|
+
prependFrames: [orig],
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (event) {
|
|
235
|
+
reportFindings({
|
|
236
|
+
ruleId,
|
|
237
|
+
sinkEvent: event,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return name === 'vm.runInNewContext'
|
|
243
|
+
? runInActiveSink('unsafe-code-execution', () => next())
|
|
244
|
+
: next();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
core.assess.dataflow.sinks.vm = {
|
|
248
|
+
install() {
|
|
249
|
+
depHooks.resolve({ name: 'vm' }, (vm) => {
|
|
250
|
+
[
|
|
251
|
+
'Script',
|
|
252
|
+
'createScript',
|
|
253
|
+
'runInContext',
|
|
254
|
+
'runInThisContext',
|
|
255
|
+
'createContext',
|
|
256
|
+
'runInNewContext',
|
|
257
|
+
].forEach((method) => {
|
|
258
|
+
const name = `vm.${method}`;
|
|
259
|
+
patcher.patch(vm, method, {
|
|
260
|
+
name,
|
|
261
|
+
patchType,
|
|
262
|
+
around,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
patcher.patch(vm.Script.prototype, 'runInNewContext', {
|
|
267
|
+
name: 'vm.Script.prototype.runInNewContext',
|
|
268
|
+
patchType,
|
|
269
|
+
around,
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return core.assess.dataflow.sinks.vm;
|
|
276
|
+
};
|