diff --git a/MD_STD_SEC.php b/MD_STD_SEC.php index bc25d56..f141889 100644 --- a/MD_STD_SEC.php +++ b/MD_STD_SEC.php @@ -8,7 +8,6 @@ declare(strict_types = 1); * Class providing static functions with basic security operations. */ final class MD_STD_SEC { - /** * Function for retrieving the anti-csrf token or generating it if need be. * @@ -45,4 +44,82 @@ final class MD_STD_SEC { } + const BRUTE_FORCE_DELAY_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds + const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.2; + const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 3; + const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 8; + + /** + * Prevent brute force attacks by delaying the login . + * + * @param string $tool_name Identifier of the login. + * @param string $username Username to look for. + * + * @return boolean + */ + public static function preventBruteForce(string $tool_name, string $username):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. + $ip = \strval($_SERVER['REMOTE_ADDR'] ?: ($_SERVER['HTTP_X_FORWARDED_FOR'] ?: $_SERVER['HTTP_CLIENT_IP'])); + + // Set name of log file + $logfile_common = \sys_get_temp_dir() . "/logins_{$tool_name}.json"; + + // Ensure the log files exist + if (!\file_exists($logfile_common)) \file_put_contents($logfile_common, "[]"); + + // Hash entered username and IP to prevent malicious strings from + // entering the system. + $hash_user = \md5($username); + $hash_ip = \md5($ip); + + // Delete the log files if they are too old + $loginLog = \json_decode(MD_STD::file_get_contents($logfile_common), \true) ?: []; + + // Ensure the counters exist and aren't old than 600 seconds / 10 minutes + if (empty($loginLog['common']) || \time() - $loginLog['common']['time'] > 600) { + $loginLog['common'] = ["count" => 0, "time" => \time()]; + } + if (empty($loginLog['usr'][$hash_user]) || \time() - $loginLog['usr'][$hash_user]['time'] > 600) { + $loginLog['usr'][$hash_user] = ["count" => 0, "time" => \time()]; + } + if (empty($loginLog['ip'][$hash_ip]) || \time() - $loginLog['ip'][$hash_ip]['time'] > 600) { + $loginLog['ip'][$hash_ip] = ["count" => 0, "time" => \time()]; + } + + // Increase counters and update timers + $loginLog['common']['count']++; + $loginLog['common']['time'] = \time(); + $loginLog['usr'][$hash_user]['count']++; + $loginLog['usr'][$hash_user]['time'] = \time(); + $loginLog['ip'][$hash_ip]['count']++; + $loginLog['ip'][$hash_ip]['time'] = \time(); + + // Update the log file + \file_put_contents($logfile_common, \json_encode($loginLog)); + + // Translate counters into delay multipliers + $delay_multiplier_common = $loginLog['common']['count']; + $delay_multiplier_per_user = $loginLog['usr'][$hash_user]['count']; + $delay_multiplier_per_ip = $loginLog['usr'][$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; + + // Sleep + \usleep(min($delay_micoseconds, \abs($max_execution_microseconds - 1000000))); + + if ($delay_micoseconds > \abs($max_execution_microseconds - 1000000)) { + return false; + } + + return true; + + } }