Set a cap to maximum delay in preventing brute force attacks
This is necessary because PHP-FPM fails if sleep / usleep runs beyond the maximum execution time of php.ini, leading to whole vhosts falling over.
This commit is contained in:
parent
f477401114
commit
52aeedd31e
@ -14,9 +14,9 @@ final class MD_STD_SEC {
|
||||
const REFRESH_TIME_IP = 180; // Time until the comp. with the same IP is cleared. This should be lower than the user-level one, as people working together may be using a common IP.
|
||||
|
||||
const BRUTE_FORCE_DELAY_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.08;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.8;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 2;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.04;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.2;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 1.8;
|
||||
|
||||
/**
|
||||
* Function for retrieving the anti-csrf token or generating it if need be.
|
||||
@ -56,14 +56,51 @@ final class MD_STD_SEC {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent brute force attacks by delaying the login .
|
||||
* Computes the delay for preventing brute force attacks.
|
||||
*
|
||||
* @param string $tool_name Identifier of the login.
|
||||
* @param string $username Username to look for.
|
||||
* @param integer $counter_common General counter.
|
||||
* @param integer $counter_by_user Username-specific counter.
|
||||
* @param integer $counter_by_ip IP-specific counter.
|
||||
* @param integer $max Optional script-specific maximum execution time.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function computeAntiBruteForceDelay(int $counter_common, int $counter_by_user, int $counter_by_ip, int $max = 0):int {
|
||||
|
||||
$counter_common = min($counter_common, 40);
|
||||
$counter_by_user = min($counter_by_user, 40);
|
||||
$counter_by_ip = min($counter_by_ip, 40);
|
||||
|
||||
// Calculate delay
|
||||
$delay_micoseconds = \abs(\intval(self::BRUTE_FORCE_DELAY_DEFAULT *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_COMMON ** $counter_common) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER ** $counter_by_user) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP ** $counter_by_ip)));
|
||||
|
||||
$max_execution_time = \abs((int)\ini_get("max_execution_time"));
|
||||
if ($max_execution_time !== 0) {
|
||||
$max_execution_microseconds = $max_execution_time * 1000000;
|
||||
$delay_micoseconds = min($delay_micoseconds, \abs($max_execution_microseconds - 1000000));
|
||||
}
|
||||
|
||||
if ($max !== 0 && $max * 1000000 < $delay_micoseconds) {
|
||||
$delay_micoseconds = max($max - 1, 1) * 1000000;
|
||||
}
|
||||
|
||||
return rand($delay_micoseconds - 100000, $delay_micoseconds);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent brute force attacks by delaying the login.
|
||||
*
|
||||
* @param string $tool_name Identifier of the login.
|
||||
* @param string $username Username to look for.
|
||||
* @param integer $max_execution_time Optional maximum execution time to stay below.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function preventBruteForce(string $tool_name, string $username):bool {
|
||||
public static function preventBruteForce(string $tool_name, string $username, int $max_execution_time = 0):bool {
|
||||
|
||||
// Unstable but working way to get the user's IP. If the IP is falsified,
|
||||
// this can't be found out anyway and security is established by _common.
|
||||
@ -112,16 +149,13 @@ final class MD_STD_SEC {
|
||||
$delay_multiplier_per_user = $loginLog['usr'][$hash_user]['count'];
|
||||
$delay_multiplier_per_ip = $loginLog['ip'][$hash_ip]['count'];
|
||||
|
||||
// Calculate delay
|
||||
$delay_micoseconds = \intval(self::BRUTE_FORCE_DELAY_DEFAULT *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_COMMON ** $delay_multiplier_common) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER ** $delay_multiplier_per_user) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP ** $delay_multiplier_per_ip));
|
||||
|
||||
$max_execution_microseconds = \abs((int)\ini_get("max_execution_time")) * 1000000;
|
||||
$delay_micoseconds = self::computeAntiBruteForceDelay($delay_multiplier_common,
|
||||
$delay_multiplier_per_user,
|
||||
$delay_multiplier_per_ip,
|
||||
$max_execution_time);
|
||||
|
||||
// Sleep
|
||||
\usleep(min($delay_micoseconds, \abs($max_execution_microseconds - 1000000)));
|
||||
\usleep($delay_micoseconds);
|
||||
|
||||
if ($delay_micoseconds > \abs($max_execution_microseconds - 1000000)) {
|
||||
return false;
|
||||
|
37
tests/MD_STD_SECTest.php
Normal file
37
tests/MD_STD_SECTest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Tests for MD_STD_SEC.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require __DIR__ . '/../src/MD_STD_SEC.php';
|
||||
|
||||
/**
|
||||
* Tests for MD_STD_SEC.
|
||||
*/
|
||||
final class MD_STD_SECTest extends TestCase {
|
||||
/**
|
||||
* Function for testing if the page can be opened using invalid values for objektnum.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
* @group MissingInputs
|
||||
* @group SafeForProduction
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testComputeAntiBruteForceDelayDoesNotGoOverMax():void {
|
||||
|
||||
$delay = MD_STD_SEC::computeAntiBruteForceDelay(100, 100, 100);
|
||||
self::assertGreaterThan(0, $delay);
|
||||
# self::assertLessThan(10 * 1000000, $delay); // Smaller than 10 seconds
|
||||
|
||||
$delay_reduced = MD_STD_SEC::computeAntiBruteForceDelay(100, 100, 100, 3);
|
||||
self::assertGreaterThan(0, $delay_reduced);
|
||||
self::assertLessThan(3 * 1000000, $delay_reduced); // Smaller than 10 seconds
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user