Skip to content

Commit 86c0478

Browse files
mcollinaRafaelGSS
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 Refs: https://hackerone.com/reports/3560402 Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> CVE-ID: CVE-2026-21710
1 parent 5197a56 commit 86c0478

3 files changed

Lines changed: 46 additions & 10 deletions

File tree

‎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
constsrc=this.rawHeaders;
134134
constdst=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
constsrc=this.rawTrailers;
174174
constdst=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+
constcommon=require('../common');
4+
constassert=require('assert');
5+
consthttp=require('http');
6+
constnet=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+
constserver=http.createServer(common.mustCall((req,res)=>{
12+
constheaders=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+
constport=server.address().port;
20+
21+
constclient=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)