@contrast/assess 1.3.0 → 1.5.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 +31 -78
- 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 +3 -3
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -3
- package/lib/dataflow/propagation/install/encode-uri-component.js +3 -3
- package/lib/dataflow/propagation/install/escape-html.js +3 -3
- package/lib/dataflow/propagation/install/escape.js +3 -3
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +3 -3
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +3 -3
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -3
- package/lib/dataflow/propagation/install/querystring/parse.js +3 -3
- 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 +5 -3
- package/lib/dataflow/propagation/install/string/match.js +122 -0
- package/lib/dataflow/propagation/install/string/replace.js +4 -4
- 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 +3 -3
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -3
- package/lib/dataflow/propagation/install/validator/hooks.js +2 -2
- package/lib/dataflow/sinks/index.js +11 -2
- package/lib/dataflow/sinks/install/child-process.js +87 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +29 -16
- package/lib/dataflow/sinks/install/http.js +21 -19
- package/lib/dataflow/sinks/install/koa/index.js +30 -0
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +113 -0
- package/lib/dataflow/sinks/install/mssql.js +8 -6
- package/lib/dataflow/sinks/install/postgres.js +27 -17
- package/lib/dataflow/sinks/install/sqlite3.js +94 -0
- package/lib/dataflow/sources/handler.js +9 -6
- 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 +40 -47
- 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 -1996
- package/lib/dataflow/signatures/mssql.js +0 -49
- 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
|
+
const { join } = require('@contrast/common');
|
|
18
|
+
const { patchType } = require('../../common');
|
|
19
|
+
const { createSubsetTags } = require('../../../tag-utils');
|
|
20
|
+
|
|
21
|
+
module.exports = function(core) {
|
|
22
|
+
const {
|
|
23
|
+
scopes: { sources, instrumentation },
|
|
24
|
+
patcher,
|
|
25
|
+
assess: {
|
|
26
|
+
dataflow: { tracker, eventFactory: { createPropagationEvent } }
|
|
27
|
+
}
|
|
28
|
+
} = core;
|
|
29
|
+
|
|
30
|
+
function getPropagationEvent(data, res, objInfo, start) {
|
|
31
|
+
const { name, args, result, hooked, orig } = data;
|
|
32
|
+
const tags = createSubsetTags(objInfo.tags, start, res.length - 1);
|
|
33
|
+
if (!tags) return;
|
|
34
|
+
|
|
35
|
+
return createPropagationEvent({
|
|
36
|
+
name,
|
|
37
|
+
history: [objInfo],
|
|
38
|
+
object: {
|
|
39
|
+
value: objInfo.value,
|
|
40
|
+
tracked: true,
|
|
41
|
+
},
|
|
42
|
+
args: args.map((arg) => {
|
|
43
|
+
const argInfo = tracker.getData(arg);
|
|
44
|
+
return {
|
|
45
|
+
value: argInfo ? argInfo.value : arg.toString(),
|
|
46
|
+
tracked: !!argInfo
|
|
47
|
+
};
|
|
48
|
+
}),
|
|
49
|
+
tags,
|
|
50
|
+
result: {
|
|
51
|
+
value: join(result),
|
|
52
|
+
tracked: false
|
|
53
|
+
},
|
|
54
|
+
stacktraceOpts: {
|
|
55
|
+
constructorOpt: hooked,
|
|
56
|
+
prependFrames: [orig]
|
|
57
|
+
},
|
|
58
|
+
source: 'O',
|
|
59
|
+
target: 'R'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return core.assess.dataflow.propagation.stringInstrumentation.match = {
|
|
64
|
+
install() {
|
|
65
|
+
const name = 'String.prototype.match';
|
|
66
|
+
|
|
67
|
+
patcher.patch(String.prototype, 'match', {
|
|
68
|
+
name,
|
|
69
|
+
patchType,
|
|
70
|
+
post(data) {
|
|
71
|
+
const { args, obj, result } = data;
|
|
72
|
+
if (
|
|
73
|
+
!obj ||
|
|
74
|
+
!result ||
|
|
75
|
+
args.length === 0 ||
|
|
76
|
+
result.length === 0 ||
|
|
77
|
+
!sources.getStore() ||
|
|
78
|
+
typeof obj !== 'string' ||
|
|
79
|
+
instrumentation.isLocked() ||
|
|
80
|
+
(args.length === 1 && args[0] == null)
|
|
81
|
+
) return;
|
|
82
|
+
|
|
83
|
+
const objInfo = tracker.getData(obj);
|
|
84
|
+
if (!objInfo) return;
|
|
85
|
+
|
|
86
|
+
let idx = 0;
|
|
87
|
+
const hasCaptureGroups = 'groups' in result;
|
|
88
|
+
for (let i = 0; i < result.length; i++) {
|
|
89
|
+
const res = result[i];
|
|
90
|
+
if (!res) continue;
|
|
91
|
+
const start = obj.indexOf(res, idx);
|
|
92
|
+
idx += hasCaptureGroups ? 0 : res.length;
|
|
93
|
+
const event = getPropagationEvent(data, res, objInfo, start);
|
|
94
|
+
if (event) {
|
|
95
|
+
const { extern } = tracker.track(res, event);
|
|
96
|
+
if (extern) {
|
|
97
|
+
data.result[i] = extern;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (hasCaptureGroups && result.groups) {
|
|
102
|
+
Object.keys(result.groups).forEach((key) => {
|
|
103
|
+
const res = result.groups[key];
|
|
104
|
+
if (!res) return;
|
|
105
|
+
const start = obj.indexOf(res);
|
|
106
|
+
const event = getPropagationEvent(data, res, objInfo, start);
|
|
107
|
+
if (event) {
|
|
108
|
+
const { extern } = tracker.track(res, event);
|
|
109
|
+
if (extern) {
|
|
110
|
+
data.result.groups[key] = extern;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
uninstall() {
|
|
119
|
+
String.prototype.match = patcher.unwrap(String.prototype.match);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -158,19 +158,19 @@ module.exports = function(core) {
|
|
|
158
158
|
history: Array.from(data._history),
|
|
159
159
|
object: {
|
|
160
160
|
value: obj,
|
|
161
|
-
|
|
161
|
+
tracked: !!data._objInfo
|
|
162
162
|
},
|
|
163
163
|
args: [{
|
|
164
164
|
value: args[0].toString(),
|
|
165
|
-
|
|
165
|
+
tracked: !!tracker.getData(args[0])
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
value: data._replacement,
|
|
169
|
-
|
|
169
|
+
tracked: !!_replacementInfo
|
|
170
170
|
}],
|
|
171
171
|
result: {
|
|
172
172
|
value: result,
|
|
173
|
-
|
|
173
|
+
tracked: true
|
|
174
174
|
},
|
|
175
175
|
tags: data._accumTags,
|
|
176
176
|
stacktraceOpts: {
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
const { patchType } = require('../../common');
|
|
17
|
+
const { createSubsetTags } = require('../../../tag-utils');
|
|
18
|
+
|
|
19
|
+
module.exports = function(core) {
|
|
20
|
+
const {
|
|
21
|
+
scopes: { sources, instrumentation },
|
|
22
|
+
patcher,
|
|
23
|
+
assess: {
|
|
24
|
+
dataflow: { tracker, eventFactory: { createPropagationEvent } }
|
|
25
|
+
}
|
|
26
|
+
} = core;
|
|
27
|
+
|
|
28
|
+
function calculateSubsetRangeForSlice({ obj, args }) {
|
|
29
|
+
let [start, end] = args;
|
|
30
|
+
const hasSingleArg = end === undefined;
|
|
31
|
+
|
|
32
|
+
if (start < 0 && hasSingleArg || start < 0 && end < 0) {
|
|
33
|
+
start = obj.length - Math.abs(start);
|
|
34
|
+
end = obj.length - (Math.abs(end) || 0);
|
|
35
|
+
} else if (start > 0 && end < 0) {
|
|
36
|
+
end = obj.length - (Math.abs(end) || 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const subsetLen = (hasSingleArg ? obj.length - start : Math.abs(end - start)) - 1;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
startIdx: start,
|
|
43
|
+
subsetLen
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return core.assess.dataflow.propagation.stringInstrumentation.slice = {
|
|
48
|
+
install() {
|
|
49
|
+
const name = 'String.prototype.slice';
|
|
50
|
+
|
|
51
|
+
patcher.patch(String.prototype, 'slice', {
|
|
52
|
+
name,
|
|
53
|
+
patchType,
|
|
54
|
+
post(data) {
|
|
55
|
+
const { name, args, obj, result, hooked, orig } = data;
|
|
56
|
+
if (!result || !sources.getStore() || instrumentation.isLocked()) return;
|
|
57
|
+
|
|
58
|
+
const objInfo = tracker.getData(obj);
|
|
59
|
+
if (!objInfo) return;
|
|
60
|
+
|
|
61
|
+
const rInfo = tracker.getData(result);
|
|
62
|
+
if (rInfo) {
|
|
63
|
+
// this may happen w/ trackedStr.slice(0) => trackedStr
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { startIdx, subsetLen } = calculateSubsetRangeForSlice(data);
|
|
68
|
+
const tags = createSubsetTags(objInfo.tags, startIdx, subsetLen);
|
|
69
|
+
if (!tags) return;
|
|
70
|
+
|
|
71
|
+
const event = createPropagationEvent({
|
|
72
|
+
name,
|
|
73
|
+
history: [objInfo],
|
|
74
|
+
object: {
|
|
75
|
+
value: obj,
|
|
76
|
+
tracked: true,
|
|
77
|
+
},
|
|
78
|
+
args: args.map((arg) => ({
|
|
79
|
+
value: arg.toString(),
|
|
80
|
+
tracked: false
|
|
81
|
+
})),
|
|
82
|
+
result: {
|
|
83
|
+
value: result,
|
|
84
|
+
tracked: true
|
|
85
|
+
},
|
|
86
|
+
tags,
|
|
87
|
+
stacktraceOpts: {
|
|
88
|
+
constructorOpt: hooked,
|
|
89
|
+
prependFrames: [orig]
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const { extern } = tracker.track(result, event);
|
|
93
|
+
|
|
94
|
+
if (extern) {
|
|
95
|
+
data.result = extern;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
uninstall() {
|
|
101
|
+
String.prototype.slice = patcher.unwrap(String.prototype.slice);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -50,7 +50,7 @@ module.exports = function(core) {
|
|
|
50
50
|
) return;
|
|
51
51
|
|
|
52
52
|
const objInfo = tracker.getData(obj);
|
|
53
|
-
if (!objInfo) return;
|
|
53
|
+
if (!objInfo || obj === result[0]) return;
|
|
54
54
|
|
|
55
55
|
let idx = 0;
|
|
56
56
|
for (let i = 0; i < result.length; i++) {
|
|
@@ -68,19 +68,19 @@ module.exports = function(core) {
|
|
|
68
68
|
history: [objInfo],
|
|
69
69
|
object: {
|
|
70
70
|
value: obj,
|
|
71
|
-
|
|
71
|
+
tracked: true,
|
|
72
72
|
},
|
|
73
73
|
args: args.map((arg) => {
|
|
74
74
|
const argInfo = tracker.getData(arg);
|
|
75
75
|
return {
|
|
76
76
|
value: argInfo ? argInfo.value : arg.toString(),
|
|
77
|
-
|
|
77
|
+
tracked: !!argInfo
|
|
78
78
|
};
|
|
79
79
|
}),
|
|
80
80
|
tags,
|
|
81
81
|
result: {
|
|
82
82
|
value: join(result),
|
|
83
|
-
|
|
83
|
+
tracked: false
|
|
84
84
|
},
|
|
85
85
|
stacktraceOpts: {
|
|
86
86
|
constructorOpt: hooked,
|
|
@@ -83,21 +83,23 @@ module.exports = function(core) {
|
|
|
83
83
|
history: [objInfo],
|
|
84
84
|
object: {
|
|
85
85
|
value: obj,
|
|
86
|
-
|
|
86
|
+
tracked: true,
|
|
87
87
|
},
|
|
88
88
|
args: args.map((arg) => ({
|
|
89
89
|
value: arg.toString(),
|
|
90
|
-
|
|
90
|
+
tracked: false
|
|
91
91
|
})),
|
|
92
92
|
result: {
|
|
93
93
|
value: result,
|
|
94
|
-
|
|
94
|
+
tracked: true
|
|
95
95
|
},
|
|
96
96
|
tags,
|
|
97
|
+
source: 'O',
|
|
97
98
|
stacktraceOpts: {
|
|
98
99
|
constructorOpt: hooked,
|
|
99
100
|
prependFrames: [orig]
|
|
100
|
-
}
|
|
101
|
+
},
|
|
102
|
+
target: 'R',
|
|
101
103
|
});
|
|
102
104
|
const { extern } = tracker.track(result, event);
|
|
103
105
|
|
|
@@ -53,13 +53,13 @@ module.exports = function(core) {
|
|
|
53
53
|
name: 'global.unescape',
|
|
54
54
|
object: {
|
|
55
55
|
value: createObjectLabel('global'),
|
|
56
|
-
|
|
56
|
+
tracked: false
|
|
57
57
|
},
|
|
58
58
|
result: {
|
|
59
59
|
value: resultInfo ? resultInfo.value : result,
|
|
60
|
-
|
|
60
|
+
tracked: true
|
|
61
61
|
},
|
|
62
|
-
args: [{ value: argInfo.value,
|
|
62
|
+
args: [{ value: argInfo.value, tracked: true }],
|
|
63
63
|
tags: newTags,
|
|
64
64
|
history,
|
|
65
65
|
removedTags: ['weak-url-encoded'],
|
|
@@ -52,13 +52,13 @@ module.exports = function(core) {
|
|
|
52
52
|
name: `url.${method}`,
|
|
53
53
|
object: {
|
|
54
54
|
value: createModuleLabel('url', version),
|
|
55
|
-
|
|
55
|
+
tracked: false
|
|
56
56
|
},
|
|
57
57
|
result: {
|
|
58
58
|
value: resultInfo ? resultInfo.value : result,
|
|
59
|
-
|
|
59
|
+
tracked: true
|
|
60
60
|
},
|
|
61
|
-
args: [{ value: argInfo.value,
|
|
61
|
+
args: [{ value: argInfo.value, tracked: true }],
|
|
62
62
|
tags: createFullLengthCopyTags(argInfo.tags, result.length) || [],
|
|
63
63
|
history,
|
|
64
64
|
source: 'P',
|
|
@@ -33,11 +33,11 @@ module.exports = function(core) {
|
|
|
33
33
|
history: [{ ...trackingData }],
|
|
34
34
|
args: [{
|
|
35
35
|
value: data.args[0],
|
|
36
|
-
|
|
36
|
+
tracked: true
|
|
37
37
|
}],
|
|
38
38
|
result: {
|
|
39
39
|
value: data.result,
|
|
40
|
-
|
|
40
|
+
tracked: !!tracker.getData(data.result)
|
|
41
41
|
},
|
|
42
42
|
tags: {
|
|
43
43
|
...trackingData.tags,
|
|
@@ -21,21 +21,30 @@ const { isSafeContentType } = require('../utils/is-safe-content-type');
|
|
|
21
21
|
|
|
22
22
|
module.exports = function (core) {
|
|
23
23
|
const {
|
|
24
|
-
messages
|
|
24
|
+
messages,
|
|
25
|
+
scopes: { sources }
|
|
25
26
|
} = core;
|
|
26
27
|
|
|
27
28
|
const sinks = core.assess.dataflow.sinks = {
|
|
28
29
|
isVulnerable,
|
|
29
30
|
isSafeContentType,
|
|
30
31
|
reportFindings(data) {
|
|
31
|
-
|
|
32
|
+
const store = sources.getStore();
|
|
33
|
+
// these events need source correlation
|
|
34
|
+
messages.emit(Event.ASSESS_DATAFLOW_FINDING, {
|
|
35
|
+
sourceInfo: store?.sourceInfo,
|
|
36
|
+
...data
|
|
37
|
+
});
|
|
32
38
|
},
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
require('./install/fastify')(core);
|
|
42
|
+
require('./install/koa')(core);
|
|
43
|
+
require('./install/child-process')(core);
|
|
36
44
|
require('./install/http')(core);
|
|
37
45
|
require('./install/mssql')(core);
|
|
38
46
|
require('./install/postgres')(core);
|
|
47
|
+
require('./install/sqlite3')(core);
|
|
39
48
|
|
|
40
49
|
sinks.install = function() {
|
|
41
50
|
callChildComponentMethodsSync(core.assess.dataflow.sinks, 'install');
|
|
@@ -0,0 +1,87 @@
|
|
|
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 { Rule, isString } = 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 pre = (name) => (data) => {
|
|
35
|
+
const store = sources.getStore()?.assess;
|
|
36
|
+
if (!store || !data.args[0] || !isString(data.args[0])) return;
|
|
37
|
+
|
|
38
|
+
const strInfo = tracker.getData(data.args[0]);
|
|
39
|
+
if (!strInfo || !isVulnerable('untrusted', [], strInfo.tags)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const event = createSinkEvent({
|
|
44
|
+
name,
|
|
45
|
+
history: [strInfo],
|
|
46
|
+
object: {
|
|
47
|
+
value: 'child_process',
|
|
48
|
+
tracked: false,
|
|
49
|
+
},
|
|
50
|
+
args: [
|
|
51
|
+
{
|
|
52
|
+
value: strInfo.value,
|
|
53
|
+
tracked: true,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
tags: strInfo.tags,
|
|
57
|
+
source: 'P0',
|
|
58
|
+
stacktraceOpts: {
|
|
59
|
+
contructorOpt: data.hooked,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (event) {
|
|
64
|
+
reportFindings({
|
|
65
|
+
ruleId: Rule.CMD_INJECTION,
|
|
66
|
+
sinkEvent: event,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
core.assess.dataflow.sinks.cmdInjection = {
|
|
72
|
+
install() {
|
|
73
|
+
depHooks.resolve({ name: 'child_process' }, cp => {
|
|
74
|
+
['spawn', 'spawnSync', 'exec', 'execSync'].forEach((method) => {
|
|
75
|
+
const name = `child_process.${method}`;
|
|
76
|
+
patcher.patch(cp, method, {
|
|
77
|
+
name,
|
|
78
|
+
patchType,
|
|
79
|
+
pre: pre(name)
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return core.assess.dataflow.sinks.cmdInjection;
|
|
87
|
+
};
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const util = require('util');
|
|
18
19
|
const { isString } = require('@contrast/common');
|
|
19
|
-
const { createModuleLabel } = require('../../../propagation/common');
|
|
20
20
|
const { patchType } = require('../../common');
|
|
21
|
+
const { createSubsetTags } = require('../../../tag-utils');
|
|
21
22
|
|
|
22
23
|
module.exports = function (core) {
|
|
23
24
|
const {
|
|
@@ -35,6 +36,8 @@ module.exports = function (core) {
|
|
|
35
36
|
const unvalidatedRedirect =
|
|
36
37
|
(core.assess.dataflow.sinks.fastify.unvalidatedRedirect = {});
|
|
37
38
|
|
|
39
|
+
const inspect = patcher.unwrap(util.inspect);
|
|
40
|
+
|
|
38
41
|
const safeTags = [
|
|
39
42
|
'limited-chars',
|
|
40
43
|
'url-encoded',
|
|
@@ -67,33 +70,43 @@ module.exports = function (core) {
|
|
|
67
70
|
const strInfo = tracker.getData(url);
|
|
68
71
|
if (!strInfo) return;
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
let urlPathTags = strInfo.tags;
|
|
74
|
+
const urlPathEndIdx = url.indexOf('?');
|
|
75
|
+
|
|
76
|
+
if (urlPathEndIdx > -1) {
|
|
77
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, urlPathEndIdx);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isVulnerable(requiredTag, safeTags, urlPathTags)) {
|
|
71
81
|
const event = createSinkEvent({
|
|
72
|
-
name: 'fastify.reply.redirect',
|
|
73
|
-
object: {
|
|
74
|
-
value: `[${createModuleLabel('fastify', version)}].Reply`,
|
|
75
|
-
isTracked: false,
|
|
76
|
-
},
|
|
77
|
-
history: [strInfo],
|
|
78
82
|
args: [{
|
|
83
|
+
tracked: true,
|
|
79
84
|
value: strInfo.value,
|
|
80
|
-
isTracked: true
|
|
81
85
|
}],
|
|
86
|
+
context: `reply.redirect(${inspect(strInfo.value)})`,
|
|
87
|
+
history: [strInfo],
|
|
88
|
+
name: 'fastify.reply.redirect',
|
|
89
|
+
object: {
|
|
90
|
+
tracked: false,
|
|
91
|
+
value: 'fastify.Reply',
|
|
92
|
+
},
|
|
82
93
|
result: {
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
tracked: false,
|
|
95
|
+
value: undefined,
|
|
85
96
|
},
|
|
86
|
-
tags:
|
|
97
|
+
tags: urlPathTags,
|
|
87
98
|
source: 'P0',
|
|
88
99
|
stacktraceOpts: {
|
|
89
100
|
constructorOpt: data.hooked,
|
|
90
101
|
},
|
|
91
102
|
});
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
if (event) {
|
|
105
|
+
reportFindings({
|
|
106
|
+
ruleId: 'unvalidated-redirect', // add Rule.UNVALIDATED_REDIRECT
|
|
107
|
+
sinkEvent: event,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
97
110
|
}
|
|
98
111
|
},
|
|
99
112
|
});
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { Rule } = require('@contrast/common');
|
|
19
|
-
const { createObjectLabel } = require('../../propagation/common');
|
|
20
19
|
const { patchType } = require('../common');
|
|
21
20
|
|
|
22
21
|
module.exports = function(core) {
|
|
@@ -48,7 +47,7 @@ module.exports = function(core) {
|
|
|
48
47
|
];
|
|
49
48
|
const requiredTag = 'untrusted';
|
|
50
49
|
|
|
51
|
-
const preHook = (name) => (data) => {
|
|
50
|
+
const preHook = (name, method) => (data) => {
|
|
52
51
|
const sourceContext = sources.getStore()?.assess;
|
|
53
52
|
if (!sourceContext) return;
|
|
54
53
|
|
|
@@ -63,32 +62,33 @@ module.exports = function(core) {
|
|
|
63
62
|
|
|
64
63
|
if (isVulnerable(requiredTag, safeTags, strInfo.tags)) {
|
|
65
64
|
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
65
|
args: [{
|
|
74
66
|
value: strInfo.value,
|
|
75
|
-
|
|
67
|
+
tracked: true
|
|
76
68
|
}],
|
|
69
|
+
context: `res.${method}(${strInfo.value})`,
|
|
70
|
+
history: [strInfo],
|
|
71
|
+
name,
|
|
72
|
+
object: {
|
|
73
|
+
tracked: false,
|
|
74
|
+
value: 'http.ServerResponse'
|
|
75
|
+
},
|
|
77
76
|
result: {
|
|
78
|
-
value:
|
|
79
|
-
|
|
77
|
+
value: data.result,
|
|
78
|
+
tracked: false,
|
|
80
79
|
},
|
|
81
|
-
|
|
80
|
+
ruleId: Rule.REFLECTED_XSS,
|
|
82
81
|
source: 'P0',
|
|
83
82
|
stacktraceOpts: {
|
|
84
83
|
constructorOpt: data.hooked
|
|
85
|
-
}
|
|
84
|
+
},
|
|
85
|
+
tags: strInfo.tags,
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
if (event) {
|
|
89
89
|
reportFindings({
|
|
90
90
|
ruleId: 'reflected-xss',
|
|
91
|
-
|
|
91
|
+
sinkEvent: event
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -98,18 +98,20 @@ module.exports = function(core) {
|
|
|
98
98
|
depHooks.resolve({ name: 'http' }, (http) => {
|
|
99
99
|
{
|
|
100
100
|
const name = 'http.ServerResponse.prototype.write';
|
|
101
|
-
|
|
101
|
+
const method = 'write';
|
|
102
|
+
patcher.patch(http.ServerResponse.prototype, method, {
|
|
102
103
|
name,
|
|
103
104
|
patchType,
|
|
104
|
-
pre: preHook(name),
|
|
105
|
+
pre: preHook(name, method),
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
{
|
|
108
109
|
const name = 'http.ServerResponse.prototype.end';
|
|
109
|
-
|
|
110
|
+
const method = 'end';
|
|
111
|
+
patcher.patch(http.ServerResponse.prototype, method, {
|
|
110
112
|
name,
|
|
111
113
|
patchType,
|
|
112
|
-
pre: preHook(name),
|
|
114
|
+
pre: preHook(name, method),
|
|
113
115
|
});
|
|
114
116
|
}
|
|
115
117
|
});
|