Skip to content

Commit f1ed420

Browse files
committed
Constrain spaces before = to 256
Side-steps ReDoS in Issue #92
1 parent fcc8abf commit f1ed420

File tree

2 files changed

+114
-3
lines changed

2 files changed

+114
-3
lines changed

Diff for: lib/cookie.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ var CONTROL_CHARS = /[\x00-\x1F]/;
5858
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
5959
// '=' and ';' are attribute/values separators
6060
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
61-
var COOKIE_PAIR = /^(([^=;]+))\s*=\s*([^\n\r\0]*)/;
61+
var COOKIE_PAIR = /^(([^=;]+))\s{0,256}=\s*([^\n\r\0]*)/;
6262

6363
// Used to parse non-RFC-compliant cookies like '=abc' when given the `loose`
6464
// option in Cookie.parse:
65-
var LOOSE_COOKIE_PAIR = /^((?:=)?([^=;]*)\s*=\s*)?([^\n\r\0]*)/;
65+
var LOOSE_COOKIE_PAIR = /^((?:=)?([^=;]*)\s{0,256}=\s*)?([^\n\r\0]*)/;
6666

6767
// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
6868
// Note ';' is \x3B

Diff for: test/parsing_test.js

+112-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ var assert = require('assert');
3636
var tough = require('../lib/cookie');
3737
var Cookie = tough.Cookie;
3838

