VYPR
Moderate severityNVD Advisory· Published Feb 21, 2021· Updated Aug 3, 2024

CVE-2021-27515

CVE-2021-27515

Description

url-parse before 1.5.0 mishandles certain uses of backslash such as http:\/ and interprets the URI as a relative path.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

url-parse before 1.5.0 incorrectly parses backslashes like http:\/ as relative paths, enabling SSRF or open redirect.

Vulnerability

Summary

The url-parse library before version 1.5.0 suffers from a parsing flaw where certain uses of backslash characters in URLs, such as http:\/, are not handled correctly. Instead of treating these as absolute HTTP URLs, the library interprets them as relative paths, which can lead to incorrect URL resolution and potential security vulnerabilities [1][2].

Exploitation

This issue specifically arises when an attacker supplies a URL containing backslashes (e.g., http:\/attacker.com or https:\/github.com/foo/bar). The library miscalculates the host and pathname, effectively treating the intended authority as part of the path. This behavior can be triggered without special authentication or network position in applications that parse user-supplied URLs, such as redirect handlers or proxy services [2][3].

Impact

An attacker could exploit this parsing inconsistency to craft URLs that bypass security checks intended for external destinations. For instance, a filter that blocks unsafe redirects might permit http:\/malicious.com because the parser treats it as a relative path, leading to open redirect, Server-Side Request Forgery (SSRF), or content spoofing. The impact varies based on how the parsed URL is used by the consuming application [1][3].

Mitigation

This vulnerability is fixed in url-parse version 1.5.0. Users should upgrade to at least that release. The patch includes additional test cases to ensure backslash handling matches expected behavior [2][4]. The project maintainers also recommend considering the native WHATWG URL API for better security and accuracy in modern environments [3].

AI Insight generated on May 21, 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.

PackageAffected versionsPatched versions
url-parsenpm
>= 0.1.0, < 1.5.01.5.0

Affected products

2

Patches

1
d1e7e8822f26

