aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Security/Bruteforce
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Security/Bruteforce')
-rw-r--r--lib/private/Security/Bruteforce/Backend/DatabaseBackend.php2
-rw-r--r--lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php4
-rw-r--r--lib/private/Security/Bruteforce/CleanupJob.php7
-rw-r--r--lib/private/Security/Bruteforce/Throttler.php118
4 files changed, 40 insertions, 91 deletions
diff --git a/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php b/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php
index 0e272d94d0d..33c2a3aae62 100644
--- a/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php
+++ b/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php
@@ -45,7 +45,7 @@ class DatabaseBackend implements IBackend {
$row = $result->fetch();
$result->closeCursor();
- return (int) $row['attempts'];
+ return (int)$row['attempts'];
}
/**
diff --git a/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php b/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php
index 32571c72fae..9a0723db47e 100644
--- a/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php
+++ b/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php
@@ -19,7 +19,7 @@ class MemoryCacheBackend implements IBackend {
ICacheFactory $cacheFactory,
private ITimeFactory $timeFactory,
) {
- $this->cache = $cacheFactory->createDistributed(__CLASS__);
+ $this->cache = $cacheFactory->createDistributed(self::class);
}
private function hash(
@@ -137,7 +137,7 @@ class MemoryCacheBackend implements IBackend {
$existingAttempts = array_values($existingAttempts);
// Store the new attempt
- $existingAttempts[] = $timestamp . '#' . $this->hash($action) . '#' . $this->hash($metadata);
+ $existingAttempts[] = $timestamp . '#' . $this->hash($action) . '#' . $this->hash($metadata);
$this->cache->set($identifier, json_encode($existingAttempts), 12 * 3600);
}
diff --git a/lib/private/Security/Bruteforce/CleanupJob.php b/lib/private/Security/Bruteforce/CleanupJob.php
index e7d981caa08..f07e4dbacbd 100644
--- a/lib/private/Security/Bruteforce/CleanupJob.php
+++ b/lib/private/Security/Bruteforce/CleanupJob.php
@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace OC\Security\Bruteforce;
use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -22,8 +21,8 @@ class CleanupJob extends TimedJob {
parent::__construct($time);
// Run once a day
- $this->setInterval(3600 * 24);
- $this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
+ $this->setInterval(60 * 60 * 24);
+ $this->setTimeSensitivity(self::TIME_INSENSITIVE);
}
protected function run($argument): void {
@@ -33,6 +32,6 @@ class CleanupJob extends TimedJob {
$qb = $this->connection->getQueryBuilder();
$qb->delete('bruteforce_attempts')
->where($qb->expr()->lt('occurred', $qb->createNamedParameter($time), IQueryBuilder::PARAM_INT));
- $qb->execute();
+ $qb->executeStatement();
}
}
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
index 7e310035ce4..574f6c80c3f 100644
--- a/lib/private/Security/Bruteforce/Throttler.php
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace OC\Security\Bruteforce;
use OC\Security\Bruteforce\Backend\IBackend;
+use OC\Security\Ip\BruteforceAllowList;
use OC\Security\Normalizer\IpAddress;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
@@ -32,14 +33,13 @@ use Psr\Log\LoggerInterface;
class Throttler implements IThrottler {
/** @var bool[] */
private array $hasAttemptsDeleted = [];
- /** @var bool[] */
- private array $ipIsWhitelisted = [];
public function __construct(
private ITimeFactory $timeFactory,
private LoggerInterface $logger,
private IConfig $config,
private IBackend $backend,
+ private BruteforceAllowList $allowList,
) {
}
@@ -83,70 +83,7 @@ class Throttler implements IThrottler {
* Check if the IP is whitelisted
*/
public function isBypassListed(string $ip): bool {
- if (isset($this->ipIsWhitelisted[$ip])) {
- return $this->ipIsWhitelisted[$ip];
- }
-
- if (!$this->config->getSystemValueBool('auth.bruteforce.protection.enabled', true)) {
- $this->ipIsWhitelisted[$ip] = true;
- return true;
- }
-
- $keys = $this->config->getAppKeys('bruteForce');
- $keys = array_filter($keys, function ($key) {
- return str_starts_with($key, 'whitelist_');
- });
-
- if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
- $type = 4;
- } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
- $type = 6;
- } else {
- $this->ipIsWhitelisted[$ip] = false;
- return false;
- }
-
- $ip = inet_pton($ip);
-
- foreach ($keys as $key) {
- $cidr = $this->config->getAppValue('bruteForce', $key, null);
-
- $cx = explode('/', $cidr);
- $addr = $cx[0];
- $mask = (int)$cx[1];
-
- // Do not compare ipv4 to ipv6
- if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
- ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
- continue;
- }
-
- $addr = inet_pton($addr);
-
- $valid = true;
- for ($i = 0; $i < $mask; $i++) {
- $part = ord($addr[(int)($i / 8)]);
- $orig = ord($ip[(int)($i / 8)]);
-
- $bitmask = 1 << (7 - ($i % 8));
-
- $part = $part & $bitmask;
- $orig = $orig & $bitmask;
-
- if ($part !== $orig) {
- $valid = false;
- break;
- }
- }
-
- if ($valid === true) {
- $this->ipIsWhitelisted[$ip] = true;
- return true;
- }
- }
-
- $this->ipIsWhitelisted[$ip] = false;
- return false;
+ return $this->allowList->isBypassListed($ip);
}
/**
@@ -176,7 +113,7 @@ class Throttler implements IThrottler {
return 0;
}
- $maxAgeTimestamp = (int) ($this->timeFactory->getTime() - 3600 * $maxAgeHours);
+ $maxAgeTimestamp = (int)($this->timeFactory->getTime() - 3600 * $maxAgeHours);
return $this->backend->getAttempts(
$ipAddress->getSubnet(),
@@ -190,12 +127,19 @@ class Throttler implements IThrottler {
*/
public function getDelay(string $ip, string $action = ''): int {
$attempts = $this->getAttempts($ip, $action);
+ return $this->calculateDelay($attempts);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function calculateDelay(int $attempts): int {
if ($attempts === 0) {
return 0;
}
$firstDelay = 0.1;
- if ($attempts > self::MAX_ATTEMPTS) {
+ if ($attempts > $this->config->getSystemValueInt('auth.bruteforce.max-attempts', self::MAX_ATTEMPTS)) {
// Don't ever overflow. Just assume the maxDelay time:s
return self::MAX_DELAY_MS;
}
@@ -204,7 +148,7 @@ class Throttler implements IThrottler {
if ($delay > self::MAX_DELAY) {
return self::MAX_DELAY_MS;
}
- return (int) \ceil($delay * 1000);
+ return (int)\ceil($delay * 1000);
}
/**
@@ -262,25 +206,31 @@ class Throttler implements IThrottler {
* {@inheritDoc}
*/
public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int {
- $delay = $this->getDelay($ip, $action);
- if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) {
- $this->logger->info('IP address blocked because it reached the maximum failed attempts in the last 30 minutes [action: {action}, ip: {ip}]', [
- 'action' => $action,
- 'ip' => $ip,
- ]);
- // If the ip made too many attempts within the last 30 mins we don't execute anymore
- throw new MaxDelayReached('Reached maximum delay');
- }
- if ($delay > 100) {
- $this->logger->info('IP address throttled because it reached the attempts limit in the last 30 minutes [action: {action}, delay: {delay}, ip: {ip}]', [
+ $maxAttempts = $this->config->getSystemValueInt('auth.bruteforce.max-attempts', self::MAX_ATTEMPTS);
+ $attempts = $this->getAttempts($ip, $action);
+ if ($attempts > $maxAttempts) {
+ $attempts30mins = $this->getAttempts($ip, $action, 0.5);
+ if ($attempts30mins > $maxAttempts) {
+ $this->logger->info('IP address blocked because it reached the maximum failed attempts in the last 30 minutes [action: {action}, attempts: {attempts}, ip: {ip}]', [
+ 'action' => $action,
+ 'ip' => $ip,
+ 'attempts' => $attempts30mins,
+ ]);
+ // If the ip made too many attempts within the last 30 mins we don't execute anymore
+ throw new MaxDelayReached('Reached maximum delay');
+ }
+
+ $this->logger->info('IP address throttled because it reached the attempts limit in the last 12 hours [action: {action}, attempts: {attempts}, ip: {ip}]', [
'action' => $action,
'ip' => $ip,
- 'delay' => $delay,
+ 'attempts' => $attempts,
]);
}
- if (!$this->config->getSystemValueBool('auth.bruteforce.protection.testing')) {
- usleep($delay * 1000);
+
+ if ($attempts > 0) {
+ return $this->calculateDelay($attempts);
}
- return $delay;
+
+ return 0;
}
}