@contrast/assess 1.6.0 → 1.8.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 +3 -0
- package/lib/dataflow/propagation/install/JSON/index.js +33 -0
- package/lib/dataflow/propagation/install/JSON/stringify.js +290 -0
- package/lib/dataflow/propagation/install/buffer.js +79 -0
- package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
- package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
- package/lib/dataflow/propagation/install/sequelize.js +310 -0
- package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
- package/lib/dataflow/propagation/install/string/match.js +2 -2
- package/lib/dataflow/propagation/install/string/replace.js +9 -4
- package/lib/dataflow/sinks/common.js +10 -1
- package/lib/dataflow/sinks/index.js +30 -1
- package/lib/dataflow/sinks/install/express/index.js +29 -0
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +96 -69
- package/lib/dataflow/sinks/install/http.js +20 -5
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +33 -9
- package/lib/dataflow/sinks/install/mongodb.js +297 -82
- package/lib/dataflow/sinks/install/mssql.js +9 -4
- package/lib/dataflow/sinks/install/mysql.js +20 -4
- package/lib/dataflow/sinks/install/postgres.js +25 -12
- package/lib/dataflow/sinks/install/sequelize.js +142 -0
- package/lib/dataflow/sinks/install/sqlite3.js +9 -4
- package/lib/dataflow/sources/handler.js +144 -26
- package/lib/dataflow/sources/index.js +6 -8
- package/lib/dataflow/sources/install/body-parser1.js +133 -0
- package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
- package/lib/dataflow/sources/install/express/index.js +31 -0
- package/lib/dataflow/sources/install/express/params.js +81 -0
- package/lib/dataflow/sources/install/express/parsedUrl.js +87 -0
- package/lib/dataflow/sources/install/http.js +32 -18
- package/lib/dataflow/sources/install/querystring.js +75 -0
- package/lib/dataflow/tag-utils.js +68 -1
- package/package.json +3 -3
|
@@ -27,18 +27,39 @@ const {
|
|
|
27
27
|
},
|
|
28
28
|
isString
|
|
29
29
|
} = require('@contrast/common');
|
|
30
|
-
const { patchType } = require('../../common');
|
|
31
30
|
const { createSubsetTags } = require('../../../tag-utils');
|
|
31
|
+
const { filterSafeTags, patchType } = require('../../common');
|
|
32
|
+
|
|
33
|
+
const ruleId = 'unvalidated-redirect';
|
|
34
|
+
const getURLArgument = (args) => {
|
|
35
|
+
if (!Array.isArray(args)) {
|
|
36
|
+
return { index: null, url: undefined };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// url can be first or second argument
|
|
40
|
+
if (typeof args[0] === 'string') {
|
|
41
|
+
return {
|
|
42
|
+
index: 0,
|
|
43
|
+
url: args[0]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
index: 1,
|
|
49
|
+
url: args[1]
|
|
50
|
+
};
|
|
51
|
+
};
|
|
32
52
|
|
|
33
53
|
module.exports = function (core) {
|
|
34
54
|
const {
|
|
55
|
+
config,
|
|
35
56
|
depHooks,
|
|
36
57
|
patcher,
|
|
37
58
|
scopes: { sources },
|
|
38
59
|
assess: {
|
|
39
60
|
dataflow: {
|
|
40
61
|
tracker,
|
|
41
|
-
sinks: { isVulnerable, reportFindings },
|
|
62
|
+
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
42
63
|
eventFactory: { createSinkEvent },
|
|
43
64
|
},
|
|
44
65
|
},
|
|
@@ -56,79 +77,85 @@ module.exports = function (core) {
|
|
|
56
77
|
URL_ENCODED,
|
|
57
78
|
];
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
args: [{
|
|
80
|
+
unvalidatedRedirect.install = function () {
|
|
81
|
+
const name = 'fastify.Reply.prototype.redirect';
|
|
82
|
+
depHooks.resolve({ name: 'fastify', file: 'lib/reply' }, (Reply, version) => {
|
|
83
|
+
patcher.patch(Reply.prototype, 'redirect', {
|
|
84
|
+
name,
|
|
85
|
+
patchType,
|
|
86
|
+
post(data) {
|
|
87
|
+
const assessStore = sources.getStore()?.assess;
|
|
88
|
+
if (!assessStore) return;
|
|
89
|
+
|
|
90
|
+
const { url, index: valueIndex } = getURLArgument(data.args);
|
|
91
|
+
if (!url || !isString(url)) return;
|
|
92
|
+
|
|
93
|
+
const strInfo = tracker.getData(url);
|
|
94
|
+
if (!strInfo) return;
|
|
95
|
+
|
|
96
|
+
// todo: how does different tag logic play into display ranges?
|
|
97
|
+
|
|
98
|
+
let urlPathTags = strInfo.tags;
|
|
99
|
+
const urlPathEndIdx = url.indexOf('?');
|
|
100
|
+
if (urlPathEndIdx > -1) {
|
|
101
|
+
urlPathTags = createSubsetTags(strInfo.tags, 0, urlPathEndIdx);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
|
|
105
|
+
const args = [];
|
|
106
|
+
// in case a status code is provided
|
|
107
|
+
if (valueIndex) {
|
|
108
|
+
args.push({
|
|
109
|
+
tracked: false,
|
|
110
|
+
value: data.args[0]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
args.push({
|
|
94
114
|
tracked: true,
|
|
95
115
|
value: strInfo.value,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const event = createSinkEvent({
|
|
119
|
+
args,
|
|
120
|
+
context: `reply.redirect(${inspect(strInfo.value)})`,
|
|
121
|
+
history: [strInfo],
|
|
122
|
+
name: 'fastify.reply.redirect',
|
|
123
|
+
object: {
|
|
124
|
+
tracked: false,
|
|
125
|
+
value: 'fastify.Reply',
|
|
126
|
+
},
|
|
127
|
+
result: {
|
|
128
|
+
tracked: false,
|
|
129
|
+
value: undefined,
|
|
130
|
+
},
|
|
131
|
+
tags: urlPathTags,
|
|
132
|
+
source: `P${valueIndex}`,
|
|
133
|
+
stacktraceOpts: {
|
|
134
|
+
constructorOpt: data.hooked,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (event) {
|
|
139
|
+
reportFindings({
|
|
140
|
+
ruleId,
|
|
141
|
+
sinkEvent: event,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} else if (config.assess.safe_positives.enable) {
|
|
145
|
+
reportSafePositive({
|
|
146
|
+
name,
|
|
147
|
+
ruleId,
|
|
148
|
+
safeTags: filterSafeTags(safeTags, strInfo),
|
|
149
|
+
strInfo: {
|
|
150
|
+
tags: strInfo.tags,
|
|
151
|
+
value: strInfo.value,
|
|
152
|
+
}
|
|
119
153
|
});
|
|
120
154
|
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
155
|
+
},
|
|
156
|
+
});
|
|
123
157
|
});
|
|
124
158
|
};
|
|
125
159
|
|
|
126
|
-
unvalidatedRedirect.install = function () {
|
|
127
|
-
depHooks.resolve(
|
|
128
|
-
{ name: 'fastify', file: 'lib/reply' },
|
|
129
|
-
registerUnvalidatedRedirectHandler
|
|
130
|
-
);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
160
|
return unvalidatedRedirect;
|
|
134
161
|
};
|
|
@@ -29,19 +29,25 @@ const {
|
|
|
29
29
|
URL_ENCODED,
|
|
30
30
|
WEAK_URL_ENCODED,
|
|
31
31
|
},
|
|
32
|
-
Rule,
|
|
32
|
+
Rule: { REFLECTED_XSS: ruleId },
|
|
33
33
|
} = require('@contrast/common');
|
|
34
|
-
const { patchType } = require('../common');
|
|
34
|
+
const { patchType, filterSafeTags } = require('../common');
|
|
35
35
|
|
|
36
36
|
module.exports = function(core) {
|
|
37
37
|
const {
|
|
38
|
+
config,
|
|
38
39
|
depHooks,
|
|
39
40
|
patcher,
|
|
40
41
|
scopes: { sources },
|
|
41
42
|
assess: {
|
|
42
43
|
dataflow: {
|
|
43
44
|
tracker,
|
|
44
|
-
sinks: {
|
|
45
|
+
sinks: {
|
|
46
|
+
isVulnerable,
|
|
47
|
+
reportFindings,
|
|
48
|
+
reportSafePositive,
|
|
49
|
+
isSafeContentType
|
|
50
|
+
},
|
|
45
51
|
eventFactory: { createSinkEvent },
|
|
46
52
|
},
|
|
47
53
|
},
|
|
@@ -91,7 +97,6 @@ module.exports = function(core) {
|
|
|
91
97
|
value: data.result,
|
|
92
98
|
tracked: false,
|
|
93
99
|
},
|
|
94
|
-
ruleId: Rule.REFLECTED_XSS,
|
|
95
100
|
source: 'P0',
|
|
96
101
|
stacktraceOpts: {
|
|
97
102
|
constructorOpt: data.hooked
|
|
@@ -101,10 +106,20 @@ module.exports = function(core) {
|
|
|
101
106
|
|
|
102
107
|
if (event) {
|
|
103
108
|
reportFindings({
|
|
104
|
-
ruleId
|
|
109
|
+
ruleId,
|
|
105
110
|
sinkEvent: event
|
|
106
111
|
});
|
|
107
112
|
}
|
|
113
|
+
} else if (config.assess.safe_positives.enable) {
|
|
114
|
+
reportSafePositive({
|
|
115
|
+
name,
|
|
116
|
+
ruleId,
|
|
117
|
+
safeTags: filterSafeTags(safeTags, strInfo),
|
|
118
|
+
strInfo: {
|
|
119
|
+
value: strInfo.value,
|
|
120
|
+
tags: strInfo.tags,
|
|
121
|
+
}
|
|
122
|
+
});
|
|
108
123
|
}
|
|
109
124
|
};
|
|
110
125
|
|
|
@@ -27,18 +27,21 @@ const {
|
|
|
27
27
|
},
|
|
28
28
|
isString
|
|
29
29
|
} = require('@contrast/common');
|
|
30
|
-
const { patchType } = require('../../common');
|
|
31
30
|
const { createSubsetTags } = require('../../../tag-utils');
|
|
31
|
+
const { filterSafeTags, patchType } = require('../../common');
|
|
32
|
+
|
|
33
|
+
const ruleId = 'unvalidated-redirect';
|
|
32
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, reportFindings },
|
|
44
|
+
sinks: { isVulnerable, reportFindings, reportSafePositive },
|
|
42
45
|
eventFactory: { createSinkEvent },
|
|
43
46
|
},
|
|
44
47
|
},
|
|
@@ -58,6 +61,7 @@ module.exports = function (core) {
|
|
|
58
61
|
|
|
59
62
|
unvalidatedRedirect.install = function () {
|
|
60
63
|
depHooks.resolve({ name: 'koa', file: 'lib/response', version: '<2.9.0' }, (Response) => {
|
|
64
|
+
const name = 'Koa.Response.redirect';
|
|
61
65
|
patcher.patch(Response, 'redirect', {
|
|
62
66
|
name: 'Koa.Response.redirect',
|
|
63
67
|
patchType,
|
|
@@ -65,9 +69,10 @@ module.exports = function (core) {
|
|
|
65
69
|
const assessStore = sources.getStore()?.assess;
|
|
66
70
|
if (!assessStore) return;
|
|
67
71
|
|
|
72
|
+
let isBackRoute = false;
|
|
68
73
|
let [url] = data.args;
|
|
69
|
-
|
|
70
74
|
if (url === 'back') {
|
|
75
|
+
isBackRoute = true;
|
|
71
76
|
url = data.obj.ctx.get('Referrer') || data.args[1];
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -83,11 +88,20 @@ module.exports = function (core) {
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
if (urlPathTags && isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
|
|
91
|
+
const args = [];
|
|
92
|
+
if (isBackRoute) {
|
|
93
|
+
args.push({
|
|
94
|
+
tracked: false,
|
|
95
|
+
value: data.args[0]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
args.push({
|
|
99
|
+
tracked: true,
|
|
100
|
+
value: strInfo.value,
|
|
101
|
+
});
|
|
102
|
+
|
|
86
103
|
const event = createSinkEvent({
|
|
87
|
-
args
|
|
88
|
-
tracked: true,
|
|
89
|
-
value: strInfo.value,
|
|
90
|
-
}],
|
|
104
|
+
args,
|
|
91
105
|
context: `response.redirect(${inspect(strInfo.value)})`,
|
|
92
106
|
history: [strInfo],
|
|
93
107
|
name: 'Koa.Response.redirect',
|
|
@@ -100,7 +114,7 @@ module.exports = function (core) {
|
|
|
100
114
|
value: undefined,
|
|
101
115
|
},
|
|
102
116
|
tags: urlPathTags,
|
|
103
|
-
source:
|
|
117
|
+
source: `P${isBackRoute ? 1 : 0}`,
|
|
104
118
|
stacktraceOpts: {
|
|
105
119
|
constructorOpt: data.hooked,
|
|
106
120
|
},
|
|
@@ -108,10 +122,20 @@ module.exports = function (core) {
|
|
|
108
122
|
|
|
109
123
|
if (event) {
|
|
110
124
|
reportFindings({
|
|
111
|
-
ruleId
|
|
125
|
+
ruleId,
|
|
112
126
|
sinkEvent: event,
|
|
113
127
|
});
|
|
114
128
|
}
|
|
129
|
+
} else if (config.assess.safe_positives.enable) {
|
|
130
|
+
reportSafePositive({
|
|
131
|
+
name,
|
|
132
|
+
ruleId,
|
|
133
|
+
safeTags: filterSafeTags(safeTags, strInfo),
|
|
134
|
+
strInfo: {
|
|
135
|
+
tags: strInfo.tags,
|
|
136
|
+
value: strInfo.value,
|
|
137
|
+
}
|
|
138
|
+
});
|
|
115
139
|
}
|
|
116
140
|
}
|
|
117
141
|
});
|