PHP 8.4+ Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
CodeHydrater/src/classes/security.php

264 lines
8.7 KiB

<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class security {
use traits\security\csrf_token_functions;
use traits\security\session_hijacking_functions;
/**
* Get unique IDs for database
* @return int
*/
public static function get_unique_number(): int {
return abs(crc32(microtime()));
}
/**
* Get token
* @return string
*/
public static function get_unique_id(): string {
$more_entropy = true;
$prefix = ""; // Blank is a rand string
return md5(uniqid($prefix, $more_entropy));
}
public static function use_hmac(string $algo, string $pepper) {
if (!function_exists("hash_hmac_algos")) {
throw new \Exception("hash_hmac not installed!");
}
if (strpos($algo, "md") !== false) {
throw new \Exception("MD is too weak!");
}
if (strpos($algo, "sha1") !== false) {
throw new \Exception("sha1 is too weak!");
}
$allowed = hash_hmac_algos();
if (in_array(strtolower($algo), $allowed)) {
return hash_hmac($algo, $pepper);
}
throw new \Exception("hmac algo not found!");
}
/*
* Consider MD5 and SHA1 which are fast and efficient,
* making them ideal for check summing and file verification.
* However, their speed makes them unsuitable for hashing a
* user’s password. With today’s computational power of
* modern CPUs/GPUs and cloud computing, a hashed password
* can be cracked by brute force in a matter of minutes.
* It’s fairly trivial to quickly generate billions of MD5
* hashes from random words until a match is found, thereby
* revealing the original plain-text password. Instead,
* intentionally slower hashing algorithms such as bcrypt
* or Argon2 should be used.
*/
public static function find_default_hash_algo() {
if (defined("PASSWORD_ARGON2ID"))
return PASSWORD_ARGON2ID;
if (defined("PASSWORD_ARGON2"))
return PASSWORD_ARGON2;
if (defined("PASSWORD_DEFAULT"))
return PASSWORD_DEFAULT;
if (defined("PASSWORD_BCRYPT"))
return PASSWORD_BCRYPT;
return false;
}
/*
* The password_hash() function not only uses a secure
* one-way hashing algorithm, but it automatically handles
* salt and prevents time based side-channel attacks.
*/
public static function do_password_hash(string $password): bool | string {
$pwd_peppered = self::make_hash($password);
$hash_algo = \main_tts\configure::get(
"security",
"hash_algo"
);
if (! empty($hash_algo)) {
return password_hash($pwd_peppered, $hash_algo);
}
return password_hash($pwd_peppered, self::find_default_hash_algo());
}
public static function do_password_verify(
string $input_pwd, $db_password
): bool {
$pwd_peppered = self::make_hash($input_pwd);
return password_verify($pwd_peppered, $db_password);
}
/**
* Make a secure Hash
* @param string $text to encode
* @param string $level (weak, low, high, max)
* @return string new Hashed
*/
public static function make_hash(string $text): string {
$level = bootstrap\configure::get('security', 'hash_level');
if (empty($level)) {
$level = "normal";
}
$pepper = bootstrap\configure::get('security', 'pepper_pwd');
if (strlen($pepper) < 12) {
throw new \Exception("Pepper Password, too short!");
}
$salt = bootstrap\configure::get('security', 'salt_pwd');
if (strlen($salt) < 5) {
throw new \Exception("Salt Password, too short!");
}
switch (strtolower($level)) {
case 'max':
// Prefer computing using HMAC
if (function_exists("hash_hmac")) {
return hash_hmac("sha512", $text, $pepper);
}
// Sha512 hash is the next best thing
if (function_exists("hash")) {
return hash("sha512", $salt . $text . $pepper);
}
case 'normal':
// Prefer computing using HMAC
if (function_exists("hash_hmac")) {
return hash_hmac("sha256", $text, $pepper);
}
// Sha256 hash is the next best thing
if (function_exists("hash")) {
return hash("sha256", $salt . $text . $pepper);
}
case 'weak':
throw \Exception("Too weak of a Hash FN");
// return sha1($salt . $text . $pepper);
case 'low':
throw \Exception("Too weak of a Hash FN");
// return md5($salt . md5($text . $pepper));
default:
break;
}
return self::use_hmac($level, $pepper);
}
/**
* @method filter_class
* @param type $class
* Please NEVER add a period or SLASH as it will allow BAD things!
* IT should be a-zA-Z0-9_ and that's it.
* @retval string of safe class name
*/
public static function filter_class(string $class): string {
if (bootstrap\requires::is_dangerous($class)) {
throw new \Exception("Dangerious URI!");
}
return preg_replace('/[^a-zA-Z0-9_]/', '', $class);
}
/**
* Filter possible unsafe URI, prevent ../up-level hackers
* @param string $uri
* @return string Safe URI
*/
public static function filter_uri(string $uri): string {
if (bootstrap\requires::is_dangerous($uri) === true) {
throw new \Exception("Dangerious URI!");
}
return bootstrap\requires::filter_file_name($uri);
}
public static function id_hash(): string {
return crc32($_SESSION['user_id']);
}
public static function get_valid_ip(string $ip) {
return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6));
}
public static function get_valid_public_ip(string $ip) {
return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE));
}
/**
* Is the server on the local test domain name
* @return bool SERVER Domain name is on whitelist
*/
public static function is_server_name_on_domain_list(array $whitelist): bool {
if (!isset($_SERVER['SERVER_NAME'])) {
return false;
}
return (in_array($_SERVER['SERVER_NAME'], $whitelist));
}
/**
* Check if same Domain as Server
* @return bool
*/
public static function request_is_same_domain(): bool {
if (!isset($_SERVER['HTTP_REFERER'])) {
// No referer send, so can't be same domain!
return false;
} else {
$referer_host = parse_url($_SERVER['HTTP_REFERER'] . PHP_URL_HOST);
if ($referer_host === false) {
return false; // Malformed URL
}
$refed_host = $referer_host['host'] ?? "";
$server_host = $_SERVER['HTTP_HOST'];
return ($refed_host === $server_host);
}
}
public static function get_client_ip_address() {
$ipaddress = '';
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
} else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else if (isset($_SERVER['HTTP_X_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
} else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
} else if (isset($_SERVER['HTTP_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED'];
} else if (isset($_SERVER['REMOTE_ADDR'])) {
$ipaddress = $_SERVER['REMOTE_ADDR'];
} else {
$ipaddress = 'UNKNOWN';
}
return $ipaddress;
}
/**
* Make sure uploads (LIKE Images, etc...) do NOT run PHP code!!
* Checks for PHP tags inside of file.
* @param string $file
* @return bool true if PHP was found
*/
public static function file_contains_php(string $file): bool {
$file_handle = fopen($file, "r");
while (!feof($file_handle)) {
$line = fgets($file_handle);
$pos = strpos($line, '<?php');
if ($pos !== false) {
fclose($file_handle);
return true;
}
}
fclose($file_handle);
return false;
}
}