[security] More backslash fixes (#197)

https://github.com/unshiftio/url-parseArnout KazemierFeb 17, 2021via ghsa
4 files changed · +85 26
  • index.js+16 5 modified
    @@ -2,8 +2,8 @@
     
     var required = require('requires-port')
       , qs = require('querystringify')
    -  , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//
    -  , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\S\s]*)/i
    +  , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:[\\/]+/
    +  , protocolre = /^([a-z][a-z0-9.+-]*:)?([\\/]{1,})?([\S\s]*)/i
       , whitespace = '[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]'
       , left = new RegExp('^'+ whitespace +'+');
     
    @@ -115,11 +115,14 @@ function lolcation(loc) {
      */
     function extractProtocol(address) {
       address = trimLeft(address);
    -  var match = protocolre.exec(address);
    +
    +  var match = protocolre.exec(address)
    +    , protocol = match[1] ? match[1].toLowerCase() : ''
    +    , slashes = !!(match[2] && match[2].length >= 2);
     
       return {
    -    protocol: match[1] ? match[1].toLowerCase() : '',
    -    slashes: !!match[2],
    +    protocol: protocol,
    +    slashes: slashes,
         rest: match[3]
       };
     }
    @@ -280,6 +283,14 @@ function Url(address, location, parser) {
         url.pathname = resolve(url.pathname, location.pathname);
       }
     
    +  //
    +  // Default to a / for pathname if none exists. This normalizes the URL
    +  // to always have a /
    +  //
    +  if (url.pathname.charAt(0) !== '/' && url.hostname) {
    +    url.pathname = '/' + url.pathname;
    +  }
    +
       //
       // We should not add port numbers if they are already the default port number
       // for a given protocol. As the host also contains the port number we're going
    
  • SECURITY.md+10 1 modified
    @@ -33,13 +33,22 @@ acknowledge your responsible disclosure, if you wish.
     
     ## History
     
    +> Using backslash in the protocol is valid in the browser, while url-parse
    +> thinks it’s a relative path. An application that validates a url using
    +> url-parse might pass a malicious link.
    +
    +- **Reporter credits**
    +  - CxSCA AppSec team at Checkmarx.
    +  - Twitter: [Yaniv Nizry](https://twitter.com/ynizry)
    +- Fixed in: 1.5.0
    +
     > The `extractProtocol` method does not return the correct protocol when
     > provided with unsanitized content which could lead to false positives.
     
     - **Reporter credits**
       - Reported through our security email & Twitter interaction.
       - Twitter: [@ronperris](https://twitter.com/ronperris)
    -  - Fixed in: 1.4.5
    +- Fixed in: 1.4.5
     
     ---
     
    
  • test/fuzzy.js+2 0 modified
    @@ -103,6 +103,8 @@ module.exports = function generate() {
         , key;
     
       spec.protocol = get('protocol');
    +  spec.slashes = true;
    +  
       spec.hostname = get('hostname');
       spec.pathname = get('pathname');
     
    
  • test/test.js+57 20 modified
    @@ -190,9 +190,10 @@ describe('url-parse', function () {
           , parsed = parse(url);
     
         assume(parsed.port).equals('');
    +    assume(parsed.pathname).equals('/');
         assume(parsed.host).equals('example.com');
         assume(parsed.hostname).equals('example.com');
    -    assume(parsed.href).equals('http://example.com');
    +    assume(parsed.href).equals('http://example.com/');
       });
     
       it('understands an / as pathname', function () {
    @@ -242,16 +243,30 @@ describe('url-parse', function () {
         assume(parsed.hostname).equals('google.com');
         assume(parsed.hash).equals('#what\\is going on');
     
    -    parsed = parse('//\\what-is-up.com');
    +    parsed = parse('http://yolo.com\\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('');
    +    assume(parsed.host).equals('what-is-up.com');
    +    assume(parsed.href).equals('//what-is-up.com/');
    +  });
    +
    +  it('does not see a slash after the protocol as path', function () {
    +    var url = 'https:\\/github.com/foo/bar'
    +      , parsed = parse(url);
    +
    +    assume(parsed.host).equals('github.com');
    +    assume(parsed.hostname).equals('github.com');
    +    assume(parsed.pathname).equals('/foo/bar');
    +
    +    url = 'https:/\/\/\github.com/foo/bar';
    +    assume(parsed.host).equals('github.com');
    +    assume(parsed.hostname).equals('github.com');
    +    assume(parsed.pathname).equals('/foo/bar');
       });
     
       describe('origin', function () {
    @@ -327,32 +342,52 @@ describe('url-parse', function () {
         it('extracts the right protocol from a url', function () {
           var testData = [
             {
    -          href: 'http://example.com',
    +          href: 'http://example.com/',
               protocol: 'http:',
    -          pathname: ''
    +          pathname: '/',
    +          slashes: true
    +        },
    +        {
    +          href: 'ws://example.com/',
    +          protocol: 'ws:',
    +          pathname: '/',
    +          slashes: true
    +        },
    +        {
    +          href: 'wss://example.com/',
    +          protocol: 'wss:',
    +          pathname: '/',
    +          slashes: true
             },
             {
               href: 'mailto:test@example.com',
               pathname: 'test@example.com',
    -          protocol: 'mailto:'
    +          protocol: 'mailto:',
    +          slashes: false
             },
             {
               href: 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E',
               pathname: 'text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E',
    -          protocol: 'data:'
    +          protocol: 'data:',
    +          slashes: false,
             },
             {
               href: 'sip:alice@atlanta.com',
               pathname: 'alice@atlanta.com',
    -          protocol: 'sip:'
    +          protocol: 'sip:',
    +          slashes: false,
             }
           ];
     
    -      var data;
    +      var data, test;
           for (var i = 0, len = testData.length; i < len; ++i) {
    -        data = parse(testData[i].href);
    -        assume(data.protocol).equals(testData[i].protocol);
    -        assume(data.pathname).equals(testData[i].pathname);
    +        test = testData[i];
    +        data = parse(test.href);
    +
    +        assume(data.protocol).equals(test.protocol);
    +        assume(data.pathname).equals(test.pathname);
    +        assume(data.slashes).equals(test.slashes);
    +        assume(data.href).equals(test.href);
           }
         });
     
    @@ -391,13 +426,14 @@ describe('url-parse', function () {
         });
     
         it('parses ipv6 with auth', function () {
    -      var url = 'http://user:password@[3ffe:2a00:100:7031::1]:8080'
    +      var url = 'http://user:password@[3ffe:2a00:100:7031::1]:8080/'
             , parsed = parse(url);
     
           assume(parsed.username).equals('user');
           assume(parsed.password).equals('password');
           assume(parsed.host).equals('[3ffe:2a00:100:7031::1]:8080');
           assume(parsed.hostname).equals('[3ffe:2a00:100:7031::1]');
    +      assume(parsed.pathname).equals('/');
           assume(parsed.href).equals(url);
         });
     
    @@ -467,7 +503,7 @@ describe('url-parse', function () {
     
           assume(data.port).equals('');
           assume(data.host).equals('localhost');
    -      assume(data.href).equals('http://localhost');
    +      assume(data.href).equals('http://localhost/');
         });
     
         it('inherits port numbers for relative urls', function () {
    @@ -516,7 +552,8 @@ describe('url-parse', function () {
         });
     
         it('inherits protocol for relative protocols', function () {
    -      var data = parse('//foo.com/foo', parse('http://sub.example.com:808/'));
    +      var lolcation = parse('http://sub.example.com:808/')
    +        , data = parse('//foo.com/foo', lolcation);
     
           assume(data.port).equals('');
           assume(data.host).equals('foo.com');
    @@ -529,13 +566,13 @@ describe('url-parse', function () {
     
           assume(data.port).equals('');
           assume(data.host).equals('localhost');
    -      assume(data.href).equals('http://localhost');
    +      assume(data.href).equals('http://localhost/');
         });
     
         it('resolves pathname for relative urls', function () {
           var data, i = 0;
           var tests = [
    -        ['', 'http://foo.com', ''],
    +        ['', 'http://foo.com', '/'],
             ['', 'http://foo.com/', '/'],
             ['', 'http://foo.com/a', '/a'],
             ['a', 'http://foo.com', '/a'],
    @@ -722,12 +759,12 @@ describe('url-parse', function () {
           data.set('hash', 'usage');
     
           assume(data.hash).equals('#usage');
    -      assume(data.href).equals('http://example.com#usage');
    +      assume(data.href).equals('http://example.com/#usage');
     
           data.set('hash', '#license');
     
           assume(data.hash).equals('#license');
    -      assume(data.href).equals('http://example.com#license');
    +      assume(data.href).equals('http://example.com/#license');
         });
     
         it('updates the port when updating host', function () {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.