diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | lib/private/Http/Client/Client.php | 65 | ||||
-rw-r--r-- | lib/private/Http/Client/ClientService.php | 11 | ||||
-rw-r--r-- | lib/private/Server.php | 1 | ||||
-rw-r--r-- | lib/public/Http/Client/LocalServerException.php | 29 |
6 files changed, 98 insertions, 10 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 3b709fe8e98..01dcecb0e47 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -323,6 +323,7 @@ return array( 'OCP\\Http\\Client\\IClient' => $baseDir . '/lib/public/Http/Client/IClient.php', 'OCP\\Http\\Client\\IClientService' => $baseDir . '/lib/public/Http/Client/IClientService.php', 'OCP\\Http\\Client\\IResponse' => $baseDir . '/lib/public/Http/Client/IResponse.php', + 'OCP\\Http\\Client\\LocalServerException' => $baseDir . '/lib/public/Http/Client/LocalServerException.php', 'OCP\\IAddressBook' => $baseDir . '/lib/public/IAddressBook.php', 'OCP\\IAppConfig' => $baseDir . '/lib/public/IAppConfig.php', 'OCP\\IAvatar' => $baseDir . '/lib/public/IAvatar.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 1c3d23011c6..7a9bd5652d6 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -352,6 +352,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Http\\Client\\IClient' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IClient.php', 'OCP\\Http\\Client\\IClientService' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IClientService.php', 'OCP\\Http\\Client\\IResponse' => __DIR__ . '/../../..' . '/lib/public/Http/Client/IResponse.php', + 'OCP\\Http\\Client\\LocalServerException' => __DIR__ . '/../../..' . '/lib/public/Http/Client/LocalServerException.php', 'OCP\\IAddressBook' => __DIR__ . '/../../..' . '/lib/public/IAddressBook.php', 'OCP\\IAppConfig' => __DIR__ . '/../../..' . '/lib/public/IAppConfig.php', 'OCP\\IAvatar' => __DIR__ . '/../../..' . '/lib/public/IAvatar.php', diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 19d5877f9fb..d21ce413f1a 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -34,8 +34,10 @@ 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\ILogger; /** * Class Client @@ -47,20 +49,19 @@ class Client implements IClient { private $client; /** @var IConfig */ private $config; + /** @var ILogger */ + private $logger; /** @var ICertificateManager */ private $certificateManager; - /** - * @param IConfig $config - * @param ICertificateManager $certificateManager - * @param GuzzleClient $client - */ public function __construct( IConfig $config, + ILogger $logger, ICertificateManager $certificateManager, GuzzleClient $client ) { $this->config = $config; + $this->logger = $logger; $this->client = $client; $this->certificateManager = $certificateManager; } @@ -144,6 +145,53 @@ class Client implements IClient { return $proxy; } + protected function preventLocalAddress(string $uri, array $options): void { + if (($options['nextcloud']['allow_local_address'] ?? false) || + $this->config->getSystemValueBool('allow_local_remote_servers', false)) { + return; + } + + $host = parse_url($uri, PHP_URL_HOST); + if ($host === false) { + $this->logger->warning("Could not detect any host in $uri"); + throw new LocalServerException('Could not detect any host'); + } + + $host = strtolower($host); + // remove brackets from IPv6 addresses + if (strpos($host, '[') === 0 && substr($host, -1) === ']') { + $host = substr($host, 1, -1); + } + + // Disallow localhost and local network + if ($host === 'localhost' || substr($host, -6) === '.local' || substr($host, -10) === '.localhost') { + $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) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + + if ((bool)filter_var($host, FILTER_VALIDATE_IP) && !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + + // Also check for IPv6 IPv4 nesting, because that's not covered by filter_var + if ((bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && substr_count($host, '.') > 0) { + $delimiter = strrpos($host, ':'); // Get last colon + $ipv4Address = substr($host, $delimiter + 1); + + if (!filter_var($ipv4Address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $this->logger->warning("Host $host was not connected to because it violates local access rules"); + throw new LocalServerException('Host violates local access rules'); + } + } + } + /** * Sends a GET request * @@ -174,6 +222,7 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function get(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); $response = $this->client->request('get', $uri, $this->buildRequestOptions($options)); $isStream = isset($options['stream']) && $options['stream']; return new Response($response, $isStream); @@ -204,6 +253,7 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function head(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); $response = $this->client->request('head', $uri, $this->buildRequestOptions($options)); return new Response($response); } @@ -238,6 +288,8 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function post(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); + if (isset($options['body']) && is_array($options['body'])) { $options['form_params'] = $options['body']; unset($options['body']); @@ -276,6 +328,7 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function put(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); $response = $this->client->request('put', $uri, $this->buildRequestOptions($options)); return new Response($response); } @@ -310,6 +363,7 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function delete(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options)); return new Response($response); } @@ -344,6 +398,7 @@ class Client implements IClient { * @throws \Exception If the request could not get completed */ public function options(string $uri, array $options = []): IResponse { + $this->preventLocalAddress($uri, $options); $response = $this->client->request('options', $uri, $this->buildRequestOptions($options)); return new Response($response); } diff --git a/lib/private/Http/Client/ClientService.php b/lib/private/Http/Client/ClientService.php index 2b18daaf737..55f03f30399 100644 --- a/lib/private/Http/Client/ClientService.php +++ b/lib/private/Http/Client/ClientService.php @@ -32,6 +32,7 @@ use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\ICertificateManager; use OCP\IConfig; +use OCP\ILogger; /** * Class ClientService @@ -41,16 +42,16 @@ use OCP\IConfig; class ClientService implements IClientService { /** @var IConfig */ private $config; + /** @var ILogger */ + private $logger; /** @var ICertificateManager */ private $certificateManager; - /** - * @param IConfig $config - * @param ICertificateManager $certificateManager - */ public function __construct(IConfig $config, + ILogger $logger, ICertificateManager $certificateManager) { $this->config = $config; + $this->logger = $logger; $this->certificateManager = $certificateManager; } @@ -58,6 +59,6 @@ class ClientService implements IClientService { * @return Client */ public function newClient(): IClient { - return new Client($this->config, $this->certificateManager, new GuzzleClient()); + return new Client($this->config, $this->logger, $this->certificateManager, new GuzzleClient()); } } diff --git a/lib/private/Server.php b/lib/private/Server.php index 1a3eabc852e..a7432342a27 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -804,6 +804,7 @@ class Server extends ServerContainer implements IServerContainer { $uid = $user ? $user : null; return new ClientService( $c->getConfig(), + $c->getLogger(), new \OC\Security\CertificateManager( $uid, new View(), diff --git a/lib/public/Http/Client/LocalServerException.php b/lib/public/Http/Client/LocalServerException.php new file mode 100644 index 00000000000..22e533bf197 --- /dev/null +++ b/lib/public/Http/Client/LocalServerException.php @@ -0,0 +1,29 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com> + * + * @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\Http\Client; + +/** + * @since 19.0.0 + */ +class LocalServerException extends \RuntimeException { +} |