CVE-2018-3774
Description
Incorrect parsing in url-parse <1.4.3 returns wrong hostname which leads to multiple vulnerabilities such as SSRF, Open Redirect, Bypass Authentication Protocol.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
url-parse prior to 1.4.3 incorrectly parses hostnames from URLs, enabling SSRF, open redirect, and authentication bypass attacks.
Vulnerability
The url-parse library for Node.js and browsers prior to version 1.4.3 contains a hostname parsing flaw [1]. The parser does not correctly extract the hostname component from maliciously crafted URL strings, which can lead to misinterpretation of the target host [1]. All versions before 1.4.3 are affected.
Exploitation
An attacker can craft a URL with an embedded hostname that differs from the one actually parsed by url-parse. No special network position or authentication is required; the victim application merely needs to process the attacker-supplied URL using the vulnerable library [1]. The attacker simply supplies a specially formatted URL string to the parsing function.
Impact
Successful exploitation can lead to Server-Side Request Forgery (SSRF), Open Redirect, or Authentication Bypass, depending on how the application uses the parsed hostname [1]. The attacker controls which host the application believes it is communicating with, potentially gaining access to internal resources or tricking users into visiting malicious sites.
Mitigation
Update url-parse to version 1.4.3 or later, which corrects the parsing logic [2][1]. The fix was committed in revision 209c296d302317268afbe19700a70c63ecbeb2d2 [2]. No workaround is provided; upgrading is the recommended action.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
url-parsenpm | >= 1.0.0, < 1.4.3 | 1.4.3 |
Affected products
2- HackerOne/url-parsev5Range: 1.4.3
Patches
3d7b582ec1243[security] Added missing SECURITY.md
1 file changed · +47 −0
SECURITY.md+47 −0 added@@ -0,0 +1,47 @@ +# Security Guidelines + +Please contact us directly at **security@3rd-Eden.com** for any bug that might +impact the security of this project. Please prefix the subject of your email +with `[security]` in lowercase and square brackets. Our email filters will +automatically prevent these messages from being moved to our spam box. All +emails that do not include security vulnerabilities will be removed and blocked +instantly. + +In addition to a dedicated email address to receive security related reports, +we also have a [Hacker1 account][hacker1] that can be used be used for +communicating security related issues. + +You will receive an acknowledgement of your report within **24 hours** of +notification. + +## Exceptions + +If you do not receive an acknowledgement within the said time frame please give +us the benefit of the doubt as it's possible that we haven't seen it yet. In +this case please send us a message **without details** using one of the +following methods: + +- Give a poke on Twitter [@3rdEden](https://twitter.com/3rdEden) +- Contact the lead developers of this project on their personal e-mails. You + can find the e-mails in the git logs, for example using the following command: + `git --no-pager show -s --format='%an <%ae>' <gitsha>` where `<gitsha>` is the + SHA1 of their latest commit in the project. + +Once we have acknowledged receipt of your report and confirmed the bug +ourselves we will work with you to fix the vulnerability and publicly +acknowledge your responsible disclosure, if you wish. + +## History + +> url-parse returns wrong hostname which leads to multiple vulnerabilities such +> as SSRF, Open Redirect, Bypass Authentication Protocol. + +- Hacker1 report: https://hackerone.com/reports/384029 +- Reported by [lolwaleet](https://hackerone.com/lolwalee) +- Triaged by [Liran Tal](https://hackerone.com/lirantal) +- Fixed in: 1.4.3 + +--- + +[twitter]: https://twitter.com/3rdEden +[hacker1]: https://hackerone.com/3rdeden
53b1794e54d0[security] Sanitize paths, hosts before parsing.
2 files changed · +50 −9
index.js+21 −9 modified@@ -20,6 +20,9 @@ var required = require('requires-port') var rules = [ ['#', 'hash'], // Extract from the back. ['?', 'query'], // Extract from the back. + function sanitize(address) { // Sanitize what is left of the address + return address.replace('\\', '/'); + }, ['/', 'pathname'], // Extract from the back. ['@', 'auth', 1], // Extract from the front. [NaN, 'host', undefined, 1, 1], // Set left over value. @@ -47,7 +50,7 @@ var ignore = { hash: 1, query: 1 }; * * @param {Object|String} loc Optional default location object. * @returns {Object} lolcation object. - * @api public + * @public */ function lolcation(loc) { var location = global && global.location || {}; @@ -89,7 +92,7 @@ function lolcation(loc) { * * @param {String} address URL we want to extract from. * @return {ProtocolExtract} Extracted information. - * @api private + * @private */ function extractProtocol(address) { var match = protocolre.exec(address); @@ -107,7 +110,7 @@ function extractProtocol(address) { * @param {String} relative Pathname of the relative URL. * @param {String} base Pathname of the base URL. * @return {String} Resolved pathname. - * @api private + * @private */ function resolve(relative, base) { var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) @@ -140,11 +143,14 @@ function resolve(relative, base) { * create an actual constructor as it's much more memory efficient and * faster and it pleases my OCD. * + * It is worth noting that we should not use `URL` as class name to prevent + * clashes with the global URL instance that got introduced in browsers. + * * @constructor * @param {String} address URL we want to parse. * @param {Object|String} location Location defaults for relative paths. * @param {Boolean|Function} parser Parser for the query string. - * @api public + * @private */ function Url(address, location, parser) { if (!(this instanceof Url)) { @@ -190,10 +196,16 @@ function Url(address, location, parser) { // When the authority component is absent the URL starts with a path // component. // - if (!extracted.slashes) instructions[2] = [/(.*)/, 'pathname']; + if (!extracted.slashes) instructions[3] = [/(.*)/, 'pathname']; for (; i < instructions.length; i++) { instruction = instructions[i]; + + if (typeof instruction === 'function') { + address = instruction(address); + continue; + } + parse = instruction[0]; key = instruction[1]; @@ -284,8 +296,8 @@ function Url(address, location, parser) { * used to parse the query. * When setting the protocol, double slash will be * removed from the final url if it is true. - * @returns {URL} - * @api public + * @returns {URL} URL instance for chaining. + * @public */ function set(part, value, fn) { var url = this; @@ -370,8 +382,8 @@ function set(part, value, fn) { * Transform the properties back in to a valid and full URL string. * * @param {Function} stringify Optional query stringify function. - * @returns {String} - * @api public + * @returns {String} Compiled version of the URL. + * @public */ function toString(stringify) { if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;
test/test.js+29 −0 modified@@ -192,6 +192,28 @@ describe('url-parse', function () { assume(parsed.pathname).equals('/b/c'); }); + it('ignores \\ in pathnames', function () { + var url = 'http://google.com:80\\@yahoo.com/#what\\is going on' + , parsed = parse(url); + + assume(parsed.port).equals(''); + assume(parsed.username).equals(''); + assume(parsed.password).equals(''); + assume(parsed.hostname).equals('google.com'); + assume(parsed.hash).equals('#what\\is going on'); + + parsed = parse('//\\what-is-up.com'); + assume(parsed.pathname).equals('/what-is-up.com'); + }); + + it('correctly ignores multiple slashes //', function () { + var url = '////what-is-up.com' + , parsed = parse(url); + + assume(parsed.host).equals(''); + assume(parsed.hostname).equals(''); + }); + describe('origin', function () { it('generates an origin property', function () { var url = 'http://google.com:80/pathname' @@ -252,6 +274,13 @@ describe('url-parse', function () { o = parse('wss://google.com:80/pathname'); assume(o.origin).equals('wss://google.com:80'); }); + + it('maintains the port number for non-default port numbers', function () { + var parsed = parse('http://google.com:8080/pathname'); + + assume(parsed.host).equals('http://google.com:8080'); + assume(parsed.href).equals('http://google.com:8080/pathname'); + }); }); describe('protocol', function () {
209c296d3023[major] Instruction based parsing.
1 file changed · +58 −78
index.js+58 −78 modified@@ -2,12 +2,31 @@ var required = require('requires-port') , lolcation = require('./lolcation') - , qs = require('querystringify'); + , qs = require('querystringify') + , relativere = /^\/(?!\/)/; -var keys = ',,protocol,username,password,host,hostname,port,pathname,query,hash'.split(',') - , inherit = { protocol: 1, host: 1, hostname: 1 } - , relativere = /^\/(?!\/)/ - , parts = keys.length; +/** + * These are the parse instructions for the URL parsers, it informs the parser + * about: + * + * 0. The char it Needs to parse, if it's a string it should be done using + * indexOf, RegExp using exec and NaN means set as current value. + * 1. The property we should set when parsing this value. + * 2. Indication if it's backwards or forward parsing, when set as number it's + * the value of extra chars that should be split off. + * 3. Inherit from location if non existing in the parser. + * 4. `toLowerCase` the resulting value. + */ +var instructions = [ + ['#', 'hash'], // Extract from the back. + ['?', 'query'], // Extract from the back. + ['//', 'protocol', 2, 1, 1], // Extract from the front. + ['@', 'auth', 1], // Extract from the front. + ['/', 'pathname'], // Extract from the back. + [NaN, 'host', undefined, 1, 1], // Set left over value. + [/\:(\d+)$/, 'port'], // RegExp the back. + [NaN, 'hostname', undefined, 1, 1] // Set left over. +]; /** * The actual URL instance. Instead of returning an object we've opted-in to @@ -25,73 +44,11 @@ function URL(address, location, parser) { return new URL(address, location, parser); } - // - // Story time children: - // - // FireFox 34 has some problems with their Regular Expression engine and - // executing a RegExp can cause a `too much recursion` error. We initially fixed - // this by moving the Regular Expression in the URL constructor so it's created - // every single time. This fixed it for some URL's but the more complex the - // URL's get the easier it is to trigger. Complexer URL like: - // - // https://www.mozilla.org/en-US/firefox/34.0/whatsnew/?oldversion=33.1 - // - // Still triggered the recursion error. After talking with Chrome and FireFox - // engineers it seemed to be caused by: - // - // https://code.google.com/p/v8/issues/detail?id=430 - // - // As FireFox started using Chrome's RegExp engine. After testing various of - // workarounds I finally stumbled upon this gem, use new RegExp as it sometimes - // behaves different then a RegExp literal. The biggest problem with this - // FireFox problem is that it's super hard to reproduce as our "normal" test - // suite doesn't catch it. The only way to reproduce it was run the parser in - // jsperf.com (uses the benchmark module from npm) and supply it the URL - // mentioned above as URL to parse. - // - // Steps for compiling the new RegExp: - // - // 1. Take the regular RegExp as seen below. - // 2. Escape the RegExp using XRegExp.escape from http://xregexp.com/tests/ - // 3. ?? - // 4. Profit. - // - // RegExp source: /^(?:(?:(([^:\/#\?]+:)?(?:(?:\/\/)(?:(?:(?:([^:@\/#\?]+)(?:\:([^:@\/#\?]*))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((?:\/?(?:[^\/\?#]+\/+)*)(?:[^\?#]*)))?(\?[^#]+)?)(#.*)?/ - // var relative = relativere.test(address) + , parse, instruction, index, key , type = typeof location , url = this - , i; - - // backwards parsing - if (~(i = address.indexOf('#'))) { - url.hash = address.slice(i); - address = address.slice(0, i); - } - - // backwards parsing - if (~(i = address.indexOf('?'))) { - url.query = address.slice(i); - address = address.slice(0, i); - } - - // forward parsing - if (~(i = address.indexOf('//'))) { - url.protocol = address.slice(0, i); - address = address.slice(i); - } - - // forward parsing - if (~(i = address.indexOf('@'))) { - url.auth = address.slice(0, i); - address = address.slice(i + 1); - } - - // backwards parsing - if (~(i = address.indexOf('/'))) { - url.pathname = address.slice(i); - address = address.slice(0, i); - } + , i = 0; // // The following if statements allows this module two have compatibility with @@ -115,17 +72,31 @@ function URL(address, location, parser) { location = lolcation(location); - for (; i < parts; key = keys[++i]) { - if (!key) continue; + for (; i < instructions.length; i++) { + instruction = instructions[i]; + parse = instruction[0]; + key = instruction[1]; + + if (parse !== parse) { + url[key] = address; + } else if ('string' === typeof parse) { + if (~(index = address.indexOf(parse))) { + if ('number' === typeof instruction[2]) { + url[key] = address.slice(0, index); + address = address.slice(index + instruction[2]); + } else { + url[key] = address.slice(index); + address = address.slice(0, index); + } + } + } else if (index = parse.exec(address)) { + url[key] = index[1]; + address = address.slice(0, address.length - index[0].length); + } - url[key] = bits[i] || (key in inherit || ('port' === key && relative) ? location[key] || '' : ''); + url[key] = url[key] || (instruction[3] || ('port' === key && relative) ? location[key] || '' : ''); - // - // The protocol, host, host name should always be lower cased even if they - // are supplied in uppercase. This way, when people generate an `origin` - // it be correct. - // - if (i === 2 || i === 5 || i === 6) url[key] = url[key].toLowerCase(); + if (instruction[4]) url[key] = url[key].toLowerCase(); } // @@ -145,6 +116,15 @@ function URL(address, location, parser) { url.port = ''; } + // + // Parse down the `auth` for the username and password. + // + if (url.auth) { + instruction = url.auth.split(':'); + url.username = instruction[0]; + url.password = instruction[1]; + } + // // The href is just the compiled result. //
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-pv4c-p2j5-38j4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-3774ghsaADVISORY
- github.com/unshiftio/url-parse/commit/209c296d302317268afbe19700a70c63ecbeb2d2ghsaWEB
- github.com/unshiftio/url-parse/commit/53b1794e54d0711ceb52505e0f74145270570d5aghsax_refsource_CONFIRMWEB
- github.com/unshiftio/url-parse/commit/d7b582ec1243e8024e60ac0b62d2569c939ef5deghsax_refsource_CONFIRMWEB
- github.com/unshiftio/url-parse/compare/0.2.3...1.0.0ghsaWEB
- hackerone.com/reports/384029ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.