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:
		| @@ -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 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_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds
 | ||||||
|     const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON   = 1.08; |     const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON   = 1.04; | ||||||
|     const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.8; |     const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.2; | ||||||
|     const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 2; |     const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 1.8; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Function for retrieving the anti-csrf token or generating it if need be. |      * 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 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  $tool_name          Identifier of the login. | ||||||
|      * @param string  $username           Username to look for. |      * @param string  $username           Username to look for. | ||||||
|  |      * @param integer $max_execution_time Optional maximum execution time to stay below. | ||||||
|      * |      * | ||||||
|      * @return boolean |      * @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,
 |         // 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.
 |         // 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_user = $loginLog['usr'][$hash_user]['count']; | ||||||
|         $delay_multiplier_per_ip = $loginLog['ip'][$hash_ip]['count']; |         $delay_multiplier_per_ip = $loginLog['ip'][$hash_ip]['count']; | ||||||
| 
 | 
 | ||||||
|         // Calculate delay
 |         $delay_micoseconds = self::computeAntiBruteForceDelay($delay_multiplier_common, | ||||||
|         $delay_micoseconds = \intval(self::BRUTE_FORCE_DELAY_DEFAULT * |             $delay_multiplier_per_user, | ||||||
|             (self::BRUTE_FORCE_DELAY_MULTIPLIER_COMMON ** $delay_multiplier_common) * |             $delay_multiplier_per_ip, | ||||||
|             (self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER ** $delay_multiplier_per_user) * |             $max_execution_time); | ||||||
|             (self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP ** $delay_multiplier_per_ip)); |  | ||||||
| 
 |  | ||||||
|         $max_execution_microseconds = \abs((int)\ini_get("max_execution_time")) * 1000000; |  | ||||||
| 
 | 
 | ||||||
|         // Sleep
 |         // Sleep
 | ||||||
|         \usleep(min($delay_micoseconds, \abs($max_execution_microseconds - 1000000))); |         \usleep($delay_micoseconds); | ||||||
| 
 | 
 | ||||||
|         if ($delay_micoseconds > \abs($max_execution_microseconds - 1000000)) { |         if ($delay_micoseconds > \abs($max_execution_microseconds - 1000000)) { | ||||||
|             return false; |             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
 | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user