Server: Add host whitelisting (#4476)
* Add host whitelisting middleware * Add prompt to enable hostWhitelist * perf: Freeze config array * Update src/middleware/hostWhitelist.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * perf: Add max known hosts limit * Add validation warning disable hint * Add conditional host whitelist middleware based on SSL configuration * Check for cache exhaustion before logging * Revert "Add conditional host whitelist middleware based on SSL configuration" This reverts commit 968104c6f4f2e4b72e1fd8ceff0a4b0ded216d69. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -94,6 +94,18 @@ autheliaAuth: false
|
||||
# the username and passwords for basic auth are the same as those
|
||||
# for the individual accounts
|
||||
perUserBasicAuth: false
|
||||
# Host whitelist configuration. Recommended if you're using a listen mode
|
||||
hostWhitelist:
|
||||
# Enable or disable host whitelisting
|
||||
enabled: false
|
||||
# Scan incoming requests for potential host header spoofing
|
||||
scan: true
|
||||
# List of allowed hosts. Do not include localhost or IPs, these are safe.
|
||||
# Use a dot to create subdomain patterns.
|
||||
# Examples:
|
||||
# - example.com
|
||||
# - .trycloudflare.com
|
||||
hosts: []
|
||||
|
||||
# User session timeout *in seconds* (defaults to 24 hours).
|
||||
## Set to a positive number to expire session after a certain time of inactivity
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Forbidden</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Forbidden</h1>
|
||||
<p>
|
||||
If you are the system administrator, add the hostname you are accessing from to the
|
||||
host whitelist, or disable host whitelisting in the
|
||||
<code>config.yaml</code> file located in the root directory of your installation.
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
<em>Access from this host is not allowed. This attempt has been logged.</em>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Generated
+10
@@ -64,6 +64,7 @@
|
||||
"handlebars": "^4.7.8",
|
||||
"helmet": "^8.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"host-validation-middleware": "^0.1.1",
|
||||
"html-entities": "^2.6.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ip-matching": "^2.1.2",
|
||||
@@ -5249,6 +5250,15 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/host-validation-middleware": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/host-validation-middleware/-/host-validation-middleware-0.1.1.tgz",
|
||||
"integrity": "sha512-fakcpp+x4nbP0fACY5gaHWpaOfstq3w8uB6wvhbPBLqH9GV/tdiM9Ht5mclZVbUuPLGBw1bkH5yyTD6HZq057g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"handlebars": "^4.7.8",
|
||||
"helmet": "^8.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"host-validation-middleware": "^0.1.1",
|
||||
"html-entities": "^2.6.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ip-matching": "^2.1.2",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import path from 'node:path';
|
||||
import { color, getConfigValue, safeReadFileSync } from '../util.js';
|
||||
import { serverDirectory } from '../server-directory.js';
|
||||
import { isHostAllowed, hostValidationMiddleware } from 'host-validation-middleware';
|
||||
|
||||
const knownHosts = new Set();
|
||||
const maxKnownHosts = 1000;
|
||||
|
||||
const hostWhitelistEnabled = !!getConfigValue('hostWhitelist.enabled', false);
|
||||
const hostWhitelist = Object.freeze(getConfigValue('hostWhitelist.hosts', []));
|
||||
const hostWhitelistScan = !!getConfigValue('hostWhitelist.scan', false, 'boolean');
|
||||
|
||||
const hostNotAllowedHtml = safeReadFileSync(path.join(serverDirectory, 'public/error/host-not-allowed.html'))?.toString() ?? '';
|
||||
|
||||
const validationMiddleware = hostValidationMiddleware({
|
||||
allowedHosts: hostWhitelist,
|
||||
generateErrorMessage: () => hostNotAllowedHtml,
|
||||
errorResponseContentType: 'text/html',
|
||||
});
|
||||
|
||||
/**
|
||||
* Middleware to validate remote hosts.
|
||||
* Useful to protect against DNS rebinding attacks.
|
||||
* @param {import('express').Request} req Request
|
||||
* @param {import('express').Response} res Response
|
||||
* @param {import('express').NextFunction} next Next middleware
|
||||
*/
|
||||
export default function hostWhitelistMiddleware(req, res, next) {
|
||||
const hostValue = req.headers.host;
|
||||
if (hostWhitelistScan && !isHostAllowed(hostValue, hostWhitelist) && !knownHosts.has(hostValue) && knownHosts.size < maxKnownHosts) {
|
||||
const isFirstWarning = knownHosts.size === 0;
|
||||
console.warn(color.red('Request from untrusted host:'), hostValue);
|
||||
console.warn(`If you trust this host, you can add it to ${color.yellow('hostWhitelist.hosts')} in config.yaml`);
|
||||
if (!hostWhitelistEnabled && isFirstWarning) {
|
||||
console.warn(`To protect against host spoofing, consider setting ${color.yellow('hostWhitelist.enabled')} to true`);
|
||||
}
|
||||
if (isFirstWarning) {
|
||||
console.warn(`To disable this warning, set ${color.yellow('hostWhitelist.scan')} to false`);
|
||||
}
|
||||
knownHosts.add(hostValue);
|
||||
}
|
||||
|
||||
if (!hostWhitelistEnabled) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return validationMiddleware(req, res, next);
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import multerMonkeyPatch from './middleware/multerMonkeyPatch.js';
|
||||
import initRequestProxy from './request-proxy.js';
|
||||
import cacheBuster from './middleware/cacheBuster.js';
|
||||
import corsProxyMiddleware from './middleware/corsProxy.js';
|
||||
import hostWhitelistMiddleware from './middleware/hostWhitelist.js';
|
||||
import {
|
||||
getVersion,
|
||||
color,
|
||||
@@ -116,6 +117,8 @@ if (cliArgs.whitelistMode) {
|
||||
app.use(whitelistMiddleware);
|
||||
}
|
||||
|
||||
app.use(hostWhitelistMiddleware);
|
||||
|
||||
if (cliArgs.listen) {
|
||||
app.use(accessLoggerMiddleware());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user