From c0f47af2d04f4417d8f9792649266d5dd7a60a59 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 1 Jul 2022 11:18:27 +0200 Subject: [PATCH] Add a public interface for the bruteforce throttler and register for injection Signed-off-by: Joas Schilling --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Security/Bruteforce/Throttler.php | 8 +- lib/private/Server.php | 2 + lib/public/Security/Bruteforce/IThrottler.php | 126 ++++++++++++++++++ 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 lib/public/Security/Bruteforce/IThrottler.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 420bc89c7359d..8853c1f17f48d 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -510,6 +510,7 @@ 'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php', 'OCP\\Search\\SearchResult' => $baseDir . '/lib/public/Search/SearchResult.php', 'OCP\\Search\\SearchResultEntry' => $baseDir . '/lib/public/Search/SearchResultEntry.php', + 'OCP\\Security\\Bruteforce\\IThrottler' => $baseDir . '/lib/public/Security/Bruteforce/IThrottler.php', 'OCP\\Security\\Bruteforce\\MaxDelayReached' => $baseDir . '/lib/public/Security/Bruteforce/MaxDelayReached.php', 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', 'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 9a371b457434f..5617430958d5d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -543,6 +543,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php', 'OCP\\Search\\SearchResult' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResult.php', 'OCP\\Search\\SearchResultEntry' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResultEntry.php', + 'OCP\\Security\\Bruteforce\\IThrottler' => __DIR__ . '/../../..' . '/lib/public/Security/Bruteforce/IThrottler.php', 'OCP\\Security\\Bruteforce\\MaxDelayReached' => __DIR__ . '/../../..' . '/lib/public/Security/Bruteforce/MaxDelayReached.php', 'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', 'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php', diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index e37746eb6a2d0..299cab93eb316 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -36,6 +36,7 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\IDBConnection; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\Bruteforce\MaxDelayReached; use Psr\Log\LoggerInterface; @@ -52,11 +53,8 @@ * * @package OC\Security\Bruteforce */ -class Throttler { +class Throttler implements IThrottler { public const LOGIN_ACTION = 'login'; - public const MAX_DELAY = 25; - public const MAX_DELAY_MS = 25000; // in milliseconds - public const MAX_ATTEMPTS = 10; /** @var IDBConnection */ private $db; @@ -311,7 +309,7 @@ public function resetDelay(string $ip, string $action, array $metadata): void { * * @param string $ip */ - public function resetDelayForIP($ip) { + public function resetDelayForIP(string $ip): void { $cutoffTime = $this->getCutoffTimestamp(); $qb = $this->db->getQueryBuilder(); diff --git a/lib/private/Server.php b/lib/private/Server.php index bcbb6ef6b00d3..842f72fa1d098 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -231,6 +231,7 @@ use OCP\Remote\IInstanceFactory; use OCP\RichObjectStrings\IValidator; use OCP\Route\IRouter; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\IContentSecurityPolicyManager; use OCP\Security\ICredentialsManager; use OCP\Security\ICrypto; @@ -1002,6 +1003,7 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Throttler', Throttler::class); + $this->registerAlias(IThrottler::class, Throttler::class); $this->registerService('IntegrityCodeChecker', function (ContainerInterface $c) { // IConfig and IAppManager requires a working database. This code // might however be called when ownCloud is not yet setup. diff --git a/lib/public/Security/Bruteforce/IThrottler.php b/lib/public/Security/Bruteforce/IThrottler.php new file mode 100644 index 0000000000000..6f492d6c59dce --- /dev/null +++ b/lib/public/Security/Bruteforce/IThrottler.php @@ -0,0 +1,126 @@ + + * + * @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 . + * + */ + +namespace OCP\Security\Bruteforce; + +/** + * Class Throttler implements the bruteforce protection for security actions in + * Nextcloud. + * + * It is working by logging invalid login attempts to the database and slowing + * down all login attempts from the same subnet. The max delay is 30 seconds and + * the starting delay are 200 milliseconds. (after the first failed login) + * + * This is based on Paragonie's AirBrake for Airship CMS. You can find the original + * code at https://github.com/paragonie/airship/blob/7e5bad7e3c0fbbf324c11f963fd1f80e59762606/src/Engine/Security/AirBrake.php + * + * @package OC\Security\Bruteforce + * @since 25.0.0 + */ +interface IThrottler { + /** + * @since 25.0.0 + */ + public const MAX_DELAY = 25; + + /** + * @since 25.0.0 + */ + public const MAX_DELAY_MS = 25000; // in milliseconds + + /** + * @since 25.0.0 + */ + public const MAX_ATTEMPTS = 10; + + /** + * Register a failed attempt to bruteforce a security control + * + * @param string $action + * @param string $ip + * @param array $metadata Optional metadata logged to the database + * @since 25.0.0 + */ + public function registerAttempt(string $action, string $ip, array $metadata = []): void; + + /** + * Get the throttling delay (in milliseconds) + * + * @param string $ip + * @param string $action optionally filter by action + * @param float $maxAgeHours + * @return int + * @since 25.0.0 + */ + public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int; + + /** + * Get the throttling delay (in milliseconds) + * + * @param string $ip + * @param string $action optionally filter by action + * @return int + * @since 25.0.0 + */ + public function getDelay(string $ip, string $action = ''): int; + + /** + * Reset the throttling delay for an IP address, action and metadata + * + * @param string $ip + * @param string $action + * @param array $metadata + * @since 25.0.0 + */ + public function resetDelay(string $ip, string $action, array $metadata): void; + + /** + * Reset the throttling delay for an IP address + * + * @param string $ip + * @since 25.0.0 + */ + public function resetDelayForIP(string $ip): void; + + /** + * Will sleep for the defined amount of time + * + * @param string $ip + * @param string $action optionally filter by action + * @return int the time spent sleeping + * @since 25.0.0 + */ + public function sleepDelay(string $ip, string $action = ''): int; + + /** + * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes + * In this case a "429 Too Many Request" exception is thrown + * + * @param string $ip + * @param string $action optionally filter by action + * @return int the time spent sleeping + * @throws MaxDelayReached when reached the maximum + * @since 25.0.0 + */ + public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int; +}