Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f8178ac

Browse filesBrowse files
rsclarkeaduh95
authored andcommitted
http: validate headers in writeEarlyHints
Add validateHeaderName/validateHeaderValue checks for non-link headers and checkInvalidHeaderChar for the Link value in HTTP/1.1 writeEarlyHints, closing a CRLF injection gap where header names and values were concatenated into the raw response without validation. Also tighten linkValueRegExp to reject CR/LF inside the <...> URL portion of Link header values. PR-URL: #61897 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent 2df328d commit f8178ac
Copy full SHA for f8178ac

3 files changed

+52-2Lines changed: 52 additions & 2 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/_http_server.js‎

Copy file name to clipboardExpand all lines: lib/_http_server.js
+10-1Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const {
5353
kUniqueHeaders,
5454
parseUniqueHeadersOption,
5555
OutgoingMessage,
56+
validateHeaderName,
57+
validateHeaderValue,
5658
} = require('_http_outgoing');
5759
const {
5860
kOutHeaders,
@@ -330,13 +332,20 @@ ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) {
330332
return;
331333
}
332334

335+
if (checkInvalidHeaderChar(link)) {
336+
throw new ERR_INVALID_CHAR('header content', 'Link');
337+
}
338+
333339
head += 'Link: ' + link + '\r\n';
334340

335341
const keys = ObjectKeys(hints);
336342
for (let i = 0; i < keys.length; i++) {
337343
const key = keys[i];
338344
if (key !== 'link') {
339-
head += key + ': ' + hints[key] + '\r\n';
345+
validateHeaderName(key);
346+
const value = hints[key];
347+
validateHeaderValue(key, value);
348+
head += key + ': ' + value + '\r\n';
340349
}
341350
}
342351

Collapse file

‎lib/internal/validators.js‎

Copy file name to clipboardExpand all lines: lib/internal/validators.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ function validateUnion(value, name, union) {
509509
(not necessarily a valid URI reference) followed by zero or more
510510
link-params separated by semicolons.
511511
*/
512-
const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;
512+
const linkValueRegExp = /^(?:<[^>\r\n]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;
513513

514514
/**
515515
* @param {any} value
Collapse file

‎test/parallel/test-http-early-hints-invalid-argument.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-http-early-hints-invalid-argument.js
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,44 @@ const testResBody = 'response content\n';
4747
req.on('information', common.mustNotCall());
4848
}));
4949
}
50+
51+
{
52+
const server = http.createServer(common.mustCall((req, res) => {
53+
debug('Server sending early hints with CRLF injection...');
54+
55+
assert.throws(() => {
56+
res.writeEarlyHints({
57+
'link': '</styles.css>; rel=preload; as=style',
58+
'X-Custom': 'valid\r\nSet-Cookie: session=evil',
59+
});
60+
}, (err) => err.code === 'ERR_INVALID_CHAR');
61+
62+
assert.throws(() => {
63+
res.writeEarlyHints({
64+
'link': '</styles.css>; rel=preload; as=style',
65+
'X-Custom\r\nSet-Cookie: session=evil': 'value',
66+
});
67+
}, (err) => err.code === 'ERR_INVALID_HTTP_TOKEN');
68+
69+
assert.throws(() => {
70+
res.writeEarlyHints({
71+
link: '</styles.css\r\nSet-Cookie: session=evil>; rel=preload; as=style',
72+
});
73+
}, (err) => err.code === 'ERR_INVALID_ARG_VALUE');
74+
75+
debug('Server sending full response...');
76+
res.end(testResBody);
77+
server.close();
78+
}));
79+
80+
server.listen(0, common.mustCall(() => {
81+
const req = http.request({
82+
port: server.address().port, path: '/'
83+
});
84+
85+
req.end();
86+
debug('Client sending request...');
87+
88+
req.on('information', common.mustNotCall());
89+
}));
90+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.