Skip to content

Commit 8a98c2f

Browse files
committed
http, querystring: added limits to prevent DoS
1 parent 93465d3 commit 8a98c2f

5 files changed

Lines changed: 49 additions & 6 deletions

File tree

doc/api/http.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ Stops the server from accepting new connections.
143143
See [net.Server.close()](net.html#server.close).
144144

145145

146+
### server.maxHeadersCount
147+
148+
Limits maximum incoming headers count, equal to 1000 by default. If set to 0 -
149+
no limit will be applied.
150+
151+
146152
## http.ServerRequest
147153

148154
This object is created internally by a HTTP server -- not by

doc/api/querystring.markdown

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ Example:
1919
// returns
2020
'foo:bar;baz:qux'
2121

22-
### querystring.parse(str, [sep], [eq])
22+
### querystring.parse(str, [sep], [eq], [options])
2323

2424
Deserialize a query string to an object.
2525
Optionally override the default separator (`'&'`) and assignment (`'='`)
2626
characters.
2727

28+
Options object may contain `maxKeys` property (equal to 1000 by default), it'll
29+
be used to limit processed keys. Set it to 0 to remove key count limitation.
30+
2831
Example:
2932

3033
querystring.parse('foo=bar&baz=qux&baz=quux&corge')

lib/http.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ var parsers = new FreeList('parsers', 1000, function() {
4343
parser._headers = [];
4444
parser._url = '';
4545

46+
// Limit incoming headers count as it may cause
47+
// hash collision DoS
48+
parser.maxHeadersCount = 1000;
49+
4650
// Only called in the slow case where slow means
4751
// that the request headers were either fragmented
4852
// across multiple TCP packets or too large to be
@@ -78,7 +82,14 @@ var parsers = new FreeList('parsers', 1000, function() {
7882
parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor;
7983
parser.incoming.url = url;
8084

81-
for (var i = 0, n = headers.length; i < n; i += 2) {
85+
var n = headers.length;
86+
87+
// If parser.maxHeadersCount <= 0 - assume that there're no limit
88+
if (parser.maxHeadersCount > 0) {
89+
n = Math.min(n, parser.maxHeadersCount << 1);
90+
}
91+
92+
for (var i = 0; i < n; i += 2) {
8293
var k = headers[i];
8394
var v = headers[i + 1];
8495
parser.incoming._addHeaderLine(k.toLowerCase(), v);
@@ -1158,6 +1169,11 @@ ClientRequest.prototype.onSocket = function(socket) {
11581169
parser.incoming = null;
11591170
req.parser = parser;
11601171

1172+
// Propagate headers limit from request object to parser
1173+
if (req.maxHeadersCount) {
1174+
parser.maxHeadersCount = req.maxHeadersCount;
1175+
}
1176+
11611177
socket._httpMessage = req;
11621178
// Setup "drain" propogation.
11631179
httpSocketSetup(socket);
@@ -1444,6 +1460,11 @@ function connectionListener(socket) {
14441460
parser.socket = socket;
14451461
parser.incoming = null;
14461462

1463+
// Propagate headers limit from server instance to parser
1464+
if (this.maxHeadersCount) {
1465+
parser.maxHeadersCount = this.maxHeadersCount;
1466+
}
1467+
14471468
socket.addListener('error', function(e) {
14481469
self.emit('clientError', e);
14491470
});

lib/querystring.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,24 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) {
160160
};
161161

162162
// Parse a key=val string.
163-
QueryString.parse = QueryString.decode = function(qs, sep, eq) {
163+
QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
164164
sep = sep || '&';
165165
eq = eq || '=';
166-
var obj = {};
166+
var obj = {},
167+
maxKeys = options && options.maxKeys || 1000;
167168

168169
if (typeof qs !== 'string' || qs.length === 0) {
169170
return obj;
170171
}
171172

172-
qs.split(sep).forEach(function(kvp) {
173+
qs = qs.split(sep);
174+
175+
// maxKeys <= 0 means that we should not limit keys count
176+
if (maxKeys > 0) {
177+
qs = qs.slice(0, maxKeys);
178+
}
179+
180+
qs.forEach(function(kvp) {
173181
var x = kvp.split(eq);
174182
var k = QueryString.unescape(x[0], true);
175183
var v = QueryString.unescape(x.slice(1).join(eq), true);

test/simple/test-querystring.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ assert.equal(f, 'a:b;q:x%3Ay%3By%3Az');
183183
assert.deepEqual({}, qs.parse());
184184

185185

186+
// Test limiting
187+
assert.equal(
188+
Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length,
189+
1
190+
);
191+
186192

187193
var b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' +
188194
'%0d%ac%a2%2f%9d%eb%d8%a2%e6');
@@ -207,4 +213,3 @@ assert.equal(0xeb, b[16]);
207213
assert.equal(0xd8, b[17]);
208214
assert.equal(0xa2, b[18]);
209215
assert.equal(0xe6, b[19]);
210-

0 commit comments

Comments
 (0)