39+
var LOTS_OF_SEMICOLONS = ';'.repeat(65535);
40+
var LOTS_OF_SPACES = ' '.repeat(65535);
41+
3942
vows
4043
.describe('Parsing')
4144
.addBatch({
@@ -327,7 +330,7 @@ vows
327330
"way too many semicolons followed by non-semicolon": {
328331
topic: function() {
329332
// takes abnormally long due to semi-catastrophic regexp backtracking
330-
var str = 'foo=bar' + (';'.repeat(65535)) + ' domain=example.com';
333+
var str = 'foo=bar' + LOTS_OF_SEMICOLONS + ' domain=example.com';
331334
return Cookie.parse(str) || null;
332335
},
333336
"parsed": function(c) { assert.ok(c) },
@@ -336,6 +339,114 @@ vows
336339
"no path": function(c) { assert.equal(c.path, null) },
337340
"no domain": function(c) { assert.equal(c.domain, 'example.com') },
338341
"no extensions": function(c) { assert.ok(!c.extensions) }
342+
},
343+
"way too many spaces": {
344+
topic: function() {
345+
// takes abnormally long due to semi-catastrophic regexp backtracking
346+
var str1 = "x" + LOTS_OF_SPACES + "x";
347+
var str2 = "x x";
348+
var t0 = Date.now();
349+
var cookie1 = Cookie.parse(str1) || null;
350+
var t1 = Date.now();
351+
var cookie2 = Cookie.parse(str2) || null;
352+
var t2 = Date.now();
353+
return { cookie1: cookie1, cookie2: cookie2, dt1: t1-t0, dt2: t2-t1 };
354+
},
355+
"large one doesn't parse": function(c) { assert.equal(c.cookie1, null) },
356+
"small one doesn't parse": function(c) { assert.equal(c.cookie2, null) },
357+
"takes about the same time for each": function(c) {
358+
var long1 = c.dt1 + 1; // avoid 0ms
359+
var short2 = c.dt2 + 1; // avoid 0ms
360+
var ratio = Math.abs(long1 / short2);
361+
assert.lesser(ratio, 250); // if broken, goes 2000-4000x
362+
}
363+
},
364+
"way too many spaces with value": {
365+
topic: function() {
366+
// takes abnormally long due to semi-catastrophic regexp backtracking
367+
var str1 = "x" + LOTS_OF_SPACES + "=x";
368+
var str2 = "x =x";
369+
var t0 = Date.now();
370+
var cookie1 = Cookie.parse(str1) || null;
371+
var t1 = Date.now();
372+
var cookie2 = Cookie.parse(str2) || null;
373+
var t2 = Date.now();
374+
return { cookie1: cookie1, cookie2: cookie2, dt1: t1-t0, dt2: t2-t1 };
375+
},
376+
"large one parses": function(c) {
377+
assert.ok(c.cookie1);
378+
assert.equal(c.cookie1.key, "x");
379+
assert.equal(c.cookie1.value, "x");
380+
},
381+
"small one parses": function(c) {
382+
assert.ok(c.cookie2)
383+
assert.equal(c.cookie2.key, "x");
384+
assert.equal(c.cookie2.value, "x");
385+
},
386+
"takes about the same time for each": function(c) {
387+
var long1 = c.dt1 + 1; // avoid 0ms
388+
var short2 = c.dt2 + 1; // avoid 0ms
389+
var ratio = Math.abs(long1 / short2);
390+
assert.lesser(ratio, 250); // if broken, goes 2000-4000x
391+
}
392+
},
393+
"way too many spaces in loose mode": {
394+
topic: function() {
395+
// takes abnormally long due to semi-catastrophic regexp backtracking
396+
var str1 = "x" + LOTS_OF_SPACES + "x";
397+
var str2 = "x x";
398+
var t0 = Date.now();
399+
var cookie1 = Cookie.parse(str1, {loose:true}) || null;
400+
var t1 = Date.now();
401+
var cookie2 = Cookie.parse(str2, {loose:true}) || null;
402+
var t2 = Date.now();
403+
return { cookie1: cookie1, cookie2: cookie2, dt1: t1-t0, dt2: t2-t1 };
404+
},
405+
"large one parses": function(c) {
406+
assert.ok(c.cookie1);
407+
assert.equal(c.cookie1.key, "");
408+
assert.equal(c.cookie1.value, "x" + LOTS_OF_SPACES + "x");
409+
},
410+
"small one parses": function(c) {
411+
assert.ok(c.cookie2)
412+
assert.equal(c.cookie2.key, "");
413+
assert.equal(c.cookie2.value, "x x");
414+
},
415+
"takes about the same time for each": function(c) {
416+
var long1 = c.dt1 + 1; // avoid 0ms
417+
var short2 = c.dt2 + 1; // avoid 0ms
418+
var ratio = Math.abs(long1 / short2);
419+
assert.lesser(ratio, 250); // if broken, goes 2000-4000x
420+
}
421+
},
422+
"way too many spaces with value in loose mode": {
423+
topic: function() {
424+
// takes abnormally long due to semi-catastrophic regexp backtracking
425+
var str1 = "x" + LOTS_OF_SPACES + "=x";
426+
var str2 = "x =x";
427+
var t0 = Date.now();
428+
var cookie1 = Cookie.parse(str1, {loose:true}) || null;
429+
var t1 = Date.now();
430+
var cookie2 = Cookie.parse(str2, {loose:true}) || null;
431+
var t2 = Date.now();
432+
return { cookie1: cookie1, cookie2: cookie2, dt1: t1-t0, dt2: t2-t1 };
433+
},
434+
"large one parses": function(c) {
435+
assert.ok(c.cookie1);
436+
assert.equal(c.cookie1.key, "x");
437+
assert.equal(c.cookie1.value, "x");
438+
},
439+
"small one parses": function(c) {
440+
assert.ok(c.cookie2)
441+
assert.equal(c.cookie2.key, "x");
442+
assert.equal(c.cookie2.value, "x");
443+
},
444+
"takes about the same time for each": function(c) {
445+
var long1 = c.dt1 + 1; // avoid 0ms
446+
var short2 = c.dt2 + 1; // avoid 0ms
447+
var ratio = Math.abs(long1 / short2);
448+
assert.lesser(ratio, 250); // if broken, goes 2000-4000x
449+
}
339450
}
340451
})
341452
.export(module);

0 commit comments

Comments
 (0)