aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2022-10-27 14:33:31 +0200
committerChristoph Wurst <christoph@winzerhof-wurst.at>2022-10-31 16:13:28 +0100
commit8aea25b5b92dac105f7e862470ee0dcf0e876615 (patch)
tree3095f0a58eb70e1c21117ce9c3450a1e60e323ba /lib
parentaa81b87f26552bc3d49de6cf0babfe6a79c21af5 (diff)
downloadnextcloud-server-8aea25b5b92dac105f7e862470ee0dcf0e876615.tar.gz
nextcloud-server-8aea25b5b92dac105f7e862470ee0dcf0e876615.zip
Add remote host validation API
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php5
-rw-r--r--lib/composer/composer/autoload_static.php5
-rw-r--r--lib/private/Http/Client/Client.php18
-rw-r--r--lib/private/Http/Client/ClientService.php10
-rw-r--r--lib/private/Http/Client/DnsPinMiddleware.php14
-rw-r--r--lib/private/Http/Client/LocalAddressChecker.php102
-rw-r--r--lib/private/Net/HostnameClassifier.php74
-rw-r--r--lib/private/Net/IpAddressClassifier.php81
-rw-r--r--lib/private/Security/RemoteHostValidator.php76
-rw-r--r--lib/private/Server.php15
-rw-r--r--lib/public/Security/IRemoteHostValidator.php51
11 files changed, 318 insertions, 133 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 2b5475f6efb..397f36e660b 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -535,6 +535,7 @@ return array(
'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php',
'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php',
'OCP\\Security\\IHasher' => $baseDir . '/lib/public/Security/IHasher.php',
+ 'OCP\\Security\\IRemoteHostValidator' => $baseDir . '/lib/public/Security/IRemoteHostValidator.php',
'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\ITrustedDomainHelper' => $baseDir . '/lib/public/Security/ITrustedDomainHelper.php',
'OCP\\Security\\VerificationToken\\IVerificationToken' => $baseDir . '/lib/public/Security/VerificationToken/IVerificationToken.php',
@@ -1288,7 +1289,6 @@ return array(
'OC\\Http\\Client\\Client' => $baseDir . '/lib/private/Http/Client/Client.php',
'OC\\Http\\Client\\ClientService' => $baseDir . '/lib/private/Http/Client/ClientService.php',
'OC\\Http\\Client\\DnsPinMiddleware' => $baseDir . '/lib/private/Http/Client/DnsPinMiddleware.php',
- 'OC\\Http\\Client\\LocalAddressChecker' => $baseDir . '/lib/private/Http/Client/LocalAddressChecker.php',
'OC\\Http\\Client\\NegativeDnsCache' => $baseDir . '/lib/private/Http/Client/NegativeDnsCache.php',
'OC\\Http\\Client\\Response' => $baseDir . '/lib/private/Http/Client/Response.php',
'OC\\Http\\CookieHelper' => $baseDir . '/lib/private/Http/CookieHelper.php',
@@ -1362,6 +1362,8 @@ return array(
'OC\\NaturalSort_DefaultCollator' => $baseDir . '/lib/private/NaturalSort_DefaultCollator.php',
'OC\\NavigationManager' => $baseDir . '/lib/private/NavigationManager.php',
'OC\\NeedsUpdateException' => $baseDir . '/lib/private/NeedsUpdateException.php',
+ 'OC\\Net\\HostnameClassifier' => $baseDir . '/lib/private/Net/HostnameClassifier.php',
+ 'OC\\Net\\IpAddressClassifier' => $baseDir . '/lib/private/Net/IpAddressClassifier.php',
'OC\\NotSquareException' => $baseDir . '/lib/private/NotSquareException.php',
'OC\\Notification\\Action' => $baseDir . '/lib/private/Notification/Action.php',
'OC\\Notification\\Manager' => $baseDir . '/lib/private/Notification/Manager.php',
@@ -1517,6 +1519,7 @@ return array(
'OC\\Security\\RateLimiting\\Backend\\MemoryCacheBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/MemoryCacheBackend.php',
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => $baseDir . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',
'OC\\Security\\RateLimiting\\Limiter' => $baseDir . '/lib/private/Security/RateLimiting/Limiter.php',
+ 'OC\\Security\\RemoteHostValidator' => $baseDir . '/lib/private/Security/RemoteHostValidator.php',
'OC\\Security\\SecureRandom' => $baseDir . '/lib/private/Security/SecureRandom.php',
'OC\\Security\\TrustedDomainHelper' => $baseDir . '/lib/private/Security/TrustedDomainHelper.php',
'OC\\Security\\VerificationToken\\CleanUpJob' => $baseDir . '/lib/private/Security/VerificationToken/CleanUpJob.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index ad62c3585e6..4c430720ef1 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -568,6 +568,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php',
'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php',
'OCP\\Security\\IHasher' => __DIR__ . '/../../..' . '/lib/public/Security/IHasher.php',
+ 'OCP\\Security\\IRemoteHostValidator' => __DIR__ . '/../../..' . '/lib/public/Security/IRemoteHostValidator.php',
'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\ITrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/public/Security/ITrustedDomainHelper.php',
'OCP\\Security\\VerificationToken\\IVerificationToken' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/IVerificationToken.php',
@@ -1321,7 +1322,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Http\\Client\\Client' => __DIR__ . '/../../..' . '/lib/private/Http/Client/Client.php',
'OC\\Http\\Client\\ClientService' => __DIR__ . '/../../..' . '/lib/private/Http/Client/ClientService.php',
'OC\\Http\\Client\\DnsPinMiddleware' => __DIR__ . '/../../..' . '/lib/private/Http/Client/DnsPinMiddleware.php',
- 'OC\\Http\\Client\\LocalAddressChecker' => __DIR__ . '/../../..' . '/lib/private/Http/Client/LocalAddressChecker.php',
'OC\\Http\\Client\\NegativeDnsCache' => __DIR__ . '/../../..' . '/lib/private/Http/Client/NegativeDnsCache.php',
'OC\\Http\\Client\\Response' => __DIR__ . '/../../..' . '/lib/private/Http/Client/Response.php',
'OC\\Http\\CookieHelper' => __DIR__ . '/../../..' . '/lib/private/Http/CookieHelper.php',
@@ -1395,6 +1395,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\NaturalSort_DefaultCollator' => __DIR__ . '/../../..' . '/lib/private/NaturalSort_DefaultCollator.php',
'OC\\NavigationManager' => __DIR__ . '/../../..' . '/lib/private/NavigationManager.php',
'OC\\NeedsUpdateException' => __DIR__ . '/../../..' . '/lib/private/NeedsUpdateException.php',
+ 'OC\\Net\\HostnameClassifier' => __DIR__ . '/../../..' . '/lib/private/Net/HostnameClassifier.php',
+ 'OC\\Net\\IpAddressClassifier' => __DIR__ . '/../../..' . '/lib/private/Net/IpAddressClassifier.php',
'OC\\NotSquareException' => __DIR__ . '/../../..' . '/lib/private/NotSquareException.php',
'OC\\Notification\\Action' => __DIR__ . '/../../..' . '/lib/private/Notification/Action.php',
'OC\\Notification\\Manager' => __DIR__ . '/../../..' . '/lib/private/Notification/Manager.php',
@@ -1550,6 +1552,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Security\\RateLimiting\\Backend\\MemoryCacheBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/MemoryCacheBackend.php',
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',
'OC\\Security\\RateLimiting\\Limiter' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Limiter.php',
+ 'OC\\Security\\RemoteHostValidator' => __DIR__ . '/../../..' . '/lib/private/Security/RemoteHostValidator.php',
'OC\\Security\\SecureRandom' => __DIR__ . '/../../..' . '/lib/private/Security/SecureRandom.php',
'OC\\Security\\TrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/private/Security/TrustedDomainHelper.php',
'OC\\Security\\VerificationToken\\CleanUpJob' => __DIR__ . '/../../..' . '/lib/private/Security/VerificationToken/CleanUpJob.php',
diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php
index d4dba3e5a44..2e370395132 100644
--- a/lib/private/Http/Client/Client.php
+++ b/lib/private/Http/Client/Client.php
@@ -37,8 +37,11 @@ use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\RequestOptions;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IResponse;
+use OCP\Http\Client\LocalServerException;
use OCP\ICertificateManager;
use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
+use function parse_url;
/**
* Class Client
@@ -52,19 +55,18 @@ class Client implements IClient {
private $config;
/** @var ICertificateManager */
private $certificateManager;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IRemoteHostValidator $remoteHostValidator;
public function __construct(
IConfig $config,
ICertificateManager $certificateManager,
GuzzleClient $client,
- LocalAddressChecker $localAddressChecker
+ IRemoteHostValidator $remoteHostValidator
) {
$this->config = $config;
$this->client = $client;
$this->certificateManager = $certificateManager;
- $this->localAddressChecker = $localAddressChecker;
+ $this->remoteHostValidator = $remoteHostValidator;
}
private function buildRequestOptions(array $options): array {
@@ -181,7 +183,13 @@ class Client implements IClient {
return;
}
- $this->localAddressChecker->throwIfLocalAddress($uri);
+ $host = parse_url($uri, PHP_URL_HOST);
+ if ($host === false || $host === null) {
+ throw new LocalServerException('Could not detect any host');
+ }
+ if (!$this->remoteHostValidator->isValid($host)) {
+ throw new LocalServerException('Host violates local access rules');
+ }
}
/**
diff --git a/lib/private/Http/Client/ClientService.php b/lib/private/Http/Client/ClientService.php
index e868d4af7a5..bbc2330176f 100644
--- a/lib/private/Http/Client/ClientService.php
+++ b/lib/private/Http/Client/ClientService.php
@@ -33,6 +33,7 @@ use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\ICertificateManager;
use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
/**
* Class ClientService
@@ -46,17 +47,16 @@ class ClientService implements IClientService {
private $certificateManager;
/** @var DnsPinMiddleware */
private $dnsPinMiddleware;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IRemoteHostValidator $remoteHostValidator;
public function __construct(IConfig $config,
ICertificateManager $certificateManager,
DnsPinMiddleware $dnsPinMiddleware,
- LocalAddressChecker $localAddressChecker) {
+ IRemoteHostValidator $remoteHostValidator) {
$this->config = $config;
$this->certificateManager = $certificateManager;
$this->dnsPinMiddleware = $dnsPinMiddleware;
- $this->localAddressChecker = $localAddressChecker;
+ $this->remoteHostValidator = $remoteHostValidator;
}
/**
@@ -73,7 +73,7 @@ class ClientService implements IClientService {
$this->config,
$this->certificateManager,
$client,
- $this->localAddressChecker
+ $this->remoteHostValidator,
);
}
}
diff --git a/lib/private/Http/Client/DnsPinMiddleware.php b/lib/private/Http/Client/DnsPinMiddleware.php
index 00bc209d7b1..294a23f9de1 100644
--- a/lib/private/Http/Client/DnsPinMiddleware.php
+++ b/lib/private/Http/Client/DnsPinMiddleware.php
@@ -25,20 +25,21 @@ declare(strict_types=1);
*/
namespace OC\Http\Client;
+use OC\Net\IpAddressClassifier;
+use OCP\Http\Client\LocalServerException;
use Psr\Http\Message\RequestInterface;
class DnsPinMiddleware {
/** @var NegativeDnsCache */
private $negativeDnsCache;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IpAddressClassifier $ipAddressClassifier;
public function __construct(
NegativeDnsCache $negativeDnsCache,
- LocalAddressChecker $localAddressChecker
+ IpAddressClassifier $ipAddressClassifier
) {
$this->negativeDnsCache = $negativeDnsCache;
- $this->localAddressChecker = $localAddressChecker;
+ $this->ipAddressClassifier = $ipAddressClassifier;
}
/**
@@ -133,7 +134,10 @@ class DnsPinMiddleware {
$curlResolves["$hostName:$port"] = [];
foreach ($targetIps as $ip) {
- $this->localAddressChecker->throwIfLocalIp($ip);
+ if (!$this->ipAddressClassifier->isLocalAddress($ip)) {
+ // TODO: continue with all non-local IPs?
+ throw new LocalServerException('Host violates local access rules');
+ }
$curlResolves["$hostName:$port"][] = $ip;
}
}
diff --git a/lib/private/Http/Client/LocalAddressChecker.php b/lib/private/Http/Client/LocalAddressChecker.php
deleted file mode 100644
index eb24f002d7d..00000000000
--- a/lib/private/Http/Client/LocalAddressChecker.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-namespace OC\Http\Client;
-
-use IPLib\Address\IPv6;
-use IPLib\Factory;
-use IPLib\ParseStringFlag;
-use OCP\Http\Client\LocalServerException;
-use Psr\Log\LoggerInterface;
-use Symfony\Component\HttpFoundation\IpUtils;
-
-class LocalAddressChecker {
- private LoggerInterface $logger;
-
- public function __construct(LoggerInterface $logger) {
- $this->logger = $logger;
- }
-
- public function throwIfLocalIp(string $ip) : void {
- $parsedIp = Factory::parseAddressString(
- $ip,
- ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
- );
- if ($parsedIp === null) {
- /* Not an IP */
- return;
- }
- /* Replace by normalized form */
- if ($parsedIp instanceof IPv6) {
- $ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
- } else {
- $ip = (string)$parsedIp;
- }
-
- $localRanges = [
- '100.64.0.0/10', // See RFC 6598
- '192.0.0.0/24', // See RFC 6890
- ];
- if (
- (bool)filter_var($ip, FILTER_VALIDATE_IP) &&
- (
- !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
- IpUtils::checkIp($ip, $localRanges)
- )) {
- $this->logger->warning("Host $ip was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
- }
-
- public function throwIfLocalAddress(string $uri) : void {
- $host = parse_url($uri, PHP_URL_HOST);
- if ($host === false || $host === null) {
- $this->logger->warning("Could not detect any host in $uri");
- throw new LocalServerException('Could not detect any host');
- }
-
- $host = idn_to_utf8(strtolower(urldecode($host)));
- // Remove brackets from IPv6 addresses
- if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
- $host = substr($host, 1, -1);
- }
-
- // Disallow local network top-level domains from RFC 6762
- $localTopLevelDomains = ['local','localhost','intranet','internal','private','corp','home','lan'];
- $topLevelDomain = substr((strrchr($host, '.') ?: ''), 1);
- if (in_array($topLevelDomain, $localTopLevelDomains)) {
- $this->logger->warning("Host $host was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
-
- // Disallow hostname only
- if (substr_count($host, '.') === 0 && !(bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
- $this->logger->warning("Host $host was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
-
- $this->throwIfLocalIp($host);
- }
-}
diff --git a/lib/private/Net/HostnameClassifier.php b/lib/private/Net/HostnameClassifier.php
new file mode 100644
index 00000000000..626aa47083e
--- /dev/null
+++ b/lib/private/Net/HostnameClassifier.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Net;
+
+use function filter_var;
+use function in_array;
+use function strrchr;
+use function substr;
+use function substr_count;
+
+/**
+ * Classifier for network hostnames
+ *
+ * @internal
+ */
+class HostnameClassifier {
+ private const LOCAL_TOPLEVEL_DOMAINS = [
+ 'local',
+ 'localhost',
+ 'intranet',
+ 'internal',
+ 'private',
+ 'corp',
+ 'home',
+ 'lan',
+ ];
+
+ /**
+ * Check host identifier for local hostname
+ *
+ * IP addresses are not considered local. Use the IpAddressClassifier for those.
+ *
+ * @param string $hostname
+ *
+ * @return bool
+ */
+ public function isLocalHostname(string $hostname): bool {
+ // Disallow local network top-level domains from RFC 6762
+ $topLevelDomain = substr((strrchr($hostname, '.') ?: ''), 1);
+ if (in_array($topLevelDomain, self::LOCAL_TOPLEVEL_DOMAINS)) {
+ return true;
+ }
+
+ // Disallow hostname only
+ if (substr_count($hostname, '.') === 0 && !filter_var($hostname, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/Net/IpAddressClassifier.php b/lib/private/Net/IpAddressClassifier.php
new file mode 100644
index 00000000000..d4698864ec8
--- /dev/null
+++ b/lib/private/Net/IpAddressClassifier.php
@@ -0,0 +1,81 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Net;
+
+use IPLib\Address\IPv6;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+use Symfony\Component\HttpFoundation\IpUtils;
+use function filter_var;
+
+/**
+ * Classifier for IP addresses
+ *
+ * @internal
+ */
+class IpAddressClassifier {
+ private const LOCAL_ADDRESS_RANGES = [
+ '100.64.0.0/10', // See RFC 6598
+ '192.0.0.0/24', // See RFC 6890
+ ];
+
+ /**
+ * Check host identifier for local IPv4 and IPv6 address ranges
+ *
+ * Hostnames are not considered local. Use the HostnameClassifier for those.
+ *
+ * @param string $ip
+ *
+ * @return bool
+ */
+ public function isLocalAddress(string $ip): bool {
+ $parsedIp = Factory::parseAddressString(
+ $ip,
+ ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
+ );
+ if ($parsedIp === null) {
+ /* Not an IP */
+ return false;
+ }
+ /* Replace by normalized form */
+ if ($parsedIp instanceof IPv6) {
+ $ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
+ } else {
+ $ip = (string)$parsedIp;
+ }
+
+ if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ /* Range address */
+ return true;
+ }
+ if (IpUtils::checkIp($ip, self::LOCAL_ADDRESS_RANGES)) {
+ /* Within local range */
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php
new file mode 100644
index 00000000000..e48bd862472
--- /dev/null
+++ b/lib/private/Security/RemoteHostValidator.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Security;
+
+use OC\Net\HostnameClassifier;
+use OC\Net\IpAddressClassifier;
+use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
+use Psr\Log\LoggerInterface;
+use function strpos;
+use function strtolower;
+use function substr;
+use function urldecode;
+
+/**
+ * @internal
+ */
+final class RemoteHostValidator implements IRemoteHostValidator {
+ private IConfig $config;
+ private HostnameClassifier $hostnameClassifier;
+ private IpAddressClassifier $ipAddressClassifier;
+ private LoggerInterface $logger;
+
+ public function __construct(IConfig $config,
+ HostnameClassifier $hostnameClassifier,
+ IpAddressClassifier $ipAddressClassifier,
+ LoggerInterface $logger) {
+ $this->config = $config;
+ $this->hostnameClassifier = $hostnameClassifier;
+ $this->ipAddressClassifier = $ipAddressClassifier;
+ $this->logger = $logger;
+ }
+
+ public function isValid(string $host): bool {
+ if ($this->config->getSystemValueBool('allow_local_remote_servers', false)) {
+ return true;
+ }
+
+ $host = idn_to_utf8(strtolower(urldecode($host)));
+ // Remove brackets from IPv6 addresses
+ if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
+ $host = substr($host, 1, -1);
+ }
+
+ if ($this->hostnameClassifier->isLocalHostname($host)
+ || $this->ipAddressClassifier->isLocalAddress($host)) {
+ $this->logger->warning("Host $host was not connected to because it violates local access rules");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 1da1b614b5b..03aa75060df 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -105,8 +105,6 @@ use OC\Files\Type\Loader;
use OC\Files\View;
use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
-use OC\Http\Client\DnsPinMiddleware;
-use OC\Http\Client\LocalAddressChecker;
use OC\Http\Client\NegativeDnsCache;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator;
@@ -858,7 +856,7 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
/** @deprecated 19.0.0 */
$this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class);
-
+ $this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
$this->registerAlias(IVerificationToken::class, VerificationToken::class);
$this->registerAlias(ICrypto::class, Crypto::class);
@@ -890,22 +888,11 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(ICertificateManager::class, CertificateManager::class);
$this->registerAlias(IClientService::class, ClientService::class);
- $this->registerService(LocalAddressChecker::class, function (ContainerInterface $c) {
- return new LocalAddressChecker(
- $c->get(LoggerInterface::class),
- );
- });
$this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
return new NegativeDnsCache(
$c->get(ICacheFactory::class),
);
});
- $this->registerService(DnsPinMiddleware::class, function (ContainerInterface $c) {
- return new DnsPinMiddleware(
- $c->get(NegativeDnsCache::class),
- $c->get(LocalAddressChecker::class)
- );
- });
$this->registerDeprecatedAlias('HttpClientService', IClientService::class);
$this->registerService(IEventLogger::class, function (ContainerInterface $c) {
return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
diff --git a/lib/public/Security/IRemoteHostValidator.php b/lib/public/Security/IRemoteHostValidator.php
new file mode 100644
index 00000000000..99f149aee04
--- /dev/null
+++ b/lib/public/Security/IRemoteHostValidator.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCP\Security;
+
+/**
+ * Validator for remote hosts
+ *
+ * @since 26.0.0
+ */
+interface IRemoteHostValidator {
+
+ /**
+ * Validate if a host may be connected to
+ *
+ * By default, Nextcloud does not connect to any local servers. That is neither
+ * localhost nor any host in the local network.
+ *
+ * Admins can overwrite this behavior with the global `allow_local_remote_servers`
+ * settings flag. If the flag is set to `true`, local hosts will be considered
+ * valid.
+ *
+ * @param string $host hostname of the remote server, IPv4 or IPv6 address
+ *
+ * @return bool
+ * @since 26.0.0
+ */
+ public function isValid(string $host): bool;
+}