2020-11-08 19:34:57 +01:00
< ? PHP
/**
* Gathers wrappers for handling basic security operations .
*/
declare ( strict_types = 1 );
/**
2021-03-09 20:09:11 +01:00
* Gathers wrappers for handling basic security operations .
2020-11-08 19:34:57 +01:00
*/
final class MD_STD_SEC {
2021-09-17 15:52:37 +02:00
2021-11-25 01:09:08 +01:00
const REFRESH_TIME_GENERAL = 60 ; // Time until the comp. with the whole service is cleared.
const REFRESH_TIME_USER = 600 ; // Time until the comp. with the same username service is cleared.
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.
2021-09-17 15:52:37 +02:00
const BRUTE_FORCE_DELAY_DEFAULT = 2000 ; // 2000 microseconds = 2 milliseconds
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.08 ;
2021-11-25 01:09:08 +01:00
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.8 ;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 2 ;
2021-09-17 15:52:37 +02:00
2020-11-08 19:34:57 +01:00
/**
* Function for retrieving the anti - csrf token or generating it if need be .
*
* @ return string
*/
public static function getAntiCsrfToken () : string {
if ( empty ( $_SESSION [ 'csrf-token' ])) {
$_SESSION [ 'csrf-token' ] = bin2hex ( random_bytes ( 32 ));
}
return $_SESSION [ 'csrf-token' ];
}
/**
* Function for validating anti - csrf tokens . Each anti - csrf token is removed
* after use .
*
* @ return boolean
*/
public static function validateAntiCsrfToken () : bool {
$validity = false ;
if ( ! empty ( $_POST [ 'csrf-token' ])
&& ! empty ( $_SESSION [ 'csrf-token' ])
&& hash_equals ( $_SESSION [ 'csrf-token' ], $_POST [ 'csrf-token' ]) === true
) {
$validity = true ;
}
2021-02-06 17:35:11 +01:00
$_SESSION [ 'csrf-token' ] = null ;
unset ( $_SESSION [ 'csrf-token' ]);
2020-11-08 19:34:57 +01:00
return $validity ;
}
2020-11-17 23:55:50 +01:00
/**
* 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.
2021-11-25 01:09:08 +01:00
$ip = \filter_var ( $_SERVER [ 'REMOTE_ADDR' ] ? : ( $_SERVER [ 'HTTP_X_FORWARDED_FOR' ] ? : $_SERVER [ 'HTTP_CLIENT_IP' ]), \FILTER_VALIDATE_IP ) ? : " Failed to find " ;
2020-11-17 23:55:50 +01:00
// Set name of log file
$logfile_common = \sys_get_temp_dir () . " /logins_ { $tool_name } .json " ;
// Ensure the log files exist
2021-02-06 20:08:37 +01:00
if ( ! \file_exists ( $logfile_common )) {
\file_put_contents ( $logfile_common , " [] " );
}
2020-11-17 23:55:50 +01:00
// 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
2021-11-25 01:09:08 +01:00
if ( empty ( $loginLog [ 'common' ]) || \time () - $loginLog [ 'common' ][ 'time' ] > self :: REFRESH_TIME_GENERAL ) {
2020-11-17 23:55:50 +01:00
$loginLog [ 'common' ] = [ " count " => 0 , " time " => \time ()];
}
2021-11-25 01:09:08 +01:00
if ( empty ( $loginLog [ 'usr' ][ $hash_user ]) || \time () - $loginLog [ 'usr' ][ $hash_user ][ 'time' ] > self :: REFRESH_TIME_USER ) {
2020-11-17 23:55:50 +01:00
$loginLog [ 'usr' ][ $hash_user ] = [ " count " => 0 , " time " => \time ()];
}
2021-11-25 01:09:08 +01:00
if ( empty ( $loginLog [ 'ip' ][ $hash_ip ]) || \time () - $loginLog [ 'ip' ][ $hash_ip ][ 'time' ] > self :: REFRESH_TIME_IP ) {
2020-11-17 23:55:50 +01:00
$loginLog [ 'ip' ][ $hash_ip ] = [ " count " => 0 , " time " => \time ()];
}
// Increase counters and update timers
2021-04-11 21:20:44 +02:00
++ $loginLog [ 'common' ][ 'count' ];
2020-11-17 23:55:50 +01:00
$loginLog [ 'common' ][ 'time' ] = \time ();
2021-04-11 21:20:44 +02:00
++ $loginLog [ 'usr' ][ $hash_user ][ 'count' ];
2020-11-17 23:55:50 +01:00
$loginLog [ 'usr' ][ $hash_user ][ 'time' ] = \time ();
2021-04-11 21:20:44 +02:00
++ $loginLog [ 'ip' ][ $hash_ip ][ 'count' ];
2020-11-17 23:55:50 +01:00
$loginLog [ 'ip' ][ $hash_ip ][ 'time' ] = \time ();
// Update the log file
2021-07-20 18:25:34 +02:00
\file_put_contents ( $logfile_common , MD_STD :: json_encode ( $loginLog ));
2020-11-17 23:55:50 +01:00
// Translate counters into delay multipliers
$delay_multiplier_common = $loginLog [ 'common' ][ 'count' ];
$delay_multiplier_per_user = $loginLog [ 'usr' ][ $hash_user ][ 'count' ];
2020-11-22 14:18:08 +01:00
$delay_multiplier_per_ip = $loginLog [ 'ip' ][ $hash_ip ][ 'count' ];
2020-11-17 23:55:50 +01:00
// 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 ;
}
2020-11-22 17:45:07 +01:00
/**
* Send CSP headers .
*
2020-11-22 23:27:54 +01:00
* @ param array { default - src : string , connect - src : string , script - src : string , img - src : string , media - src : string , style - src : string , frame - src : string , object - src : string , base - uri : string , form - action : string , frame - ancestors ? : string } $directives Directives to send . Font source is always set to 'self' , and hence excluded .
* @ param string $frame_ancestors Frame ancestors directive . Default is to not set it .
2020-11-22 17:45:07 +01:00
*
* @ return void
*/
2020-11-22 23:27:54 +01:00
public static function sendContentSecurityPolicy ( array $directives , string $frame_ancestors = " " ) : void {
2020-11-22 17:45:07 +01:00
2021-05-15 17:17:53 +02:00
$policy = 'Content-Security-Policy: default-src ' . $directives [ 'default-src' ] . '; connect-src ' . $directives [ 'connect-src' ] . '; script-src ' . $directives [ 'script-src' ] . '; img-src ' . $directives [ 'img-src' ] . '; media-src ' . $directives [ 'media-src' ] . '; style-src ' . $directives [ 'style-src' ] . '; font-src \'self\'; frame-src ' . $directives [ 'frame-src' ] . '; object-src ' . $directives [ 'object-src' ] . '; base-uri ' . $directives [ 'base-uri' ] . '; form-action ' . $directives [ 'form-action' ] . '; manifest-src \'self\'; worker-src \'self\';' ;
2020-11-22 23:27:54 +01:00
if ( ! empty ( $frame_ancestors )) {
$policy .= ' frame-ancestors ' . $frame_ancestors . ';' ;
}
header ( $policy );
2020-11-22 17:45:07 +01:00
}
2020-11-08 19:34:57 +01:00
}