Skip to content

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

Closed
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions