From 2aa7e0e53d510f0e32504661e279439a13bbe985 Mon Sep 17 00:00:00 2001 From: Jade Michael Thornton Date: Tue, 12 Oct 2021 22:43:22 -0500 Subject: [PATCH] fix crash on redirect with formfeed in URL Fixes a vulnerability inherited from ecstatic, reported in CVE-2019-10775. --- lib/core/index.js | 17 ++++++++++++++--- test/main.test.js | 13 +++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/core/index.js b/lib/core/index.js index b4311ff4..9b845d81 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -31,6 +31,11 @@ function decodePathname(pathname) { ? normalized.replace(/\\/g, '/') : normalized; } +const nonUrlSafeCharsRgx = /[\x00-\x1F\x20\x7F-\uFFFF]+/g; +function ensureUriEncoded(text) { + return text + return String(text).replace(nonUrlSafeCharsRgx, encodeURIComponent); +} // Check to see if we should try to compress a file with gzip. function shouldCompressGzip(req) { @@ -161,7 +166,8 @@ module.exports = function createMiddleware(_dir, _options) { if (opts.weakCompare && clientEtag !== serverEtag && clientEtag !== `W/${serverEtag}` && `W/${clientEtag}` !== serverEtag) { return false; - } else if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) { + } + if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) { return false; } } @@ -340,8 +346,10 @@ module.exports = function createMiddleware(_dir, _options) { }, res, next); } else { // Try to serve default ./404.html + const rawUrl = (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url); + const encodedUrl = ensureUriEncoded(rawUrl); middleware({ - url: (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url), + url: encodedUrl, headers: req.headers, statusCode: 404, }, res, next); @@ -359,7 +367,10 @@ module.exports = function createMiddleware(_dir, _options) { if (!pathname.match(/\/$/)) { res.statusCode = 302; const q = parsed.query ? `?${parsed.query}` : ''; - res.setHeader('location', `${parsed.pathname}/${q}`); + res.setHeader( + 'location', + ensureUriEncoded(`${parsed.pathname}/${q}`) + ); res.end(); return; } diff --git a/test/main.test.js b/test/main.test.js index cead9647..df4c65b8 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -87,6 +87,19 @@ test('http-server main', (t) => { .indexOf('X-Test') >= 0, 204); }).catch(err => t.fail(err.toString())), + t.test( + "Regression: don't crash on control characters in query strings", + {}, + (t) => { + requestAsync({ + uri: encodeURI('http://localhost:8080/file?\x0cfoo'), + }).then(res => { + t.equal(res.statusCode, 200); + }).catch(err => t.fail(err.toString())) + .finally(() => t.end()); + } + ), + // Light compression testing. Heavier compression tests exist in // compression.test.js requestAsync({