docs: Unsafe example for converting a `message.url` to an `URL`
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.
- 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'
}
- 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
- 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: ''
}
- 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: ''
}