Skip to content

Commit 380ea72

Browse files
mcollinajuanarbol
authored andcommitted
http: use null prototype for headersDistinct/trailersDistinct
Use { __proto__: null } instead of {} when initializing the headersDistinct and trailersDistinct destination objects. A plain {} inherits from Object.prototype, so when a __proto__ header is received, dest["__proto__"] resolves to Object.prototype (truthy), causing _addHeaderLineDistinct to call .push() on it, which throws an uncaught TypeError and crashes the process. Ref: https://hackerone.com/reports/3560402 PR-URL: nodejs-private/node-private#821 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> CVE-ID: CVE-2026-21710
1 parent df8fbfb commit 380ea72

File tree

3 files changed

+46
-10
lines changed

3 files changed

+46
-10
lines changed

lib/_http_incoming.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', {
128128
__proto__: null,
129129
get: function() {
130130
if (!this[kHeadersDistinct]) {
131-
this[kHeadersDistinct] = {};
131+
this[kHeadersDistinct] = { __proto__: null };
132132

133133
const src = this.rawHeaders;
134134
const dst = this[kHeadersDistinct];
@@ -168,7 +168,7 @@ ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
168168
__proto__: null,
169169
get: function() {
170170
if (!this[kTrailersDistinct]) {
171-
this[kTrailersDistinct] = {};
171+
this[kTrailersDistinct] = { __proto__: null };
172172

173173
const src = this.rawTrailers;
174174
const dst = this[kTrailersDistinct];
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
const net = require('net');
7+
8+
// Regression test: sending a __proto__ header must not crash the server
9+
// when accessing req.headersDistinct or req.trailersDistinct.
10+
11+
const server = http.createServer(common.mustCall((req, res) => {
12+
const headers = req.headersDistinct;
13+
assert.strictEqual(Object.getPrototypeOf(headers), null);
14+
assert.deepStrictEqual(Object.getOwnPropertyDescriptor(headers, '__proto__').value, ['test']);
15+
res.end();
16+
}));
17+
18+
server.listen(0, common.mustCall(() => {
19+
const port = server.address().port;
20+
21+
const client = net.connect(port, common.mustCall(() => {
22+
client.write(
23+
'GET / HTTP/1.1\r\n' +
24+
'Host: localhost\r\n' +
25+
'__proto__: test\r\n' +
26+
'Connection: close\r\n' +
27+
'\r\n',
28+
);
29+
}));
30+
31+
client.on('end', common.mustCall(() => {
32+
server.close();
33+
}));
34+
35+
client.resume();
36+
}));

test/parallel/test-http-multiple-headers.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ const server = createServer(
2626
host,
2727
'transfer-encoding': 'chunked'
2828
});
29-
assert.deepStrictEqual(req.headersDistinct, {
29+
assert.deepStrictEqual(req.headersDistinct, Object.assign({ __proto__: null }, {
3030
'connection': ['close'],
3131
'x-req-a': ['eee', 'fff', 'ggg', 'hhh'],
3232
'x-req-b': ['iii; jjj; kkk; lll'],
3333
'host': [host],
34-
'transfer-encoding': ['chunked']
35-
});
34+
'transfer-encoding': ['chunked'],
35+
}));
3636

3737
req.on('end', common.mustCall(() => {
3838
assert.deepStrictEqual(req.rawTrailers, [
@@ -45,7 +45,7 @@ const server = createServer(
4545
);
4646
assert.deepStrictEqual(
4747
req.trailersDistinct,
48-
{ 'x-req-x': ['xxx', 'yyy'], 'x-req-y': ['zzz; www'] }
48+
Object.assign({ __proto__: null }, { 'x-req-x': ['xxx', 'yyy'], 'x-req-y': ['zzz; www'] })
4949
);
5050

5151
res.setHeader('X-Res-a', 'AAA');
@@ -132,14 +132,14 @@ server.listen(0, common.mustCall(() => {
132132
'x-res-d': 'JJJ; KKK; LLL',
133133
'transfer-encoding': 'chunked'
134134
});
135-
assert.deepStrictEqual(res.headersDistinct, {
135+
assert.deepStrictEqual(res.headersDistinct, Object.assign({ __proto__: null }, {
136136
'x-res-a': [ 'AAA', 'BBB', 'CCC' ],
137137
'x-res-b': [ 'DDD; EEE; FFF; GGG' ],
138138
'connection': [ 'close' ],
139139
'x-res-c': [ 'HHH', 'III' ],
140140
'x-res-d': [ 'JJJ; KKK; LLL' ],
141-
'transfer-encoding': [ 'chunked' ]
142-
});
141+
'transfer-encoding': [ 'chunked' ],
142+
}));
143143

144144
res.on('end', common.mustCall(() => {
145145
assert.deepStrictEqual(res.rawTrailers, [
@@ -153,7 +153,7 @@ server.listen(0, common.mustCall(() => {
153153
);
154154
assert.deepStrictEqual(
155155
res.trailersDistinct,
156-
{ 'x-res-x': ['XXX', 'YYY'], 'x-res-y': ['ZZZ; WWW'] }
156+
Object.assign({ __proto__: null }, { 'x-res-x': ['XXX', 'YYY'], 'x-res-y': ['ZZZ; WWW'] })
157157
);
158158
server.close();
159159
}));

0 commit comments

Comments
 (0)