Skip to content

docs: Unsafe example for converting a message.url to an URL #52494

@mlegenhausen

Description

@mlegenhausen

Affected URL(s)

https://nodejs.org/api/http.html#messageurl

Description of the problem

Following the proposed way of converting request.url to an URL object you can easily come up with the following implementation:

import * as http from 'node:http';

const server = http.createServer((req, res) => {
  console.log(new URL(req.url, `http://${req.headers.host}`));
  res.end();
});

server.listen(3000);

This simple implementation lacks two issues.

  1. You can crash the server via
curl http://localhost:3000//

with following message

TypeError: Invalid URL
    at new URL (node:internal/url:775:36)
    at Server.<anonymous> (file:///server.js:3:26)
    at Server.emit (node:events:518:28)
    at parserOnIncoming (node:_http_server:1151:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17) {
  code: 'ERR_INVALID_URL',
  input: '//',
  base: 'http://localhost:3000'
}
  1. You can change the host to whatever you want
curl http://localhost:3000//evil.com/foo/bar

this generates following URL

URL {
  href: 'http://evil.com/foo/bar',
  origin: 'http://evil.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'evil.com',
  hostname: 'evil.com',
  port: '',
  pathname: '/foo/bar',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

This might have security implications if you e. g. base your CSRF protection on the parsed URL host value.

A better approach would it be to concatenate the host with the req.url instead of using the baseUrl parameter from the URL constructor.

import * as http from 'node:http';

const server = http.createServer((req, res) => {
  console.log(new URL(`http://${req.headers.host}${req.url}`));
  res.end();
});

server.listen(3000);

If you now repeat both curls. You get

  1. No crash
URL {
  href: 'http://localhost:3000//',
  origin: 'http://localhost:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost:3000',
  hostname: 'localhost',
  port: '3000',
  pathname: '//',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}
  1. No host change
URL {
  href: 'http://localhost:3000//evil.com/foo/bar',
  origin: 'http://localhost:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost:3000',
  hostname: 'localhost',
  port: '3000',
  pathname: '//evil.com/foo/bar',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to the documentations.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions