Permalink
Browse files

http, querystring: added limits to prevent DoS

  • Loading branch information...
1 parent 2202887 commit fabc966419df8f6dc578da13c1ecdc4bf3440e7c @indutny committed Jan 15, 2012
Showing with 49 additions and 6 deletions.
  1. +6 −0 doc/api/http.markdown
  2. +4 −1 doc/api/querystring.markdown
  3. +22 −1 lib/http.js
  4. +11 −3 lib/querystring.js
  5. +6 −1 test/simple/test-querystring.js
View
6 doc/api/http.markdown
@@ -143,6 +143,12 @@ Stops the server from accepting new connections.
See [net.Server.close()](net.html#server.close).
+### server.maxHeadersCount
+
+Limits maximum incoming headers count, equal to 1000 by default. If set to 0 -
+no limit will be applied.
+
+
## http.ServerRequest
This object is created internally by a HTTP server -- not by
View
5 doc/api/querystring.markdown
@@ -19,12 +19,15 @@ Example:
// returns
'foo:bar;baz:qux'
-### querystring.parse(str, [sep], [eq])
+### querystring.parse(str, [sep], [eq], [options])
Deserialize a query string to an object.
Optionally override the default separator (`'&'`) and assignment (`'='`)
characters.
+Options object may contain `maxKeys` property (equal to 1000 by default), it'll
+be used to limit processed keys. Set it to 0 to remove key count limitation.
+
Example:
querystring.parse('foo=bar&baz=qux&baz=quux&corge')
View
23 lib/http.js
@@ -43,6 +43,10 @@ var parsers = new FreeList('parsers', 1000, function() {
parser._headers = [];
parser._url = '';
+ // Limit incoming headers count as it may cause
+ // hash collision DoS
+ parser.maxHeadersCount = 1000;
+
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
@@ -78,7 +82,14 @@ var parsers = new FreeList('parsers', 1000, function() {
parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor;
parser.incoming.url = url;
- for (var i = 0, n = headers.length; i < n; i += 2) {
+ var n = headers.length;
+
+ // If parser.maxHeadersCount <= 0 - assume that there're no limit
+ if (parser.maxHeadersCount > 0) {
+ n = Math.min(n, parser.maxHeadersCount << 1);
+ }
+
+ for (var i = 0; i < n; i += 2) {
var k = headers[i];
var v = headers[i + 1];
parser.incoming._addHeaderLine(k.toLowerCase(), v);
@@ -1158,6 +1169,11 @@ ClientRequest.prototype.onSocket = function(socket) {
parser.incoming = null;
req.parser = parser;
+ // Propagate headers limit from request object to parser
+ if (req.maxHeadersCount) {
+ parser.maxHeadersCount = req.maxHeadersCount;
+ }
+
socket._httpMessage = req;
// Setup "drain" propogation.
httpSocketSetup(socket);
@@ -1444,6 +1460,11 @@ function connectionListener(socket) {
parser.socket = socket;
parser.incoming = null;
+ // Propagate headers limit from server instance to parser
+ if (this.maxHeadersCount) {
+ parser.maxHeadersCount = this.maxHeadersCount;
+ }
+
socket.addListener('error', function(e) {
self.emit('clientError', e);
});
View
14 lib/querystring.js
@@ -160,16 +160,24 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) {
};
// Parse a key=val string.
-QueryString.parse = QueryString.decode = function(qs, sep, eq) {
+QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
- var obj = {};
+ var obj = {},
+ maxKeys = options && options.maxKeys || 1000;
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
- qs.split(sep).forEach(function(kvp) {
+ qs = qs.split(sep);
+
+ // maxKeys <= 0 means that we should not limit keys count
+ if (maxKeys > 0) {
+ qs = qs.slice(0, maxKeys);
+ }
+
+ qs.forEach(function(kvp) {
var x = kvp.split(eq);
var k = QueryString.unescape(x[0], true);
var v = QueryString.unescape(x.slice(1).join(eq), true);
View
7 test/simple/test-querystring.js
@@ -183,6 +183,12 @@ assert.equal(f, 'a:b;q:x%3Ay%3By%3Az');
assert.deepEqual({}, qs.parse());
+// Test limiting
+assert.equal(
+ Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length,
+ 1
+);
+
var b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' +
'%0d%ac%a2%2f%9d%eb%d8%a2%e6');
@@ -207,4 +213,3 @@ assert.equal(0xeb, b[16]);
assert.equal(0xd8, b[17]);
assert.equal(0xa2, b[18]);
assert.equal(0xe6, b[19]);
-

0 comments on commit fabc966

Please sign in to comment.