summaryrefslogtreecommitdiffstats
path: root/lib/private/Http
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/private/Http
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/private/Http')
-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
4 files changed, 27 insertions, 117 deletions
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);
- }
-}