@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.
- package/lib/dataflow/event-factory.js +41 -83
- package/lib/dataflow/index.js +0 -1
- package/lib/dataflow/propagation/install/array-prototype-join.js +3 -3
- package/lib/dataflow/propagation/install/contrast-methods/add.js +23 -16
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +30 -22
- package/lib/dataflow/propagation/install/decode-uri-component.js +8 -5
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -5
- package/lib/dataflow/propagation/install/encode-uri-component.js +3 -3
- package/lib/dataflow/propagation/install/escape-html.js +10 -7
- package/lib/dataflow/propagation/install/escape.js +8 -5
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +8 -5
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +8 -5
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -3
- package/lib/dataflow/propagation/install/querystring/parse.js +11 -6
- package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
- package/lib/dataflow/propagation/install/string/concat.js +4 -4
- package/lib/dataflow/propagation/install/string/format-methods.js +2 -2
- package/lib/dataflow/propagation/install/string/html-methods.js +5 -5
- package/lib/dataflow/propagation/install/string/index.js +1 -0
- package/lib/dataflow/propagation/install/string/match.js +3 -3
- package/lib/dataflow/propagation/install/string/replace.js +9 -5
- package/lib/dataflow/propagation/install/string/slice.js +104 -0
- package/lib/dataflow/propagation/install/string/split.js +4 -4
- package/lib/dataflow/propagation/install/string/substring.js +6 -4
- package/lib/dataflow/propagation/install/string/trim.js +2 -2
- package/lib/dataflow/propagation/install/unescape.js +8 -5
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -3
- package/lib/dataflow/propagation/install/validator/hooks.js +2 -2
- package/lib/dataflow/propagation/install/validator/methods.js +60 -51
- package/lib/dataflow/sinks/index.js +15 -2
- package/lib/dataflow/sinks/install/child-process.js +224 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +47 -23
- package/lib/dataflow/sinks/install/fs.js +136 -0
- package/lib/dataflow/sinks/install/http.js +48 -32
- package/lib/dataflow/sinks/install/koa/index.js +30 -0
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +122 -0
- package/lib/dataflow/sinks/install/marsdb.js +135 -0
- package/lib/dataflow/sinks/install/mongodb.js +205 -0
- package/lib/dataflow/sinks/install/mssql.js +19 -13
- package/lib/dataflow/sinks/install/mysql.js +122 -0
- package/lib/dataflow/sinks/install/postgres.js +40 -29
- package/lib/dataflow/sinks/install/sqlite3.js +99 -0
- package/lib/dataflow/sources/handler.js +19 -15
- package/lib/dataflow/sources/index.js +9 -0
- package/lib/dataflow/sources/install/busboy1.js +112 -0
- package/lib/dataflow/sources/install/fastify/fastify.js +23 -29
- package/lib/dataflow/sources/install/fastify/index.js +4 -5
- package/lib/dataflow/sources/install/formidable1.js +91 -0
- package/lib/dataflow/sources/install/http.js +35 -14
- package/lib/dataflow/sources/install/koa/index.js +32 -0
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +92 -0
- package/lib/dataflow/sources/install/koa/koa-routers.js +84 -0
- package/lib/dataflow/sources/install/koa/koa2.js +103 -0
- package/lib/dataflow/sources/install/qs6.js +84 -0
- package/lib/dataflow/utils/is-vulnerable.js +1 -1
- package/package.json +2 -2
- package/lib/dataflow/signatures/index.js +0 -2006
- package/lib/dataflow/signatures/mssql.js +0 -49
- package/lib/dataflow/sources/install/fastify/cookie.js +0 -61
|
@@ -0,0 +1,224 @@
|
|
|
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 {
|
|
18
|
+
DataflowTag: { UNTRUSTED }
|
|
19
|
+
} = require('@contrast/common');
|
|
20
|
+
|
|
21
|
+
const { patchType } = require('../common');
|
|
22
|
+
const { Rule, isString, inspect } = require('@contrast/common');
|
|
23
|
+
|
|
24
|
+
module.exports = function (core) {
|
|
25
|
+
const {
|
|
26
|
+
depHooks,
|
|
27
|
+
patcher,
|
|
28
|
+
scopes: { sources },
|
|
29
|
+
assess: {
|
|
30
|
+
dataflow: {
|
|
31
|
+
tracker,
|
|
32
|
+
sinks: { isVulnerable, reportFindings },
|
|
33
|
+
eventFactory: { createSinkEvent },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} = core;
|
|
37
|
+
|
|
38
|
+
const safeTags = [];
|
|
39
|
+
|
|
40
|
+
function commandCheck(name, command, secondArg, thirdArg, hooked) {
|
|
41
|
+
const strInfo = tracker.getData(command);
|
|
42
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) return {
|
|
43
|
+
strInfo: null,
|
|
44
|
+
reported: false
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const event = createSinkEvent({
|
|
48
|
+
name,
|
|
49
|
+
history: [strInfo],
|
|
50
|
+
object: {
|
|
51
|
+
value: 'child_process',
|
|
52
|
+
tracked: false,
|
|
53
|
+
},
|
|
54
|
+
args: [
|
|
55
|
+
{
|
|
56
|
+
value: strInfo.value,
|
|
57
|
+
tracked: true,
|
|
58
|
+
},
|
|
59
|
+
(secondArg && {
|
|
60
|
+
value: inspect(secondArg),
|
|
61
|
+
tracked: false
|
|
62
|
+
}),
|
|
63
|
+
(thirdArg && {
|
|
64
|
+
value: inspect(thirdArg),
|
|
65
|
+
tracked: false
|
|
66
|
+
})
|
|
67
|
+
].filter(Boolean),
|
|
68
|
+
tags: strInfo.tags,
|
|
69
|
+
source: 'P0',
|
|
70
|
+
stacktraceOpts: {
|
|
71
|
+
constructorOpt: hooked,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (event) {
|
|
76
|
+
reportFindings({
|
|
77
|
+
ruleId: Rule.CMD_INJECTION,
|
|
78
|
+
sinkEvent: event,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
strInfo,
|
|
83
|
+
reported: true
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
strInfo,
|
|
89
|
+
reported: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function argumentsCheck(name, command, commandInfo, args, options, hooked) {
|
|
94
|
+
if (!Array.isArray(args) || !args?.length) return;
|
|
95
|
+
|
|
96
|
+
const trackedArgs = [];
|
|
97
|
+
let vulnerableArgIdx;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < args.length; i++) {
|
|
100
|
+
const trackData = tracker.getData(args[i]);
|
|
101
|
+
|
|
102
|
+
trackedArgs.push(trackData);
|
|
103
|
+
|
|
104
|
+
if (!trackData) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
!vulnerableArgIdx &&
|
|
110
|
+
vulnerableArgIdx != 0 &&
|
|
111
|
+
isVulnerable(UNTRUSTED, safeTags, trackData.tags)
|
|
112
|
+
) {
|
|
113
|
+
vulnerableArgIdx = i;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (vulnerableArgIdx != 0 && !vulnerableArgIdx) return;
|
|
118
|
+
|
|
119
|
+
const event = createSinkEvent({
|
|
120
|
+
name,
|
|
121
|
+
history: [trackedArgs[vulnerableArgIdx]],
|
|
122
|
+
object: {
|
|
123
|
+
value: 'child_process',
|
|
124
|
+
tracked: false,
|
|
125
|
+
},
|
|
126
|
+
args: [
|
|
127
|
+
{
|
|
128
|
+
value: commandInfo?.value || command,
|
|
129
|
+
tracked: !!commandInfo,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
value: inspect(args),
|
|
133
|
+
tracked: true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
value: inspect(options),
|
|
137
|
+
tracked: false
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
tags: trackedArgs[vulnerableArgIdx].tags,
|
|
141
|
+
source: 'P1',
|
|
142
|
+
stacktraceOpts: {
|
|
143
|
+
contructorOpt: hooked,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (event) {
|
|
148
|
+
reportFindings({
|
|
149
|
+
ruleId: Rule.CMD_INJECTION,
|
|
150
|
+
sinkEvent: event,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
core.assess.dataflow.sinks.cmdInjection = {
|
|
156
|
+
install() {
|
|
157
|
+
depHooks.resolve({ name: 'child_process' }, cp => {
|
|
158
|
+
['spawn', 'spawnSync'].forEach((method) => {
|
|
159
|
+
const name = `child_process.${method}`;
|
|
160
|
+
patcher.patch(cp, method, {
|
|
161
|
+
name,
|
|
162
|
+
patchType,
|
|
163
|
+
pre(data) {
|
|
164
|
+
const store = sources.getStore()?.assess;
|
|
165
|
+
const [command] = data.args;
|
|
166
|
+
|
|
167
|
+
if (!store || !command || !isString(command)) return;
|
|
168
|
+
|
|
169
|
+
const cpArgs = Array.isArray(data.args[1]) && data.args[1];
|
|
170
|
+
const options = cpArgs ? data.args[2] : data.args[1];
|
|
171
|
+
|
|
172
|
+
const cmdCheck = commandCheck(name, command, cpArgs, options, data.hooked);
|
|
173
|
+
|
|
174
|
+
if (cmdCheck.reported || !options?.shell) return;
|
|
175
|
+
|
|
176
|
+
argumentsCheck(name, command, cmdCheck.strInfo, cpArgs, options, data.hooked);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
['exec', 'execSync'].forEach((method) => {
|
|
182
|
+
const name = `child_process.${method}`;
|
|
183
|
+
patcher.patch(cp, method, {
|
|
184
|
+
name,
|
|
185
|
+
patchType,
|
|
186
|
+
pre(data) {
|
|
187
|
+
const store = sources.getStore()?.assess;
|
|
188
|
+
const [command, secondArg, thirdArg] = data.args;
|
|
189
|
+
|
|
190
|
+
if (!store || !command || !isString(command)) return;
|
|
191
|
+
|
|
192
|
+
commandCheck(name, command, secondArg, thirdArg, data.hooked);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
['execFile', 'execFileSync'].forEach((method) => {
|
|
198
|
+
const name = `child_process.${method}`;
|
|
199
|
+
patcher.patch(cp, method, {
|
|
200
|
+
name,
|
|
201
|
+
patchType,
|
|
202
|
+
pre(data) {
|
|
203
|
+
const store = sources.getStore()?.assess;
|
|
204
|
+
const [command] = data.args;
|
|
205
|
+
|
|
206
|
+
if (!store || !command || !isString(command)) return;
|
|
207
|
+
|
|
208
|
+
const cpArgs = Array.isArray(data.args[1]) && data.args[1];
|
|
209
|
+
const options = cpArgs ? data.args[2] : data.args[1];
|
|
210
|
+
|
|
211
|
+
if (!options?.shell) return;
|
|
212
|
+
|
|
213
|
+
const cmdInfo = tracker.getData(command);
|
|
214
|
+
|
|
215
|
+
argumentsCheck(name, command, cmdInfo, cpArgs, options, data.hooked);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return core.assess.dataflow.sinks.cmdInjection;
|
|
224
|
+
};
|
|
@@ -15,9 +15,20 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const {
|
|
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');
|
|
20
30
|
const { patchType } = require('../../common');
|
|
31
|
+
const { createSubsetTags } = require('../../../tag-utils');
|
|
21
32
|
|
|
22
33
|
module.exports = function (core) {
|
|
23
34
|
const {
|
|
@@ -35,14 +46,15 @@ module.exports = function (core) {
|
|
|
35
46
|
const unvalidatedRedirect =
|
|
36
47
|
(core.assess.dataflow.sinks.fastify.unvalidatedRedirect = {});
|
|
37
48
|
|
|
49
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
50
|
+
|
|
38
51
|
const safeTags = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
CUSTOM_ENCODED,
|
|
53
|
+
CUSTOM_VALIDATED,
|
|
54
|
+
HTML_ENCODED,
|
|
55
|
+
LIMITED_CHARS,
|
|
56
|
+
URL_ENCODED,
|
|
44
57
|
];
|
|
45
|
-
const requiredTag = 'untrusted';
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
60
|
* Patches `Reply.prototype.redirect` for
|
|
@@ -67,33 +79,45 @@ module.exports = function (core) {
|
|
|
67
79
|
const strInfo = tracker.getData(url);
|
|
68
80
|
if (!strInfo) return;
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
// todo: how does different tag logic play into display ranges?
|
|
83
|
+
|
|
84
|
+
let urlPathTags = strInfo.tags;
|
|
85
|
+
const urlPathEndIdx = url.indexOf('?');
|
|
86
|
+
|
|
87
|
+
if (urlPathEndIdx > -1) {
|
|
88
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, urlPathEndIdx);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
|
|
71
92
|
const event = createSinkEvent({
|
|
72
|
-
name: 'fastify.reply.redirect',
|
|
73
|
-
object: {
|
|
74
|
-
value: `[${createModuleLabel('fastify', version)}].Reply`,
|
|
75
|
-
isTracked: false,
|
|
76
|
-
},
|
|
77
|
-
history: [strInfo],
|
|
78
93
|
args: [{
|
|
94
|
+
tracked: true,
|
|
79
95
|
value: strInfo.value,
|
|
80
|
-
isTracked: true
|
|
81
96
|
}],
|
|
97
|
+
context: `reply.redirect(${inspect(strInfo.value)})`,
|
|
98
|
+
history: [strInfo],
|
|
99
|
+
name: 'fastify.reply.redirect',
|
|
100
|
+
object: {
|
|
101
|
+
tracked: false,
|
|
102
|
+
value: 'fastify.Reply',
|
|
103
|
+
},
|
|
82
104
|
result: {
|
|
83
|
-
|
|
84
|
-
|
|
105
|
+
tracked: false,
|
|
106
|
+
value: undefined,
|
|
85
107
|
},
|
|
86
|
-
tags:
|
|
108
|
+
tags: urlPathTags,
|
|
87
109
|
source: 'P0',
|
|
88
110
|
stacktraceOpts: {
|
|
89
111
|
constructorOpt: data.hooked,
|
|
90
112
|
},
|
|
91
113
|
});
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
if (event) {
|
|
116
|
+
reportFindings({
|
|
117
|
+
ruleId: 'unvalidated-redirect', // add Rule.UNVALIDATED_REDIRECT
|
|
118
|
+
sinkEvent: event,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
97
121
|
}
|
|
98
122
|
},
|
|
99
123
|
});
|
|
@@ -0,0 +1,136 @@
|
|
|
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 } = require('../common');
|
|
18
|
+
const { FS_METHODS, Rule, isString, DataflowTag: { URL_ENCODED, LIMITED_CHARS, ALPHANUM_SPACE_HYPHEN, SAFE_PATH, UNTRUSTED } } = require('@contrast/common');
|
|
19
|
+
|
|
20
|
+
module.exports = function (core) {
|
|
21
|
+
const {
|
|
22
|
+
depHooks,
|
|
23
|
+
patcher,
|
|
24
|
+
scopes: { sources },
|
|
25
|
+
assess: {
|
|
26
|
+
dataflow: {
|
|
27
|
+
tracker,
|
|
28
|
+
sinks: { isVulnerable, reportFindings },
|
|
29
|
+
eventFactory: { createSinkEvent },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} = core;
|
|
33
|
+
|
|
34
|
+
const safeTags = [URL_ENCODED, LIMITED_CHARS, ALPHANUM_SPACE_HYPHEN, SAFE_PATH];
|
|
35
|
+
|
|
36
|
+
function getValues(indices, args) {
|
|
37
|
+
return indices.reduce((acc, idx) => {
|
|
38
|
+
const value = args[idx];
|
|
39
|
+
if (value && isString(value)) acc.push(value);
|
|
40
|
+
return acc;
|
|
41
|
+
}, []);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pre = (name, indices) => (data) => {
|
|
45
|
+
const store = sources.getStore()?.assess;
|
|
46
|
+
if (!store) return;
|
|
47
|
+
|
|
48
|
+
const values = getValues(indices, data.args);
|
|
49
|
+
if (!values.length) return;
|
|
50
|
+
|
|
51
|
+
const args = [];
|
|
52
|
+
for (let i = 0; i < values.length; i++) {
|
|
53
|
+
const strInfo = tracker.getData(values[i]);
|
|
54
|
+
args.push({ value: strInfo ? strInfo.value : values[i], isTracked: !!strInfo });
|
|
55
|
+
if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const event = createSinkEvent({
|
|
60
|
+
name,
|
|
61
|
+
history: [strInfo],
|
|
62
|
+
object: {
|
|
63
|
+
value: 'fs',
|
|
64
|
+
isTracked: false,
|
|
65
|
+
},
|
|
66
|
+
args,
|
|
67
|
+
tags: strInfo.tags,
|
|
68
|
+
source: `P${i}`,
|
|
69
|
+
stacktraceOpts: {
|
|
70
|
+
contructorOpt: data.hooked,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (event) {
|
|
75
|
+
reportFindings({
|
|
76
|
+
ruleId: Rule.PATH_TRAVERSAL,
|
|
77
|
+
sinkEvent: event,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
core.assess.dataflow.sinks.pathTraversal = {
|
|
84
|
+
install() {
|
|
85
|
+
depHooks.resolve({ name: 'fs' }, (fs) => {
|
|
86
|
+
for (const method of FS_METHODS) {
|
|
87
|
+
// not all methods are available on every OS or Node version.
|
|
88
|
+
if (fs[method.name]) {
|
|
89
|
+
const name = `fs.${method.name}`;
|
|
90
|
+
patcher.patch(fs, method.name, {
|
|
91
|
+
name,
|
|
92
|
+
patchType,
|
|
93
|
+
pre: pre(name, method.indices)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (method.sync) {
|
|
98
|
+
const syncName = `${method.name}Sync`;
|
|
99
|
+
if (fs[syncName]) {
|
|
100
|
+
const name = `fs.${syncName}`;
|
|
101
|
+
patcher.patch(fs, syncName, {
|
|
102
|
+
name,
|
|
103
|
+
patchType,
|
|
104
|
+
pre: pre(name, method.indices)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (method.promises && fs.promises && fs.promises[method.name]) {
|
|
110
|
+
const name = `fs.promises.${method.name}`;
|
|
111
|
+
patcher.patch(fs.promises, method.name, {
|
|
112
|
+
name,
|
|
113
|
+
patchType,
|
|
114
|
+
pre: pre(name, method.indices)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
depHooks.resolve({ name: 'fs/promises' }, (fsPromises) => {
|
|
121
|
+
for (const method of FS_METHODS) {
|
|
122
|
+
if (method.promises && fsPromises[method.name]) {
|
|
123
|
+
const name = `fsPromises.${method.name}`;
|
|
124
|
+
patcher.patch(fsPromises, method.name, {
|
|
125
|
+
name,
|
|
126
|
+
patchType,
|
|
127
|
+
pre: pre(name, method.indices)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return core.assess.dataflow.sinks.pathTraversal;
|
|
136
|
+
};
|
|
@@ -15,8 +15,22 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const {
|
|
19
|
-
|
|
18
|
+
const {
|
|
19
|
+
DataflowTag: {
|
|
20
|
+
UNTRUSTED,
|
|
21
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
22
|
+
COOKIE,
|
|
23
|
+
CUSTOM_ENCODED,
|
|
24
|
+
CUSTOM_VALIDATED,
|
|
25
|
+
HEADER,
|
|
26
|
+
HTML_ENCODED,
|
|
27
|
+
LIMITED_CHARS,
|
|
28
|
+
SQL_ENCODED,
|
|
29
|
+
URL_ENCODED,
|
|
30
|
+
WEAK_URL_ENCODED,
|
|
31
|
+
},
|
|
32
|
+
Rule,
|
|
33
|
+
} = require('@contrast/common');
|
|
20
34
|
const { patchType } = require('../common');
|
|
21
35
|
|
|
22
36
|
module.exports = function(core) {
|
|
@@ -35,20 +49,19 @@ module.exports = function(core) {
|
|
|
35
49
|
const http = core.assess.dataflow.sinks.http = {};
|
|
36
50
|
|
|
37
51
|
const safeTags = [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
ALPHANUM_SPACE_HYPHEN,
|
|
53
|
+
COOKIE,
|
|
54
|
+
CUSTOM_ENCODED,
|
|
55
|
+
CUSTOM_VALIDATED,
|
|
56
|
+
HEADER,
|
|
57
|
+
HTML_ENCODED,
|
|
58
|
+
LIMITED_CHARS,
|
|
59
|
+
SQL_ENCODED,
|
|
60
|
+
URL_ENCODED,
|
|
61
|
+
WEAK_URL_ENCODED,
|
|
48
62
|
];
|
|
49
|
-
const requiredTag = 'untrusted';
|
|
50
63
|
|
|
51
|
-
const preHook = (name) => (data) => {
|
|
64
|
+
const preHook = (name, method) => (data) => {
|
|
52
65
|
const sourceContext = sources.getStore()?.assess;
|
|
53
66
|
if (!sourceContext) return;
|
|
54
67
|
|
|
@@ -61,34 +74,35 @@ module.exports = function(core) {
|
|
|
61
74
|
const { contentType } = sourceContext.responseData;
|
|
62
75
|
if (contentType && isSafeContentType(contentType)) return;
|
|
63
76
|
|
|
64
|
-
if (isVulnerable(
|
|
77
|
+
if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
65
78
|
const event = createSinkEvent({
|
|
66
|
-
ruleId: Rule.REFLECTED_XSS,
|
|
67
|
-
name,
|
|
68
|
-
history: [strInfo],
|
|
69
|
-
object: {
|
|
70
|
-
isTracked: false,
|
|
71
|
-
value: createObjectLabel('http.ServerResponse')
|
|
72
|
-
},
|
|
73
79
|
args: [{
|
|
74
80
|
value: strInfo.value,
|
|
75
|
-
|
|
81
|
+
tracked: true
|
|
76
82
|
}],
|
|
83
|
+
context: `res.${method}(${strInfo.value})`,
|
|
84
|
+
history: [strInfo],
|
|
85
|
+
name,
|
|
86
|
+
object: {
|
|
87
|
+
tracked: false,
|
|
88
|
+
value: 'http.ServerResponse'
|
|
89
|
+
},
|
|
77
90
|
result: {
|
|
78
|
-
value:
|
|
79
|
-
|
|
91
|
+
value: data.result,
|
|
92
|
+
tracked: false,
|
|
80
93
|
},
|
|
81
|
-
|
|
94
|
+
ruleId: Rule.REFLECTED_XSS,
|
|
82
95
|
source: 'P0',
|
|
83
96
|
stacktraceOpts: {
|
|
84
97
|
constructorOpt: data.hooked
|
|
85
|
-
}
|
|
98
|
+
},
|
|
99
|
+
tags: strInfo.tags,
|
|
86
100
|
});
|
|
87
101
|
|
|
88
102
|
if (event) {
|
|
89
103
|
reportFindings({
|
|
90
104
|
ruleId: 'reflected-xss',
|
|
91
|
-
|
|
105
|
+
sinkEvent: event
|
|
92
106
|
});
|
|
93
107
|
}
|
|
94
108
|
}
|
|
@@ -98,18 +112,20 @@ module.exports = function(core) {
|
|
|
98
112
|
depHooks.resolve({ name: 'http' }, (http) => {
|
|
99
113
|
{
|
|
100
114
|
const name = 'http.ServerResponse.prototype.write';
|
|
101
|
-
|
|
115
|
+
const method = 'write';
|
|
116
|
+
patcher.patch(http.ServerResponse.prototype, method, {
|
|
102
117
|
name,
|
|
103
118
|
patchType,
|
|
104
|
-
pre: preHook(name),
|
|
119
|
+
pre: preHook(name, method),
|
|
105
120
|
});
|
|
106
121
|
}
|
|
107
122
|
{
|
|
108
123
|
const name = 'http.ServerResponse.prototype.end';
|
|
109
|
-
|
|
124
|
+
const method = 'end';
|
|
125
|
+
patcher.patch(http.ServerResponse.prototype, method, {
|
|
110
126
|
name,
|
|
111
127
|
patchType,
|
|
112
|
-
pre: preHook(name),
|
|
128
|
+
pre: preHook(name, method),
|
|
113
129
|
});
|
|
114
130
|
}
|
|
115
131
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
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(core) {
|
|
21
|
+
const koa = core.assess.dataflow.sinks.koa = {};
|
|
22
|
+
|
|
23
|
+
require('./unvalidated-redirect')(core);
|
|
24
|
+
|
|
25
|
+
koa.install = function() {
|
|
26
|
+
callChildComponentMethodsSync(koa, 'install');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return koa;
|
|
30
|
+
};
|