Compare commits

...

6 Commits

Author SHA1 Message Date
23232f4e6a Remove superfluous variable assignments 2022-09-15 21:38:41 +02:00
c38f0146dc Allow passing existing redis connections to MD_STD_CACHE 2022-09-04 23:27:04 +02:00
bbac217aa0 Reduce factors for setting anti-brute force delays 2022-08-14 18:12:33 +02:00
ced5a65122 Fix variable misnomers in MD_STD_SEC 2022-08-14 16:17:03 +02:00
52aeedd31e 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.
2022-08-14 13:08:40 +02:00
f477401114 Add functions for validating longitudes and latitutdes 2022-07-28 11:01:30 +02:00
9 changed files with 171 additions and 33 deletions

View File

@ -0,0 +1,19 @@
<?PHP
declare(strict_types = 1);
/**
* Custom exception class for coordinates (longitude, latitude) that are out
* of range (-90 - 90 for latitudes, -180 to 180 for longitudes).
*/
final class MDCoordinateOutOfRange extends MDExpectedException {
/**
* Error message.
*
* @return string
*/
public function errorMessage() {
//error message
return 'Parameter: <b>' . $this->getMessage() . '</b> is not a valid, numeric values.';
}
}

View File

@ -12,8 +12,7 @@ final class MDJailSecurityOptionNotSetException extends Exception {
*/
public function errorMessage() {
//error message
$errorMsg = 'A security option of MD_JAIL has not been set: <b>' . $this->getMessage() . '</b>).';
return $errorMsg;
return 'A security option of MD_JAIL has not been set: <b>' . $this->getMessage() . '</b>).';
}
}

View File

@ -12,8 +12,7 @@ final class MDJsonEncodingFailedException extends Exception {
*/
public function errorMessage() {
//error message
$errorMsg = 'Failed to encode JSON data.';
return $errorMsg;
return 'Failed to encode JSON data.';
}
}

View File

@ -132,12 +132,10 @@ final class MDFormatter {
*/
public static function formatMarkdownCodeBlock(string $content, string $language = ''):string {
$output = '```' . $language . '
return '```' . $language . '
' . $content . '
```' . PHP_EOL;
return $output;
}
/**

View File

@ -386,11 +386,12 @@ final class MD_STD {
} while($running > 0);
$res = [];
foreach($urls as $i => $url) {
$keys = \array_keys($urls);
foreach($keys as $i){
$res[$i] = \curl_multi_getcontent($curl_array[$i]) ?: '';
}
foreach($urls as $i => $url){
foreach($keys as $i){
\curl_multi_remove_handle($mh, $curl_array[$i]);
}
\curl_multi_close($mh);
@ -579,8 +580,7 @@ final class MD_STD {
*/
public static function openssl_random_pseudo_bytes(int $length):string {
$output = \openssl_random_pseudo_bytes($length);
return $output;
return \openssl_random_pseudo_bytes($length);
}
@ -750,8 +750,7 @@ final class MD_STD {
*/
public static function string_to_color_code(string $str):string {
$code = \substr(\dechex(\crc32($str)), 0, 6);
return $code;
return \substr(\dechex(\crc32($str)), 0, 6);
}

View File

@ -55,18 +55,25 @@ final class MD_STD_CACHE {
* Caches and serves a page through redis. Should be called at the start
* of the script generating a page.
*
* @param string $redisKey Key to cache by in redis.
* @param integer $expiry Expiration time in seconds.
* @param string $redisKey Key to cache by in redis.
* @param integer $expiry Expiration time in seconds.
* @param Redis|null $redis Redis connection already opened, if one exists.
* If this parameter is not provided, a separate
* redis connection is opened for this function.
*
* @return string
*/
public static function serve_page_through_redis_cache(string $redisKey, int $expiry = 3600):string {
public static function serve_page_through_redis_cache(string $redisKey, int $expiry = 3600, ?Redis $redis = null):string {
if (PHP_SAPI === 'cli') {
return '';
}
$redis = self::open_redis_default();
if ($redis === null) {
$redis = self::open_redis_default();
$closeRedis = true;
}
else $closeRedis = false;
if ($redis->ping() !== false) {
@ -94,7 +101,10 @@ final class MD_STD_CACHE {
}
}
$redis->close();
if ($closeRedis === true) {
$redis->close();
}
return '';

View File

@ -295,6 +295,46 @@ final class MD_STD_IN {
}
/**
* Validates a coordinate.
*
* @param string|integer $input Input string.
*
* @return float
*/
public static function validate_longitude(string|int $input):float {
if (is_string($input)) $output = self::sanitize_float($input);
else $output = $input;
if ($output < -180 || $output > 180) {
throw new MDCoordinateOutOfRange("Longitude out of range");
}
return $output;
}
/**
* Validates a coordinate.
*
* @param string|integer $input Input string.
*
* @return float
*/
public static function validate_latitude(string|int $input):float {
if (is_string($input)) $output = self::sanitize_float($input);
else $output = $input;
if ($output < -90 || $output > 90) {
throw new MDCoordinateOutOfRange("Latitude out of range");
}
return $output;
}
/**
* Validates ISBNs. Empty strings are accepted as well.
*

View File

@ -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.0;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 1.6;
/**
* Function for retrieving the anti-csrf token or generating it if need be.
@ -56,14 +56,52 @@ 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 max(0, 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.
* In seconds.
*
* @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,18 +150,17 @@ 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));
$delay_micoseconds = self::computeAntiBruteForceDelay($delay_multiplier_common,
$delay_multiplier_per_user,
$delay_multiplier_per_ip,
$max_execution_time);
$max_execution_microseconds = \abs((int)\ini_get("max_execution_time")) * 1000000;
\error_log("Logged in: " . $username . "; Delay: " . ($delay_micoseconds / 1000000) . " seconds");
// 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_time - 1000000)) {
return false;
}

37
tests/MD_STD_SECTest.php Normal file
View 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
}
}