Skip to content

Commit f542e74

Browse files
bnoordhuisFishrock123
authored andcommitted
http: guard against response splitting in trailers
Commit 3c293ba ("http: protect against response splitting attacks") filters out newline characters from HTTP headers but forgot to apply the same logic to trailing HTTP headers, i.e., headers that come after the response body. This commit rectifies that. The expected security impact is low because approximately no one uses trailing headers. Some HTTP clients can't even parse them. PR-URL: #2945 Reviewed-By: Сковорода Никита Андреевич <[email protected]> Reviewed-By: Rod Vagg <[email protected]>
1 parent 2084f52 commit f542e74

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

lib/_http_outgoing.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,7 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
295295
};
296296

297297
function storeHeader(self, state, field, value) {
298-
// Protect against response splitting. The if statement is there to
299-
// minimize the performance impact in the common case.
300-
if (/[\r\n]/.test(value))
301-
value = value.replace(/[\r\n]+[ \t]*/g, '');
302-
298+
value = escapeHeaderValue(value);
303299
state.messageHeader += field + ': ' + value + CRLF;
304300

305301
if (connectionExpression.test(field)) {
@@ -481,6 +477,13 @@ function connectionCorkNT(conn) {
481477
}
482478

483479

480+
function escapeHeaderValue(value) {
481+
// Protect against response splitting. The regex test is there to
482+
// minimize the performance impact in the common case.
483+
return /[\r\n]/.test(value) ? value.replace(/[\r\n]+[ \t]*/g, '') : value;
484+
}
485+
486+
484487
OutgoingMessage.prototype.addTrailers = function(headers) {
485488
this._trailer = '';
486489
var keys = Object.keys(headers);
@@ -496,7 +499,7 @@ OutgoingMessage.prototype.addTrailers = function(headers) {
496499
value = headers[key];
497500
}
498501

499-
this._trailer += field + ': ' + value + CRLF;
502+
this._trailer += field + ': ' + escapeHeaderValue(value) + CRLF;
500503
}
501504
};
502505

test/parallel/test-http-header-response-splitting.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var common = require('../common'),
44
http = require('http');
55

66
var testIndex = 0;
7-
const testCount = 4 * 6;
7+
const testCount = 2 * 4 * 6;
88
const responseBody = 'Hi mars!';
99

1010
var server = http.createServer(function(req, res) {
@@ -29,9 +29,15 @@ var server = http.createServer(function(req, res) {
2929
default:
3030
assert.fail('unreachable');
3131
}
32-
res.end(responseBody);
32+
res.write(responseBody);
33+
if (testIndex % 8 < 4) {
34+
res.addTrailers({ ta: header, tb: header });
35+
} else {
36+
res.addTrailers([['ta', header], ['tb', header]]);
37+
}
38+
res.end();
3339
}
34-
switch ((testIndex / 4) | 0) {
40+
switch ((testIndex / 8) | 0) {
3541
case 0:
3642
reply('foo \r\ninvalid: bar');
3743
break;
@@ -70,6 +76,10 @@ server.listen(common.PORT, common.mustCall(function() {
7076
res.on('data', function(s) { data += s; });
7177
res.on('end', common.mustCall(function() {
7278
assert.equal(data, responseBody);
79+
assert.strictEqual(res.trailers.ta, 'foo invalid: bar');
80+
assert.strictEqual(res.trailers.tb, 'foo invalid: bar');
81+
assert.strictEqual(res.trailers.foo, undefined);
82+
assert.strictEqual(res.trailers.invalid, undefined);
7383
}));
7484
res.resume();
7585
}));

0 commit comments

Comments
 (0)