From 8e13c116be5459fdb8f8efadb79aa7ed07b2bab9 Mon Sep 17 00:00:00 2001 From: rcannon Date: Thu, 23 Jul 2015 12:05:05 -0700 Subject: [PATCH] Handle backslashes like Node.js and Chrome [RFC 2396][] section 2.4.3 puts backslashes (`\`) in the "unwise" list of characters that aren't allowed in URIs. However, IE, Opera and Chrome normalize backslashes to slashes (`/`), as noted in [Chromium][]. Since URI.js doesn't do this, it creates possible vulnerabilities. For example: ```js var page = URI(window.location.href); var redirect = URI(page.search(true).redirect_uri); if (page.domain() === redirect.domain()) { window.location = redirect.href(); } ``` This logic will work fine, except when `redirect` has backslashes in the host, e.g. ``` http://i.xss.com\www.example.org/foo ``` In this case, you'll get: ```js URI("http://www.example.org").domain(); // example.org URI("http://i.xss.com\\www.example.org/foo").domain(); // example.org ``` ...yet the browsers will redirect you to ``` http://i.xss.com/www.example.org/foo ``` which could be a phishing site. The supplied change simply replaces all backslashes before the query/hash with slashes. This workaround is also in [Node][Node]. [RFC 2396]: https://www.ietf.org/rfc/rfc2396.txt [Chromium]: https://code.google.com/p/chromium/issues/detail?id=25916 [Node]: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124 --- src/URI.js | 6 ++++++ test/urls.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/URI.js b/src/URI.js index 938e49f2..aeafdfdf 100644 --- a/src/URI.js +++ b/src/URI.js @@ -483,6 +483,12 @@ string = string.substring(0, pos); } + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://github.com/joyent/node/blob/master/lib/url.js + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + string = string.replace(/\\/g, '/'); + // extract protocol if (string.substring(0, 2) === '//') { // relative-scheme diff --git a/test/urls.js b/test/urls.js index 3960676d..4c2cb547 100644 --- a/test/urls.js +++ b/test/urls.js @@ -1752,6 +1752,54 @@ var urls = [{ idn: false, punycode: false } + }, { + name: 'backslashes', + url: 'http://i.xss.com\\www.example.org/some/directory/file.html?query=string#fragment', + _url: 'http://i.xss.com/www.example.org/some/directory/file.html?query=string#fragment', + parts: { + protocol: 'http', + username: null, + password: null, + hostname: 'i.xss.com', + port: null, + path: '/www.example.org/some/directory/file.html', + query: 'query=string', + fragment: 'fragment' + }, + accessors: { + protocol: 'http', + username: '', + password: '', + port: '', + path: '/www.example.org/some/directory/file.html', + query: 'query=string', + fragment: 'fragment', + resource: '/www.example.org/some/directory/file.html?query=string#fragment', + authority: 'i.xss.com', + userinfo: '', + subdomain: 'i', + domain: 'xss.com', + tld: 'com', + directory: '/www.example.org/some/directory', + filename: 'file.html', + suffix: 'html', + hash: '#fragment', + search: '?query=string', + host: 'i.xss.com', + hostname: 'i.xss.com' + }, + is: { + urn: false, + url: true, + relative: false, + name: true, + sld: false, + ip: false, + ip4: false, + ip6: false, + idn: false, + punycode: false + } } ];