From 84e7388160e0da819d12f3922dd8573e3e7b1e9a Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 17 Oct 2022 01:22:04 -0700 Subject: [PATCH] url: improve port validation If a port is not a number, throw rather than treating the `:` that delineates the port as part of the path. This is consistent with WHATWG URL and also mitigates hostname-spoofing. Concerns about hostname-spoofing were raised and presented in excellent detail by pyozzi-toss (pyozzi@toss.im/Security-Tech Team in Toss). PR-URL: https://github.com/nodejs/node/pull/45012 Reviewed-By: Rafael Gonzaga Reviewed-By: Yagiz Nizipli Reviewed-By: Antoine du Hamel Reviewed-By: James M Snell --- lib/url.js | 8 ++++++-- test/parallel/test-url-parse-format.js | 16 ---------------- test/parallel/test-url-parse-invalid-input.js | 12 ++++++++++++ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/url.js b/lib/url.js index d5462be6332c3e..fa7a3d21d20963 100644 --- a/lib/url.js +++ b/lib/url.js @@ -383,7 +383,7 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { // validate a little. if (!ipv6Hostname) { - rest = getHostname(this, rest, hostname); + rest = getHostname(this, rest, hostname, url); } if (this.hostname.length > hostnameMaxLen) { @@ -502,7 +502,7 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { return this; }; -function getHostname(self, rest, hostname) { +function getHostname(self, rest, hostname, url) { for (let i = 0; i < hostname.length; ++i) { const code = hostname.charCodeAt(i); const isValid = (code !== CHAR_FORWARD_SLASH && @@ -512,6 +512,10 @@ function getHostname(self, rest, hostname) { code !== CHAR_COLON); if (!isValid) { + // If leftover starts with :, then it represents an invalid port. + if (hostname.charCodeAt(i) === 58) { + throw new ERR_INVALID_URL(url); + } self.hostname = hostname.slice(0, i); return `/${hostname.slice(i)}${rest}`; } diff --git a/test/parallel/test-url-parse-format.js b/test/parallel/test-url-parse-format.js index 0da66f8f99fab0..f046df74b1c0f7 100644 --- a/test/parallel/test-url-parse-format.js +++ b/test/parallel/test-url-parse-format.js @@ -865,22 +865,6 @@ const parseTests = { href: 'http://a%0D%22%20%09%0A%3C\'b:b@c/%0D%0Ad/e?f' }, - // Git urls used by npm - 'git+ssh://git@github.com:npm/npm': { - protocol: 'git+ssh:', - slashes: true, - auth: 'git', - host: 'github.com', - port: null, - hostname: 'github.com', - hash: null, - search: null, - query: null, - pathname: '/:npm/npm', - path: '/:npm/npm', - href: 'git+ssh://git@github.com/:npm/npm' - }, - 'https://*': { protocol: 'https:', slashes: true, diff --git a/test/parallel/test-url-parse-invalid-input.js b/test/parallel/test-url-parse-invalid-input.js index 794a756524925b..7ab86380ad7384 100644 --- a/test/parallel/test-url-parse-invalid-input.js +++ b/test/parallel/test-url-parse-invalid-input.js @@ -74,3 +74,15 @@ if (common.hasIntl) { (e) => e.code === 'ERR_INVALID_URL', 'parsing http://\u00AD/bad.com/'); } + +{ + const badURLs = [ + 'https://evil.com:.example.com', + 'git+ssh://git@github.com:npm/npm', + ]; + badURLs.forEach((badURL) => { + assert.throws(() => { url.parse(badURL); }, + (e) => e.code === 'ERR_INVALID_URL', + `parsing ${badURL}`); + }); +}