@contrast/assess 1.29.0 → 1.30.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 +26 -17
- package/lib/dataflow/propagation/install/ejs/template.js +1 -1
- package/lib/dataflow/propagation/install/pug/index.js +1 -1
- package/lib/dataflow/propagation/install/url/searchParams.js +74 -15
- package/lib/dataflow/sinks/install/http/server-response.js +43 -14
- package/lib/response-scanning/handlers/index.js +24 -24
- package/lib/response-scanning/install/http.js +31 -45
- package/package.json +2 -2
|
@@ -24,18 +24,30 @@ const { InstrumentationType: { RULE } } = require('../../constants');
|
|
|
24
24
|
const { PATCH_TYPE: patchType } = require('../common');
|
|
25
25
|
|
|
26
26
|
const SAFE_HASH_ALGORITHMS = new Set([
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'RSA-SHA512',
|
|
27
|
+
// SHA224
|
|
28
|
+
'rsa-sha224',
|
|
29
|
+
'sha-224',
|
|
30
|
+
'sha2-224',
|
|
32
31
|
'sha224',
|
|
33
|
-
'
|
|
32
|
+
'sha224withrsaencryption',
|
|
33
|
+
// SHA256
|
|
34
|
+
'rsa-sha256',
|
|
35
|
+
'sha-256',
|
|
36
|
+
'sha2-256',
|
|
34
37
|
'sha256',
|
|
35
|
-
'
|
|
38
|
+
'sha256withrsaencryption',
|
|
39
|
+
// SHA384
|
|
40
|
+
'rsa-sha384',
|
|
41
|
+
'sha-384',
|
|
42
|
+
'sha2-384',
|
|
36
43
|
'sha384',
|
|
37
|
-
'
|
|
38
|
-
|
|
44
|
+
'sha384withrsaencryption',
|
|
45
|
+
// SHA512
|
|
46
|
+
'rsa-sha512',
|
|
47
|
+
'sha-512',
|
|
48
|
+
'sha2-512',
|
|
49
|
+
'sha512',
|
|
50
|
+
'sha512withrsaencryption',
|
|
39
51
|
]);
|
|
40
52
|
const SAFE_HASH_LIBS = ['etag', 'browserify', 'deps-sort'];
|
|
41
53
|
const SAFE_CIPHER_ALGORITHM_PREFIXES = ['des-ede', 'id-aes', 'aes', 'rsa'];
|
|
@@ -53,7 +65,6 @@ module.exports = function (core) {
|
|
|
53
65
|
logger,
|
|
54
66
|
patcher,
|
|
55
67
|
assess: {
|
|
56
|
-
inspect, // todo: remove
|
|
57
68
|
eventFactory,
|
|
58
69
|
cryptoAnalysis,
|
|
59
70
|
getSourceContext,
|
|
@@ -68,16 +79,14 @@ module.exports = function (core) {
|
|
|
68
79
|
patchType,
|
|
69
80
|
post(data) {
|
|
70
81
|
const [alg] = data.args;
|
|
82
|
+
if (!isString(alg) || !getSourceContext(RULE, Rule.CRYPTO_BAD_MAC)) return;
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
!getSourceContext(RULE, Rule.CRYPTO_BAD_MAC) ||
|
|
75
|
-
SAFE_HASH_ALGORITHMS.has(alg)
|
|
76
|
-
) return;
|
|
84
|
+
const algLower = StringPrototypeToLowerCase.call(alg);
|
|
85
|
+
if (SAFE_HASH_ALGORITHMS.has(algLower)) return;
|
|
77
86
|
|
|
78
87
|
const event = eventFactory.createCryptoAnalysisEvent({
|
|
79
88
|
args: [{ tracked: false, value: alg }],
|
|
80
|
-
context: `crypto.createHash(${
|
|
89
|
+
context: `crypto.createHash(${algLower})`,
|
|
81
90
|
methodName: 'createHash',
|
|
82
91
|
moduleName: 'crypto',
|
|
83
92
|
name: 'crypto.createHash',
|
|
@@ -130,7 +139,7 @@ module.exports = function (core) {
|
|
|
130
139
|
|
|
131
140
|
const event = eventFactory.createCryptoAnalysisEvent({
|
|
132
141
|
args: [{ tracked: false, value: alg }],
|
|
133
|
-
context: `crypto.${method}(${
|
|
142
|
+
context: `crypto.${method}(${algLower})`,
|
|
134
143
|
methodName: method,
|
|
135
144
|
moduleName: 'crypto',
|
|
136
145
|
name: `crypto.${method}`,
|
|
@@ -38,7 +38,7 @@ module.exports = function (core) {
|
|
|
38
38
|
} = core;
|
|
39
39
|
|
|
40
40
|
/** @type {import('@contrast/rewriter').RewriteOpts} */
|
|
41
|
-
const REWRITE_OPTS = { isModule: false, inject: false, wrap: false
|
|
41
|
+
const REWRITE_OPTS = { isModule: false, inject: false, wrap: false };
|
|
42
42
|
const WRAPPER_PREFIX = ArrayPrototypeJoin.call([
|
|
43
43
|
'function tempWrapper() {',
|
|
44
44
|
'function __append(s) { if (s !== undefined && s !== null) __output += s }'
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const { patchType } = require('../../common');
|
|
18
18
|
|
|
19
19
|
/** @type {import('@contrast/rewriter').RewriteOpts} */
|
|
20
|
-
const REWRITE_OPTS = { isModule: false, inject: false, wrap: false
|
|
20
|
+
const REWRITE_OPTS = { isModule: false, inject: false, wrap: false };
|
|
21
21
|
|
|
22
22
|
module.exports = function (core) {
|
|
23
23
|
const store = { lock: true, name: 'assess:propagators:pug-compile' };
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { patchType } = require('../../common');
|
|
19
|
-
const { isString } = require('@contrast/common');
|
|
19
|
+
const { isString, StringPrototypeConcat, StringPrototypeReplaceAll } = require('@contrast/common');
|
|
20
|
+
const { createAppendTags } = require('../../../tag-utils');
|
|
20
21
|
|
|
21
22
|
module.exports = function(core) {
|
|
22
23
|
const {
|
|
@@ -74,10 +75,9 @@ module.exports = function(core) {
|
|
|
74
75
|
if (isString(params)) {
|
|
75
76
|
params.split('&').forEach((query) => {
|
|
76
77
|
const endIdx = query.indexOf('=');
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
const key = query.substring(query.indexOf('?') + 1, endIdx);
|
|
78
|
+
// we don't want to create a propagation event by splitting off
|
|
79
|
+
// the '?'. so if there start at index 1 else 0.
|
|
80
|
+
const key = query.substring(query[0] === '?' ? 1 : 0, endIdx);
|
|
81
81
|
const param = query.substring(endIdx + 1, query.length);
|
|
82
82
|
|
|
83
83
|
const keyInfo = tracker.getData(key);
|
|
@@ -122,17 +122,76 @@ module.exports = function(core) {
|
|
|
122
122
|
patchType,
|
|
123
123
|
post(data) {
|
|
124
124
|
const { obj: params } = data;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
params.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
|
|
126
|
+
// params.size doesn't exist until v18.16.0
|
|
127
|
+
const entries = [...params.entries()];
|
|
128
|
+
if (!entries.length) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let finalTags = {};
|
|
132
|
+
const history = [];
|
|
133
|
+
//
|
|
134
|
+
// local function to capture tags and history for entries in URLSearchParams
|
|
135
|
+
//
|
|
136
|
+
function createTagsAndHistoryIfNeeded(entry, base) {
|
|
137
|
+
// check key
|
|
138
|
+
let tracking = tracker.getData(entry[0]);
|
|
139
|
+
if (tracking) {
|
|
140
|
+
finalTags = createAppendTags(finalTags, tracking.tags, base + 1);
|
|
141
|
+
history.push(tracking);
|
|
142
|
+
}
|
|
143
|
+
// check value
|
|
144
|
+
tracking = tracker.getData(entry[1]);
|
|
145
|
+
if (tracking) {
|
|
146
|
+
// adjust tags to account for the 'key='
|
|
147
|
+
finalTags = createAppendTags(finalTags, tracking.tags, base + 2 + entry[0].length);
|
|
148
|
+
history.push(tracking);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// do the first one outside the loop to avoid the &. first base
|
|
153
|
+
// is -1 because there is no & to account for.
|
|
154
|
+
let result = StringPrototypeConcat.call(entries[0][0], '=', entries[0][1]);
|
|
155
|
+
createTagsAndHistoryIfNeeded(entries[0], -1);
|
|
156
|
+
|
|
157
|
+
// do remaining entries
|
|
158
|
+
let base = result.length;
|
|
159
|
+
for (let i = 1; i < entries.length; i++) {
|
|
160
|
+
result = StringPrototypeConcat.call(result, '&', entries[i][0], '=', entries[i][1]);
|
|
161
|
+
createTagsAndHistoryIfNeeded(entries[i], base);
|
|
162
|
+
base = result.length;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
result = StringPrototypeReplaceAll.call(result, ' ', '+');
|
|
166
|
+
|
|
167
|
+
if (Object.keys(finalTags).length) {
|
|
168
|
+
const event = createPropagationEvent({
|
|
169
|
+
args: [],
|
|
170
|
+
context: `${inspect(params)}.toString()`,
|
|
171
|
+
moduleName: 'url',
|
|
172
|
+
methodName: 'URLSearchParams.toString',
|
|
173
|
+
history,
|
|
174
|
+
object: { value: 'URLSearchParams', tracked: false },
|
|
175
|
+
name: 'URLSearchParams.toString',
|
|
176
|
+
result: {
|
|
177
|
+
value: `${inspect(result)}`,
|
|
178
|
+
tracked: true
|
|
179
|
+
},
|
|
180
|
+
source: 'O',
|
|
181
|
+
stacktraceOpts: {
|
|
182
|
+
constructorOpt: data.hooked, // hide stack above this point
|
|
183
|
+
},
|
|
184
|
+
tags: finalTags,
|
|
185
|
+
target: 'R',
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const { extern } = tracker.track(result, event);
|
|
189
|
+
if (extern) {
|
|
190
|
+
result = extern;
|
|
132
191
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
data.result =
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
data.result = result;
|
|
136
195
|
}
|
|
137
196
|
});
|
|
138
197
|
}
|
|
@@ -77,11 +77,13 @@ module.exports = function(core) {
|
|
|
77
77
|
WEAK_URL_ENCODED,
|
|
78
78
|
];
|
|
79
79
|
|
|
80
|
-
const preHook = (
|
|
80
|
+
const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked, orig }) => {
|
|
81
|
+
const methodName = `${responseName + (moduleName !== 'spdy' ? '.prototype' : '')}.${method}`;
|
|
82
|
+
const name = `${moduleName}.${methodName}`;
|
|
81
83
|
const sourceContext = getSourceContext(RULE, ruleId);
|
|
82
84
|
if (!sourceContext) return;
|
|
83
85
|
|
|
84
|
-
const payload =
|
|
86
|
+
const payload = args[0];
|
|
85
87
|
if (!payload) return;
|
|
86
88
|
|
|
87
89
|
const strInfo = tracker.getData(payload);
|
|
@@ -90,6 +92,7 @@ module.exports = function(core) {
|
|
|
90
92
|
const { contentType } = sourceContext.responseData;
|
|
91
93
|
if (contentType && isSafeContentType(contentType)) return;
|
|
92
94
|
|
|
95
|
+
if (moduleName === 'spdy') response.spdyStream.once('finish', () => response.emit('finish'));
|
|
93
96
|
if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
|
|
94
97
|
const event = createSinkEvent({
|
|
95
98
|
args: [{
|
|
@@ -99,20 +102,20 @@ module.exports = function(core) {
|
|
|
99
102
|
context: `res.${method}('${strInfo.value}')`,
|
|
100
103
|
history: [strInfo],
|
|
101
104
|
name,
|
|
102
|
-
moduleName
|
|
103
|
-
methodName
|
|
105
|
+
moduleName,
|
|
106
|
+
methodName,
|
|
104
107
|
object: {
|
|
105
108
|
tracked: false,
|
|
106
|
-
value:
|
|
109
|
+
value: `${moduleName}.${responseName}`
|
|
107
110
|
},
|
|
108
111
|
result: {
|
|
109
|
-
value:
|
|
112
|
+
value: result,
|
|
110
113
|
tracked: false,
|
|
111
114
|
},
|
|
112
115
|
source: 'P0',
|
|
113
116
|
stacktraceOpts: {
|
|
114
|
-
constructorOpt:
|
|
115
|
-
prependFrames: [
|
|
117
|
+
constructorOpt: hooked,
|
|
118
|
+
prependFrames: [orig]
|
|
116
119
|
},
|
|
117
120
|
tags: strInfo.tags,
|
|
118
121
|
});
|
|
@@ -139,21 +142,47 @@ module.exports = function(core) {
|
|
|
139
142
|
http.install = function() {
|
|
140
143
|
depHooks.resolve({ name: 'http' }, (http) => {
|
|
141
144
|
{
|
|
142
|
-
const name = 'http.ServerResponse.prototype.write';
|
|
143
145
|
const method = 'write';
|
|
144
146
|
patcher.patch(http.ServerResponse.prototype, method, {
|
|
145
|
-
name,
|
|
147
|
+
name: 'http.ServerResponse.prototype.write',
|
|
146
148
|
patchType,
|
|
147
|
-
pre: preHook(
|
|
149
|
+
pre: preHook('http', 'ServerResponse', method),
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
152
|
{
|
|
151
|
-
const name = 'http.ServerResponse.prototype.end';
|
|
152
153
|
const method = 'end';
|
|
153
154
|
patcher.patch(http.ServerResponse.prototype, method, {
|
|
154
|
-
name,
|
|
155
|
+
name: 'http.ServerResponse.prototype.end',
|
|
155
156
|
patchType,
|
|
156
|
-
pre: preHook(
|
|
157
|
+
pre: preHook('http', 'ServerResponse', method),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
depHooks.resolve({ name: 'http2' }, (http2) => {
|
|
162
|
+
{
|
|
163
|
+
const method = 'write';
|
|
164
|
+
patcher.patch(http2.Http2ServerResponse.prototype, method, {
|
|
165
|
+
name: 'http2.Http2ServerResponse.prototype.write',
|
|
166
|
+
patchType,
|
|
167
|
+
pre: preHook('http2', 'Http2ServerResponse', method),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
{
|
|
171
|
+
const method = 'end';
|
|
172
|
+
patcher.patch(http2.Http2ServerResponse.prototype, method, {
|
|
173
|
+
name: 'http2.Http2ServerResponse.prototype.end',
|
|
174
|
+
patchType,
|
|
175
|
+
pre: preHook('http2', 'Http2ServerResponse', method),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
|
|
180
|
+
{
|
|
181
|
+
const method = 'end';
|
|
182
|
+
patcher.patch(response, method, {
|
|
183
|
+
name: 'spdy.response.end',
|
|
184
|
+
patchType,
|
|
185
|
+
pre: preHook('spdy', 'response', method),
|
|
157
186
|
});
|
|
158
187
|
}
|
|
159
188
|
});
|
|
@@ -56,15 +56,15 @@ module.exports = function(core) {
|
|
|
56
56
|
}
|
|
57
57
|
} = core;
|
|
58
58
|
|
|
59
|
-
responseScanning.handleAutoCompleteMissing = function(sourceContext,
|
|
59
|
+
responseScanning.handleAutoCompleteMissing = function(sourceContext, resHeaders, resBody) {
|
|
60
60
|
if (
|
|
61
61
|
!isEnabled(AUTOCOMPLETE_MISSING, sourceContext) ||
|
|
62
|
-
!isHtmlContent(
|
|
62
|
+
!isHtmlContent(resHeaders)
|
|
63
63
|
) {
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const elements = getElements('form',
|
|
67
|
+
const elements = getElements('form', resBody);
|
|
68
68
|
|
|
69
69
|
for (let i = 0; i < elements.length; i++) {
|
|
70
70
|
const autocomplete = getAttribute('autocomplete', elements[i]);
|
|
@@ -84,24 +84,24 @@ module.exports = function(core) {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
responseScanning.handleCacheControlsMissing = function(sourceContext,
|
|
87
|
+
responseScanning.handleCacheControlsMissing = function(sourceContext, resHeaders, resBody) {
|
|
88
88
|
const instructions = [];
|
|
89
89
|
|
|
90
90
|
// de-dupe; this will be re-emitted for parseableBody handlers anyway
|
|
91
91
|
if (
|
|
92
92
|
!isEnabled(CACHE_CONTROLS_MISSING, sourceContext) ||
|
|
93
|
-
(isParseableResponse(
|
|
93
|
+
(isParseableResponse(resHeaders) && !resBody)
|
|
94
94
|
) {
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
-
const cacheControlHeader =
|
|
97
|
+
const cacheControlHeader = resHeaders['cache-control'];
|
|
98
98
|
|
|
99
99
|
// save the Pragma Header
|
|
100
|
-
if (
|
|
100
|
+
if (resHeaders['pragma']) {
|
|
101
101
|
instructions.push({
|
|
102
102
|
type: 'Header',
|
|
103
103
|
name: 'pragma',
|
|
104
|
-
value:
|
|
104
|
+
value: resHeaders['pragma']
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -124,7 +124,7 @@ module.exports = function(core) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// we checked the headers now time to check the HTML content
|
|
127
|
-
checkMetaTags({ body:
|
|
127
|
+
checkMetaTags({ body: resBody, cacheControlHeader, instructions });
|
|
128
128
|
|
|
129
129
|
if (instructions.length) {
|
|
130
130
|
reportFindings(sourceContext, {
|
|
@@ -136,11 +136,11 @@ module.exports = function(core) {
|
|
|
136
136
|
}
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
responseScanning.handleClickJackingControlsMissing = function(sourceContext,
|
|
139
|
+
responseScanning.handleClickJackingControlsMissing = function(sourceContext, resHeaders) {
|
|
140
140
|
if (!isEnabled(CLICKJACKING_CONTROL_MISSING, sourceContext)) return;
|
|
141
141
|
|
|
142
142
|
// look for x-frame-options headers with deny or sameorigin
|
|
143
|
-
const xFrameHeaders =
|
|
143
|
+
const xFrameHeaders = resHeaders['x-frame-options'];
|
|
144
144
|
let hasFrameBusting = false;
|
|
145
145
|
|
|
146
146
|
if (xFrameHeaders) {
|
|
@@ -155,12 +155,12 @@ module.exports = function(core) {
|
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
-
responseScanning.handleParameterPollution = function(sourceContext,
|
|
158
|
+
responseScanning.handleParameterPollution = function(sourceContext, resBody) {
|
|
159
159
|
if (!isEnabled(PARAMETER_POLLUTION, sourceContext)) return;
|
|
160
160
|
|
|
161
161
|
// look for form tag with missing action attribute.
|
|
162
162
|
// ex: <form method="post">..
|
|
163
|
-
const elements = getElements('form',
|
|
163
|
+
const elements = getElements('form', resBody);
|
|
164
164
|
|
|
165
165
|
for (let i = 0; i < elements.length; i++) {
|
|
166
166
|
const action = getAttribute('action', elements[i]);
|
|
@@ -180,11 +180,11 @@ module.exports = function(core) {
|
|
|
180
180
|
* Checks the response headers for the CSP. If found, and insecure, will return
|
|
181
181
|
* the evidence for reporting.
|
|
182
182
|
*
|
|
183
|
-
* @param {Object}
|
|
184
|
-
* @returns {Object}
|
|
183
|
+
* @param {Object} resHeaders HTTP headers object.
|
|
184
|
+
* @returns {Object} Evidence for insecure CSP header.
|
|
185
185
|
*/
|
|
186
|
-
responseScanning.handleCspHeader = function(sourceContext,
|
|
187
|
-
const cspHeaders = getCspHeaders(
|
|
186
|
+
responseScanning.handleCspHeader = function(sourceContext, resHeaders) {
|
|
187
|
+
const cspHeaders = getCspHeaders(resHeaders);
|
|
188
188
|
|
|
189
189
|
// Don't report if not set; this report belongs to 'csp-header-missing'
|
|
190
190
|
if (!cspHeaders && isEnabled(CSP_HEADER_MISSING, sourceContext)) {
|
|
@@ -213,10 +213,10 @@ module.exports = function(core) {
|
|
|
213
213
|
}
|
|
214
214
|
};
|
|
215
215
|
|
|
216
|
-
responseScanning.handleHstsHeaderMissing = function(sourceContext,
|
|
216
|
+
responseScanning.handleHstsHeaderMissing = function(sourceContext, resHeaders) {
|
|
217
217
|
if (!isEnabled(HSTS_HEADER_MISSING, sourceContext)) return;
|
|
218
218
|
|
|
219
|
-
let header =
|
|
219
|
+
let header = resHeaders['strict-transport-security'];
|
|
220
220
|
let maxAge;
|
|
221
221
|
|
|
222
222
|
if (header) {
|
|
@@ -245,11 +245,11 @@ module.exports = function(core) {
|
|
|
245
245
|
}
|
|
246
246
|
};
|
|
247
247
|
|
|
248
|
-
responseScanning.handleXContentTypeHeaderMissing = function(sourceContext,
|
|
248
|
+
responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, resHeaders) {
|
|
249
249
|
if (!isEnabled(XCONTENTTYPE_HEADER_MISSING, sourceContext)) return;
|
|
250
250
|
|
|
251
251
|
const headerName = 'x-content-type-options';
|
|
252
|
-
let header =
|
|
252
|
+
let header = resHeaders[headerName];
|
|
253
253
|
|
|
254
254
|
if (header) {
|
|
255
255
|
header = StringPrototypeToLowerCase.call(header);
|
|
@@ -266,11 +266,11 @@ module.exports = function(core) {
|
|
|
266
266
|
});
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
-
responseScanning.handleXPoweredByHeader = function(sourceContext,
|
|
269
|
+
responseScanning.handleXPoweredByHeader = function(sourceContext, resHeaders) {
|
|
270
270
|
if (!isEnabled(X_POWERED_BY_HEADER, sourceContext)) return;
|
|
271
271
|
|
|
272
272
|
const headerName = 'x-powered-by';
|
|
273
|
-
let header =
|
|
273
|
+
let header = resHeaders[headerName];
|
|
274
274
|
|
|
275
275
|
if (header) {
|
|
276
276
|
header = StringPrototypeToLowerCase.call(header);
|
|
@@ -284,7 +284,7 @@ module.exports = function(core) {
|
|
|
284
284
|
}
|
|
285
285
|
};
|
|
286
286
|
|
|
287
|
-
responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext,
|
|
287
|
+
responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, responseHeaders) {
|
|
288
288
|
if (!isEnabled(XXSPROTECTION_HEADER_DISABLED, sourceContext)) return;
|
|
289
289
|
|
|
290
290
|
const header = responseHeaders['x-xss-protection'];
|
|
@@ -67,25 +67,25 @@ module.exports = function(core) {
|
|
|
67
67
|
|
|
68
68
|
const patchType = 'response-scanning';
|
|
69
69
|
|
|
70
|
-
function writeHookChecks(sourceContext,
|
|
70
|
+
function writeHookChecks(sourceContext, resHeaders, resBody) {
|
|
71
71
|
// Check only the rules concerning the response body
|
|
72
|
-
handleAutoCompleteMissing(sourceContext,
|
|
73
|
-
handleCacheControlsMissing(sourceContext,
|
|
74
|
-
handleParameterPollution(sourceContext,
|
|
75
|
-
handleXxsProtectionHeaderDisabled(sourceContext,
|
|
72
|
+
handleAutoCompleteMissing(sourceContext, resHeaders, resBody);
|
|
73
|
+
handleCacheControlsMissing(sourceContext, resHeaders, resBody);
|
|
74
|
+
handleParameterPollution(sourceContext, resBody);
|
|
75
|
+
handleXxsProtectionHeaderDisabled(sourceContext, resHeaders, resBody);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function endHookChecks(sourceContext,
|
|
78
|
+
function endHookChecks(sourceContext, resHeaders, resBody) {
|
|
79
79
|
// Check all the response scanning rules
|
|
80
|
-
handleAutoCompleteMissing(sourceContext,
|
|
81
|
-
handleCacheControlsMissing(sourceContext,
|
|
82
|
-
handleClickJackingControlsMissing(sourceContext,
|
|
83
|
-
handleParameterPollution(sourceContext,
|
|
84
|
-
handleCspHeader(sourceContext,
|
|
85
|
-
handleHstsHeaderMissing(sourceContext,
|
|
86
|
-
handleXPoweredByHeader(sourceContext,
|
|
87
|
-
handleXContentTypeHeaderMissing(sourceContext,
|
|
88
|
-
handleXxsProtectionHeaderDisabled(sourceContext,
|
|
80
|
+
handleAutoCompleteMissing(sourceContext, resHeaders, resBody);
|
|
81
|
+
handleCacheControlsMissing(sourceContext, resHeaders, resBody);
|
|
82
|
+
handleClickJackingControlsMissing(sourceContext, resHeaders, resBody);
|
|
83
|
+
handleParameterPollution(sourceContext, resBody);
|
|
84
|
+
handleCspHeader(sourceContext, resHeaders);
|
|
85
|
+
handleHstsHeaderMissing(sourceContext, resHeaders);
|
|
86
|
+
handleXPoweredByHeader(sourceContext, resHeaders);
|
|
87
|
+
handleXContentTypeHeaderMissing(sourceContext, resHeaders);
|
|
88
|
+
handleXxsProtectionHeaderDisabled(sourceContext, resHeaders);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
http.install = function() {
|
|
@@ -98,13 +98,7 @@ module.exports = function(core) {
|
|
|
98
98
|
post(data) {
|
|
99
99
|
const sourceContext = getSourceContext();
|
|
100
100
|
if (!sourceContext) return;
|
|
101
|
-
|
|
102
|
-
const evaluationContext = {
|
|
103
|
-
responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
|
|
104
|
-
responseHeaders: parseHeaders(data.result._header),
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
writeHookChecks(sourceContext, evaluationContext);
|
|
101
|
+
writeHookChecks(sourceContext, parseHeaders(data.result._header), StringPrototypeToLowerCase.call(data.args[0] || ''));
|
|
108
102
|
}
|
|
109
103
|
});
|
|
110
104
|
}
|
|
@@ -116,20 +110,14 @@ module.exports = function(core) {
|
|
|
116
110
|
post(data) {
|
|
117
111
|
const sourceContext = getSourceContext();
|
|
118
112
|
if (!sourceContext) return;
|
|
119
|
-
|
|
120
|
-
const evaluationContext = {
|
|
121
|
-
responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
|
|
122
|
-
responseHeaders: parseHeaders(data.result._header),
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
endHookChecks(sourceContext, evaluationContext);
|
|
113
|
+
endHookChecks(sourceContext, parseHeaders(data.result._header), StringPrototypeToLowerCase.call(data.args[0] || ''));
|
|
126
114
|
}
|
|
127
115
|
});
|
|
128
116
|
}
|
|
129
117
|
});
|
|
130
118
|
|
|
131
119
|
depHooks.resolve({ name: 'http2' }, (http2) => {
|
|
132
|
-
//
|
|
120
|
+
// todo: NODE-3467
|
|
133
121
|
{
|
|
134
122
|
const name = 'http2.Http2ServerResponse.prototype.write';
|
|
135
123
|
patcher.patch(http2.Http2ServerResponse.prototype, 'write', {
|
|
@@ -138,14 +126,8 @@ module.exports = function(core) {
|
|
|
138
126
|
post(data) {
|
|
139
127
|
const sourceContext = getSourceContext();
|
|
140
128
|
if (!sourceContext) return;
|
|
141
|
-
|
|
142
129
|
const headersSymbol = Object.getOwnPropertySymbols(data.obj).find(symbol => symbol.toString().includes('headers'));
|
|
143
|
-
|
|
144
|
-
responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
|
|
145
|
-
responseHeaders: data.obj[headersSymbol],
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
writeHookChecks(sourceContext, evaluationContext);
|
|
130
|
+
writeHookChecks(sourceContext, data.obj[headersSymbol], StringPrototypeToLowerCase.call(data.args[0] || ''));
|
|
149
131
|
}
|
|
150
132
|
});
|
|
151
133
|
}
|
|
@@ -159,20 +141,24 @@ module.exports = function(core) {
|
|
|
159
141
|
if (!sourceContext) return;
|
|
160
142
|
|
|
161
143
|
const headersSymbol = Object.getOwnPropertySymbols(data.result).find(symbol => symbol.toString().includes('headers'));
|
|
162
|
-
|
|
163
|
-
responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
|
|
164
|
-
responseHeaders: data.result[headersSymbol],
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
endHookChecks(sourceContext, evaluationContext);
|
|
144
|
+
endHookChecks(sourceContext, data.result[headersSymbol], StringPrototypeToLowerCase.call(data.args[0] || ''));
|
|
168
145
|
}
|
|
169
146
|
});
|
|
170
147
|
}
|
|
148
|
+
});
|
|
171
149
|
|
|
172
|
-
|
|
150
|
+
depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
|
|
151
|
+
patcher.patch(response, 'end', {
|
|
152
|
+
name: 'spdy.response.end',
|
|
153
|
+
patchType: 'test',
|
|
154
|
+
post(data) {
|
|
155
|
+
const sourceContext = getSourceContext();
|
|
156
|
+
if (!sourceContext) return;
|
|
157
|
+
endHookChecks(sourceContext, data.obj.getHeaders?.(), StringPrototypeToLowerCase.call(data.args[0] || ''));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
173
160
|
});
|
|
174
161
|
};
|
|
175
162
|
|
|
176
163
|
return http;
|
|
177
164
|
};
|
|
178
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Assess support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.21.
|
|
20
|
+
"@contrast/common": "1.21.3",
|
|
21
21
|
"@contrast/distringuish": "^5.0.0",
|
|
22
22
|
"@contrast/scopes": "1.4.1"
|
|
23
23
|
}
|