From 9b60d01456089a647359ecd4a03a0317a022d5a7 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 31 Jul 2025 20:53:30 -0400 Subject: [PATCH] init 9 --- docs/TODO.md | 24 +- src/bootstrap/safer_io.php | 4 +- src/classes/apis/api.php | 2 +- src/classes/app.php | 8 - src/classes/assets.php | 8 +- src/classes/exceptions/DB_Exception.php | 4 +- src/classes/php_file_cache.php | 7 +- src/classes/random_engine.php | 2 +- src/classes/safer_sql.php | 465 ++++++++++++++++++ src/classes/security.php | 23 +- .../services/paragon_crypto/crypto.php | 30 +- .../paragon_crypto/password_storage.php | 4 +- .../paragon_crypto/sodium_storage.php | 4 +- .../sessions/cookie_session_handler.php | 4 +- 14 files changed, 531 insertions(+), 58 deletions(-) create mode 100644 src/classes/safer_sql.php diff --git a/docs/TODO.md b/docs/TODO.md index cc75ff2..97cf701 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,6 +7,10 @@ [x] → Encrypted/Compressed Sessions + [ ] → Ensure JS error reporting works + + [ ] → Force Database methods to try Cache First and Save Cache Data + [ ] → Kernels [ ] → HTTP → Requests, Responce @@ -41,11 +45,17 @@ [x] → End/Open Tag Matching - [x] → UUIDv7 for APIs, etc... - [ ] → Rate Limiting - [?] → PHP Mailer / Access Token + [x] → PHP Mailer + + [ ] → Access Tokens (like for emails) + + [x] → UUIDv7 (like Database IDs) + + [x] → CSRF Tokens in security.php... + + [x] → Password Hashing/Verify in security.php [x] → Twilio Support @@ -53,10 +63,6 @@ [x] → Error Handler - [x] → CSRF Tokens - - [x] → Password Hashing/Verify - [ ] → Sane Config/Service file defaults [ ] → Sane Folder Structure and Documentation @@ -68,6 +74,4 @@ [ ] → Tests - [x] → RSS Feed - - [x] → Private API for Sensitive Transactions: + [x] → RSS Feed \ No newline at end of file diff --git a/src/bootstrap/safer_io.php b/src/bootstrap/safer_io.php index 2097b4e..5fe86e3 100644 --- a/src/bootstrap/safer_io.php +++ b/src/bootstrap/safer_io.php @@ -521,9 +521,9 @@ final class safer_io { $safer_db_data = $safer_data; } else { if (isset($a->use_db_filter) && $a->use_db_filter == DB_FILTER::ON) { - $safe_for_db = \tts\extras\safer_sql::get_safer_sql_text($safer_data); + $safe_for_db = \CodeHydrater\safer_sql::get_safer_sql_text($safer_data); $text = $safe_for_db["text"]; - $meta[$input_field_name]['db_filter_status'] = $safe_for_db["status"] ?? \tts\SQL_SAFETY_FLAG::filtered; + $meta[$input_field_name]['db_filter_status'] = $safe_for_db["status"] ?? \CodeHydrater\SQL_SAFETY_FLAG::filtered; } else { $text = $safer_data; } diff --git a/src/classes/apis/api.php b/src/classes/apis/api.php index 61b2d1e..b0d5e98 100644 --- a/src/classes/apis/api.php +++ b/src/classes/apis/api.php @@ -231,7 +231,7 @@ class api { $data['code'] = $long_code; - $memory_check = bootstrap\common::get_bool(\tts\misc::request_var('debug')); + $memory_check = bootstrap\common::get_bool(\CodeHydrater\misc::request_var('debug')); if ($memory_check) { $echo = false; $data['memory_used'] = memory_usage::get_memory_stats($echo); diff --git a/src/classes/app.php b/src/classes/app.php index c38d27f..c07262e 100644 --- a/src/classes/app.php +++ b/src/classes/app.php @@ -12,14 +12,6 @@ namespace CodeHydrater; use Exception; -/** - * - * @todo Ensure JS error reporting works - * @todo Finish Session MGT, Encrypted Sessions - * @todo Make Cached Sessions - * @todo Force Database methods to try Cache First and Save Cache Data - */ - class app { private $file; private $class; diff --git a/src/classes/assets.php b/src/classes/assets.php index 5573370..48b6e9b 100644 --- a/src/classes/assets.php +++ b/src/classes/assets.php @@ -112,13 +112,13 @@ final class assets { } if ($scope === 'project' || $scope === 'app') { $path = PROJECT_ASSETS_DIR . "/"; - $tts = self::is_minified($path, $file); - return ($tts !== false) ? PROJECT_ASSETS_BASE_REF . "/" . $tts : false; + $ism = self::is_minified($path, $file); + return ($ism !== false) ? PROJECT_ASSETS_BASE_REF . "/" . $ism : false; } if ($scope === 'assets') { $path = ASSETS_DIR . "/"; - $tts = self::is_minified($path, $file); - return ($tts !== false) ? ASSETS_BASE_REF . "/" . $tts : false; + $ism = self::is_minified($path, $file); + return ($ism !== false) ? ASSETS_BASE_REF . "/" . $ism : false; } return $file; } diff --git a/src/classes/exceptions/DB_Exception.php b/src/classes/exceptions/DB_Exception.php index cb957d8..8e72026 100644 --- a/src/classes/exceptions/DB_Exception.php +++ b/src/classes/exceptions/DB_Exception.php @@ -53,8 +53,8 @@ class DB_Exception extends \Exception { if (empty($message)) { $message = self::$error_message; } - $live = (\main_tts\is_live()); - if ($live === false || \tts\session_management::has_user_right('debugger')) { + $live = (\CodeHydrater\bootstrap\is_live()); + if ($live === false || \CodeHydrater\session_management::has_user_right('debugger')) { $msg = ($debug == DEBUGGER::basic || (self::$debug === DEBUGGER::basic && $debug === DEBUGGER::static) diff --git a/src/classes/php_file_cache.php b/src/classes/php_file_cache.php index dfde1ee..e7ae93e 100644 --- a/src/classes/php_file_cache.php +++ b/src/classes/php_file_cache.php @@ -18,7 +18,12 @@ class php_file_cache { protected $cache_path; public function __construct($path) { - $this->cache_path = rtrim($path, '/') . '/'; + $is_safe_path = bootstrap\requires::is_dangerous($path); + if ($is_safe_path === false) { + throw new \Exception("Bad cache path"); + } + $safer_path = bootstrap\requires::filter_dir_path($path); + $this->cache_path = rtrim($safer_path, '/') . '/'; if (!is_dir($this->cache_path)) { mkdir($this->cache_path, 0775, true); } diff --git a/src/classes/random_engine.php b/src/classes/random_engine.php index 06fbfb7..1681977 100644 --- a/src/classes/random_engine.php +++ b/src/classes/random_engine.php @@ -61,7 +61,7 @@ class random_engine { } private function select_from_array(array $a, int $num ): array { - $array_count = \bs_tts\common::get_count($a) - 1; + $array_count = \CodeHydrater\bootstrap\common::get_count($a) - 1; if ($array_count < 1) { return []; } diff --git a/src/classes/safer_sql.php b/src/classes/safer_sql.php new file mode 100644 index 0000000..1bd22d3 --- /dev/null +++ b/src/classes/safer_sql.php @@ -0,0 +1,465 @@ + + * @copyright Copyright (c) 2022, Robert Strutts. + * @license MIT + */ + +/** + * @todo This is just for PLAY for now!!!! + * Too many FALSE positives and too many FALSE Negatives!!! + */ + +namespace CodeHydrater; + +enum SQL_SAFETY_FLAG { + case good; // All Okey + case filtered; // Found isseues but tried to filter them out + case dangerious; // May still be bad +} + +class safer_sql { + + /** + * List of SQL attacks: + * @link https://github.com/payloadbox/sql-injection-payload-list + * @link https://www.neuralegion.com/blog/sql-injection-payloads/ + * @link https://ismailtasdelen.medium.com/sql-injection-payload-list-b97656cfd66b + */ + + // NoSQL attacks and now SQL attacks as JSON is used EveryWhere + private static function found_bad_json(string $string): bool { + if (function_exists("str_contains")) { + if (str_contains($string, '[]') === true) return true; // GET Array + } + if (preg_match('/.match/', $string) === 1) return true; + if (preg_match('/&&/', $string) === 1) return true; + if (preg_match('/\|\|/', $string) === 1) return true; + if (preg_match('/\$where/', $string) === 1) return true; + if (preg_match("/mapReduce/", $string) === 1) return true; + if (preg_match('/\$group/', $string) === 1) return true; + if (preg_match('/\$regex/', $string) === 1) return true; + if (preg_match('/\$exists/', $string) === 1) return true; + if (preg_match('/\$ne/', $string) === 1) return true; + if (preg_match('/\$gt/', $string) === 1) return true; + if (preg_match('/\$lt/', $string) === 1) return true; + if (preg_match('/\$eq/', $string) === 1) return true; + if (preg_match('/\$nin/', $string) === 1) return true; + if (preg_match('/\$where/', $string) === 1) return true; + if (preg_match('/\$or/', $string) === 1) return true; + if (preg_match('/\$and/', $string) === 1) return true; + if (preg_match('/\$in/', $string) === 1) return true; + if (preg_match('/\$accumulator/', $string) === 1) return true; + if (preg_match('/\$function/', $string) === 1) return true; + if (preg_match("/:/", $string) === 1 && + preg_match("/{/", $string) === 1) return true; + return false; + } + + // Stacked queries can be used to execute multiple queries in succession. + private static function found_sql_stacker(string $string): bool { + if (preg_match("/;/", $string) === 1) return true; + return false; + } + + private static function found_sql_compair(string $string): bool { + if (preg_match("/=/", $string) === 1) return true; + return false; + } + + private static function found_quotes_in(string $string): bool { + if (preg_match("/'/", $string) === 1) return true; + if (preg_match("/\"/", $string) === 1) return true; + return false; + } + + private static function found_sql_comment(string $string): bool { + if (preg_match("/\#/", $string) === 1) return true; // Hash + if (preg_match("/\/\*/", $string) === 1) return true; // C-style comment + if (preg_match("/--/", $string) === 1) return true; // SQL comment + if (preg_match("/\`/", $string) === 1) return true; // Backtick + //if (preg_match("/\x00/", $string) === 1) return true; // NULL Byte, !errors out! + return false; + } + + private static function found_string_cat(string $string): bool { + if (preg_match("/\+/i", $string) === 1) return true; // addition, concatenate (or space in url) + if (preg_match("/\|\|/i", $string) === 1) return true; // (double pipe) concatenate + if (preg_match("/\%/", $string) === 1) return true; // wildcard attribute indicator + return false; + } + + private static function found_sql_time_based_attack(string $string): bool { + if (preg_match("/union/i", $string) === 1) { + if (preg_match("/all/i", $string) === 1) { + return true; + } + if (preg_match("/select/i", $string) === 1) { + return true; + } + if (preg_match("/null/i", $string) === 1) { + return true; + } + } + if (preg_match("/sleep\s*\(/i", $string) === 1) return true; + if (preg_match("/waitfor delay/i", $string) === 1) return true; + if (preg_match("/benchmark\s*\(/i", $string) === 1) return true; + if (preg_match("/pg_sleep\s*\(/i", $string) === 1) return true; + if (preg_match("/randomblob/i", $string) === 1) return true; + + if (preg_match("/convert/i", $string) === 1) { + if (preg_match("/int/i", $string) === 1) { + return true; + } + } + return false; + } + + private static function found_sql_suspicious(string $string): bool { + if (self::found_sql_compair($string) === true) return true; + if (self::found_sql_stacker($string) === true) return true; + if (self::found_sql_comment($string) === true) return true; + if (self::found_quotes_in($string) === true) return true; + if (self::found_string_cat($string) === true) return true; + return false; + } + + private static function found_sql_admin_attack(string $string): bool { + if (preg_match("/admin/i", $string) === 1) { + if (preg_match("/ or /i", $string) === 1) { + if (preg_match("/1/", $string) === 1) { + return true; + } + if (preg_match("/0/", $string) === 1) { + return true; + } + } + if (self::found_sql_suspicious($string) === true) return true; + } + return false; + } + + private static function found_sql_statement(string $string): bool { + if (preg_match("/or/i", $string) === 1 || preg_match("/and/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + if (preg_match("/drop database/i", $string) === 1) return true; + if (preg_match("/drop table/i", $string) === 1) return true; + if (preg_match("/drop user/i", $string) === 1) return true; + if (preg_match("/drop view/i", $string) === 1) return true; + if (preg_match("/drop column/i", $string) === 1) return true; + if (preg_match("/drop procedure/i", $string) === 1) return true; + if (preg_match("/drop/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + if (preg_match("/where/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + if (preg_match("/select/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + if (preg_match("/having/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + if (preg_match("/distinct/i", $string) === 1) { + if (self::found_sql_suspicious($string) === true) return true; + } + // NOTE: \s* is for no-space or spaces!!! /i is to ignore Case! + if (preg_match("/\'\s*or/i", $string) === 1) return true; + if (preg_match("/\'\)\s*or/i", $string) === 1) return true; + if (preg_match("/\'\s*and/i", $string) === 1) return true; + if (preg_match("/\'\)\s*and/i", $string) === 1) return true; + if (preg_match("/or\s*true\-\-/", $string) === 1) return true; + if (preg_match("/if\s*\(/i", $string) === 1) return true; + if (preg_match("/\@\@version/i", $string) === 1) return true; // Get DB Ver + if (preg_match("/\@\@hostname/i", $string) === 1) return true; + if (preg_match("/\@\@datadir/i", $string) === 1) return true; + if (preg_match("/connection_id/i", $string) === 1) return true; // MySQL DB ID + if (preg_match("/crc32\s*\(/i", $string) === 1) return true; // MySQL DB ID + if (preg_match("/conv\s*\(/i", $string) === 1) return true; // MySQL DB ID + if (preg_match("/\@\@connections/i", $string) === 1) return true; // MSSQL DB ID + if (preg_match("/\@\@cpu_busy/i", $string) === 1) return true; // MSSQL DB ID + if (preg_match("/user_id\s*\(/i", $string) === 1) return true; // MSSQL DB ID + if (preg_match("/rownum/i", $string) === 1) return true; // Oracle DB ID + if (preg_match("/rawtohex\s*\(/i", $string) === 1) return true; // Oracle DB ID + if (preg_match("/lnnvl\s*\(/i", $string) === 1) return true; // Oracle DB ID + if (preg_match("/::int/i", $string) === 1) return true; // PostgreSQL DB ID + if (preg_match("/pg_client_encoding\s*\(/i", $string) === 1) return true; // PostgreSQL DB ID + if (preg_match("/get_current_ts_config\s*\(/i", $string) === 1) return true; // PostgreSQL DB ID + if (preg_match("/quote_literal\s*\(/i", $string) === 1) return true; // PostgreSQL DB ID + if (preg_match("/current_database\s*\(/i", $string) === 1) return true; // PostgreSQL DB ID + if (preg_match("/sqlite_version\s*\(/i", $string) === 1) return true; // SQLite DB ID + if (preg_match("/last_insert_rowid\s*\(/i", $string) === 1) return true; // SQLite DB ID + if (preg_match("/last_insert_rowid\s*\(/i", $string) === 1) return true; // SQLite DB ID + if (preg_match("/val\s*\(/i", $string) === 1) return true; // MSAccess DB ID + if (preg_match("/cvar\s*\(/i", $string) === 1) return true; // MSAccess DB ID + if (preg_match("/iif\s*\(/i", $string) === 1) return true; // MSAccess DB ID + if (preg_match("/atn\s*\(/i", $string) === 1) return true; // MSAccess DB ID + if (preg_match("/cdbl\s*\(/i", $string) === 1) return true; // MSAccess DB ID + if (preg_match("/grant\s*all/i", $string) === 1) return true; + if (preg_match("/into\s*dumpfile/i", $string) === 1) return true; + if (preg_match("/load\s*data\s*infile/i", $string) === 1) return true; + if (preg_match("/into\s*outfile/i", $string) === 1) return true; + if (preg_match("/load_file/i", $string) === 1) return true; + if (preg_match("/delete\s*from/i", $string) === 1) return true; + if (preg_match("/group_concat/i", $string) === 1) return true; + if (preg_match("/order by/i", $string) === 1) return true; + if (preg_match("/case when/i", $string) === 1) return true; + if (preg_match("/extractvalue/i", $string) === 1) return true; + if (preg_match("/\/etc\/passwd/i", $string) === 1) return true; + if (preg_match("/\/var\/log/i", $string) === 1) return true; + if (preg_match("/binary_checksum\s*\(/i", $string) === 1) return true; // ID MSSQL DB Engine + if (preg_match("/user\s*\(\)/i", $string) === 1) return true; // Get current user + if (preg_match("/system_user[\s]+\(\)/i", $string) === 1) return true; // Get current user + if (preg_match("/mysql.user/i", $string) === 1) return true; // List Users + if (preg_match("/mysql.db/i", $string) === 1) return true; // List Databases + if (preg_match("/database\s*\(/i", $string) === 1) return true; + if (preg_match("/information_schema/i", $string) === 1) return true; // List Columns + if (preg_match("/table_schema/i", $string) === 1) return true; + if (preg_match("/table_name/i", $string) === 1) return true; + if (preg_match("/\.columns/i", $string) === 1) return true; + if (preg_match("/\.tables/i", $string) === 1) return true; + + // https://www.w3schools.com/mysql/mysql_ref_functions.asp + $fns = [ + // String FNs + 'ascii', 'char_length', 'character_length', 'concat', 'concat_ws', + 'field', 'find_in_set', 'format', 'insert', 'instr', 'lcase', 'left', + 'length', 'locate', 'lower', 'lpad', 'ltrim', 'mid', 'position', + 'repeat', 'replace', 'reverse', 'right', 'rpad', 'rtrim', 'space', + 'strcmp', 'substr', 'substring', 'substring_index', 'trim', 'ucase', + 'upper', + // Numeric FNs + 'abs','acos', 'asin', 'atan', 'atan2', 'avg', 'ceil', 'ceiling', 'cos', + 'cot', 'count', 'degrees', 'div', 'exp', 'floor', 'greatest', 'least', + 'ln', 'log', 'log10', 'log2', 'max', 'min', 'mod', 'pi', 'pow', 'power', + 'radians', 'rand', 'round', 'sign', 'sin', 'sqrt', 'sum', 'tan', 'truncate', + // ADV FNs + 'bin', 'binary', 'case', 'cast', 'coalesce', 'connection_id', 'conv', + 'convert', 'current_user', 'database', 'if', 'ifnull', 'isnull', + 'last_insert_id', 'nullif', 'session_user', 'system_user', 'user', + 'version', + // OP FNs + 'and', 'between', 'cast', 'char', 'count', 'div', 'in', 'is', 'is_not', + 'is_not_null', 'is_null', 'like', 'member_of', 'not', 'not_between', + 'not_in', 'not_like', 'not_regexp', 'or', 'regexp', 'sounds_like', + 'floor', 'md5', 'rand', 'rlike', 'row', 'xor', + // https://dev.mysql.com/doc/refman/8.0/en/string-functions.html + 'bit_length', 'let', 'export_set', 'from_base64', 'hex', 'load_file', + 'make_set', 'match', 'oct', 'octet_length', 'ord', 'quote', 'regexp', + 'regexp_instr', 'regexp_like', 'regexp_replace', 'regexp_substr', + 'select', 'soundex', 'to_base64', 'unhex', 'weight_string' + ]; + + foreach($fns as $fn_keyword) { + if (preg_match("/{$fn_keyword}\s*\(/i", $string) === 1) return true; + } + return false; + } + + private static function found_sql_evil_symbols(string $string): bool { + if (preg_match("/\-/", $string) === 1) return true; + if (preg_match("/\&/", $string) === 1) return true; + if (preg_match("/\*/", $string) === 1) return true; + if (preg_match("/\^/", $string) === 1) return true; + if (preg_match("/\'\s*\'/", $string) === 1) return true; + if (preg_match("/\"\s*\"/", $string) === 1) return true; + if (preg_match("/\)\)/", $string) === 1) return true; + if (preg_match("/\//", $string) === 1) return true; + if (function_exists("str_contains")) { + if (str_contains($string, '\')') === true) return true; + if (str_contains($string, '\")') === true) return true; + if (str_contains($string, '\`)') === true) return true; + if (str_contains($string, '\\') === true) return true; + if (str_contains($string, '%00') === true) return true; + if (str_contains($string, '\' or 1=1-- -') === true) return true; // Normal SQL + if (str_contains($string, '\' || 1==1//') === true) return true; // Mongo SQL + if (str_contains($string, '\' || 1==1%00') === true) return true; // Mongo SQL + } + return false; + } + + private static function found_sql_bad_symbols(string $string): bool { + $ltc = ltrim($string); + $ltc_len = strlen($ltc); + $left_most = ($ltc_len < 6) ? $ltc_len : 6; + $first_chars = substr($ltc, 0, $left_most); + if ($first_chars !== false) { + if (function_exists("str_contains")) { + if (str_contains($first_chars, 'or') === true) return true; + if (str_contains($first_chars, 'and') === true) return true; + } + if (self::found_sql_suspicious($first_chars) === true) return true; + if (self::found_sql_evil_symbols($first_chars) === true) return true; + } + if (self::found_sql_suspicious($string) === false) return false; + + if (function_exists("str_contains")) { + if (str_contains($first_chars, '1') === true) return true; + if (str_contains($first_chars, '0') === true) return true; + if (str_contains($first_chars, '1337') === true) return true; + } + if (self::found_sql_evil_symbols($string) === true) return true; + return false; + } + + public static function found_sql_injection_cmds(string $string): bool { + /* + * There are a number of ways to prevent Poison Null Byte injections + * within PHP. These include escaping the NULL byte with a backslash, + * however, the most recommended way to do so is to completely remove + * the byte by using code similar to the following: + */ + $string = str_replace(chr(0), '', $string); + if (self::found_sql_time_based_attack($string) === true) return true; + if (self::found_sql_admin_attack($string) === true) return true; + if (self::found_sql_bad_symbols($string) === true) return true; + if (self::found_sql_statement($string) === true) return true; + if (self::found_bad_json($string) === true) return true; + if (self::found_sql_comment($string) === true && + self::found_quotes_in($string) === true) { + return true; + } + + return false; + } + + private static function found_sql_keyword(string $string): bool { + $words = [ +'ACCESSIBLE','ACCOUNT','ACTION','ACTIVE','ADD','ADMIN','AFTER','AGAINST','AGGREGATE', +'ALGORITHM','ALL','ALTER','ALWAYS','ANALYSE','ANALYZE','AND','ANY','ARRAY','AS', +'ASC','ASCII','ASENSITIVE','AT','AUTOEXTEND_SIZE','AUTO_INCREMENT','AVG','AVG_ROW_LENGTH', +'BACKUP','BEFORE','BEGIN','BETWEEN','BIGINT','BINARY','BINLOG','BIT','BLOB','BLOCK', +'BOOL','BOOLEAN','BOTH','BTREE','BUCKETS','BY','BYTE','CACHE','CALL','CASCADE','CASCADED', +'CASE','CATALOG_NAME','CHAIN','CHANGE','CHANGED','CHANNEL','CHAR','CHARACTER','CHARSET', +'CHECK','CHECKSUM','CIPHER','CLASS_ORIGIN','CLIENT','CLONE','CLOSE','COALESCE','CODE', +'COLLATE','COLLATION','COLUMN','COLUMNS','COLUMN_FORMAT','COLUMN_NAME','COMMENT','COMMIT', +'COMMITTED','COMPACT','COMPLETION','COMPONENT','COMPRESSED','COMPRESSION','CONCURRENT', +'CONDITION','CONNECTION','CONSISTENT','CONSTRAINT','CONSTRAINT_CATALOG','CONSTRAINT_NAME', +'CONSTRAINT_SCHEMA','CONTAINS','CONTEXT','CONTINUE','CONVERT','CPU','CREATE','CROSS', +'CUBE','CUME_DIST','CURRENT','CURRENT_DATE','CURRENT_TIME','CURRENT_TIMESTAMP','CURRENT_USER', +'CURSOR','CURSOR_NAME','DATA','DATABASE','DATABASES','DATAFILE','DATE','DATETIME','DAY', +'DAY_HOUR','DAY_MICROSECOND','DAY_MINUTE','DAY_SECOND','DEALLOCATE','DEC','DECIMAL', +'DECLARE','DEFAULT','DEFAULT_AUTH','DEFINER','DEFINITION','DELAYED','DELAY_KEY_WRITE', +'DELETE','DENSE_RANK','DESC','DESCRIBE','DESCRIPTION','DES_KEY_FILE','DETERMINISTIC', +'DIAGNOSTICS','DIRECTORY','DISABLE','DISCARD','DISK','DISTINCT','DISTINCTROW','DIV', +'DO','DOUBLE','DROP','DUAL','DUMPFILE','DUPLICATE','DYNAMIC','EACH','ELSE','ELSEIF', +'EMPTY','ENABLE','ENCLOSED','ENCRYPTION','END','ENDS','ENFORCED','ENGINE','ENGINES', +'ENUM','ERROR','ERRORS','ESCAPE','ESCAPED','EVENT','EVENTS','EVERY','EXCEPT', +'EXCHANGE','EXCLUDE','EXECUTE','EXISTS','EXIT','EXPANSION','EXPIRE','EXPLAIN', +'EXPORT','EXTENDED','EXTENT_SIZE','FAILED_LOGIN_ATTEMPTS','FALSE','FAST','FAULTS', +'FETCH','FIELDS','FILE','FILE_BLOCK_SIZE','FILTER','FIRST','FIRST_VALUE','FIXED', +'FLOAT','FLOAT4','FLOAT8','FLUSH','FOLLOWING','FOLLOWS','FOR','FORCE','FOREIGN', +'FORMAT','FOUND','FROM','FULL','FULLTEXT','FUNCTION','GENERAL','GENERATED', +'GEOMCOLLECTION','GEOMETRY','GEOMETRYCOLLECTION','GET','GET_FORMAT','GET_MASTER_PUBLIC_KEY', +'GLOBAL','GRANT','GRANTS','GROUP','GROUPING','GROUPS','GROUP_REPLICATION','HANDLER', +'HASH','HAVING','HELP','HIGH_PRIORITY','HISTOGRAM','HISTORY','HOST','HOSTS','HOUR', +'HOUR_MICROSECOND','HOUR_MINUTE','HOUR_SECOND','IDENTIFIED','IF','IGNORE', +'IGNORE_SERVER_IDS','IMPORT','IN','INACTIVE','INDEX','INDEXES','INFILE','INITIAL_SIZE', +'INNER','INOUT','INSENSITIVE','INSERT','INSERT_METHOD','INSTALL','INSTANCE','INT','INT1', +'INT2','INT3','INT4','INT8','INTEGER','INTERVAL','INTO','INVISIBLE','INVOKER','IO', +'IO_AFTER_GTIDS','IO_BEFORE_GTIDS','IO_THREAD','IPC','IS','ISOLATION','ISSUER','ITERATE', +'JOIN','JSON','JSON_TABLE','JSON_VALUE','KEY','KEYS','KEY_BLOCK_SIZE','KILL','LAG', +'LANGUAGE','LAST','LAST_VALUE','LATERAL','LEAD','LEADING','LEAVE','LEAVES','LEFT', +'LESS','LEVEL','LIKE','LIMIT','LINEAR','LINES','LINESTRING','LIST','LOAD','LOCAL', +'LOCALTIME','LOCALTIMESTAMP','LOCK','LOCKED','LOCKS','LOGFILE','LOGS','LONG', +'LONGBLOB','LONGTEXT','LOOP','LOW_PRIORITY','MASTER','MASTER_AUTO_POSITION', +'MASTER_BIND','MASTER_COMPRESSION_ALGORITHMS','MASTER_CONNECT_RETRY','MASTER_DELAY', +'MASTER_HEARTBEAT_PERIOD','MASTER_HOST','MASTER_LOG_FILE', +'MASTER_LOG_POS','MASTER_PASSWORD','MASTER_PORT','MASTER_PUBLIC_KEY_PATH', +'MASTER_RETRY_COUNT','MASTER_SERVER_ID','MASTER_SSL','MASTER_SSL_CA', +'MASTER_SSL_CAPATH','MASTER_SSL_CERT','MASTER_SSL_CIPHER','MASTER_SSL_CRL', +'MASTER_SSL_CRLPATH','MASTER_SSL_KEY','MASTER_SSL_VERIFY_SERVER_CERT', +'MASTER_TLS_CIPHERSUITES','MASTER_TLS_VERSION','MASTER_USER', +'MASTER_ZSTD_COMPRESSION_LEVEL','MATCH','MAXVALUE','MAX_CONNECTIONS_PER_HOUR', +'MAX_QUERIES_PER_HOUR','MAX_ROWS','MAX_SIZE','MAX_UPDATES_PER_HOUR','MAX_USER_CONNECTIONS', +'MEDIUM','MEDIUMBLOB','MEDIUMINT','MEDIUMTEXT','MEMBER','MEMORY','MERGE','MESSAGE_TEXT', +'MICROSECOND','MIDDLEINT','MIGRATE','MINUTE','MINUTE_MICROSECOND','MINUTE_SECOND', +'MIN_ROWS','MOD','MODE','MODIFIES','MODIFY','MONTH','MULTILINESTRING','MULTIPOINT', +'MULTIPOLYGON','MUTEX','MYSQL_ERRNO','NAME','NAMES','NATIONAL','NATURAL','NCHAR', +'NDB','NDBCLUSTER','NESTED','NETWORK_NAMESPACE','NEVER','NEW','NEXT','NO','NODEGROUP', +'NONE','NOT','NOWAIT','NO_WAIT','NO_WRITE_TO_BINLOG','NTH_VALUE','NTILE','NULL','NULLS', +'NUMBER','NUMERIC','NVARCHAR','OF','OFF','OFFSET','OJ','OLD','ON','ONE','ONLY','OPEN', +'OPTIMIZE','OPTIMIZER_COSTS','OPTION','OPTIONAL','OPTIONALLY','OPTIONS','OR','ORDER', +'ORDINALITY','ORGANIZATION','OTHERS','OUT','OUTER','OUTFILE','OVER','OWNER','PACK_KEYS', +'PAGE','PARSER','PARTIAL','PARTITION','PARTITIONING','PARTITIONS','PASSWORD', +'PASSWORD_LOCK_TIME','PATH','PERCENT_RANK','PERSIST','PERSIST_ONLY','PHASE','PLUGIN', +'PLUGINS','PLUGIN_DIR','POINT','POLYGON','PORT','PRECEDES','PRECEDING','PRECISION', +'PREPARE','PRESERVE','PREV','PRIMARY','PRIVILEGES','PRIVILEGE_CHECKS_USER','PROCEDURE', +'PROCESS','PROCESSLIST','PROFILE','PROFILES','PROXY','PURGE','QUARTER','QUERY','QUICK', +'RANDOM','RANGE','RANK','READ','READS','READ_ONLY','READ_WRITE','REAL','REBUILD', +'RECOVER','RECURSIVE','REDOFILE','REDO_BUFFER_SIZE','REDUNDANT','REFERENCE','REFERENCES', +'REGEXP','RELAY','RELAYLOG','RELAY_LOG_FILE','RELAY_LOG_POS','RELAY_THREAD','RELEASE', +'RELOAD','REMOTE','REMOVE','RENAME','REORGANIZE','REPAIR','REPEAT','REPEATABLE','REPLACE', +'REPLICATE_DO_DB','REPLICATE_DO_TABLE','REPLICATE_IGNORE_DB','REPLICATE_IGNORE_TABLE', +'REPLICATE_REWRITE_DB','REPLICATE_WILD_DO_TABLE','REPLICATE_WILD_IGNORE_TABLE', +'REPLICATION','REQUIRE','REQUIRE_ROW_FORMAT','RESET','RESIGNAL','RESOURCE','RESPECT', +'RESTART','RESTORE','RESTRICT','RESUME','RETAIN','RETURN','RETURNED_SQLSTATE','RETURNING', +'RETURNS','REUSE','REVERSE','REVOKE','RIGHT','RLIKE','ROLE','ROLLBACK','ROLLUP','ROTATE', +'ROUTINE','ROW','ROWS','ROW_COUNT','ROW_FORMAT','ROW_NUMBER','RTREE','SAVEPOINT','SCHEDULE', +'SCHEMA','SCHEMAS','SCHEMA_NAME','SECOND','SECONDARY','SECONDARY_ENGINE','SECONDARY_LOAD', +'SECONDARY_UNLOAD','SECOND_MICROSECOND','SECURITY','SELECT','SENSITIVE','SEPARATOR','SERIAL', +'SERIALIZABLE','SERVER','SESSION','SET','SHARE','SHOW','SHUTDOWN','SIGNAL','SIGNED','SIMPLE', +'SKIP','SLAVE','SLOW','SMALLINT','SNAPSHOT','SOCKET','SOME','SONAME','SOUNDS','SOURCE', +'SPATIAL','SPECIFIC','SQL','SQLEXCEPTION','SQLSTATE','SQLWARNING','SQL_AFTER_GTIDS', +'SQL_AFTER_MTS_GAPS','SQL_BEFORE_GTIDS','SQL_BIG_RESULT','SQL_BUFFER_RESULT','SQL_CACHE', +'SQL_CALC_FOUND_ROWS','SQL_NO_CACHE','SQL_SMALL_RESULT','SQL_THREAD','SQL_TSI_DAY', +'SQL_TSI_HOUR','SQL_TSI_MINUTE','SQL_TSI_MONTH','SQL_TSI_QUARTER','SQL_TSI_SECOND', +'SQL_TSI_WEEK','SQL_TSI_YEAR','SRID','SSL','STACKED','START','STARTING','STARTS', +'STATS_AUTO_RECALC','STATS_PERSISTENT','STATS_SAMPLE_PAGES','STATUS','STOP','STORAGE', +'STORED','STRAIGHT_JOIN','STREAM','STRING','SUBCLASS_ORIGIN','SUBJECT','SUBPARTITION', +'SUBPARTITIONS','SUPER','SUSPEND','SWAPS','SWITCHES','SYSTEM','TABLE','TABLES','TABLESPACE', +'TABLE_CHECKSUM','TABLE_NAME','TEMPORARY','TEMPTABLE','TERMINATED','TEXT','THAN','THEN', +'THREAD_PRIORITY','TIES','TIME','TIMESTAMP','TIMESTAMPADD','TIMESTAMPDIFF','TINYBLOB', +'TINYINT','TINYTEXT','TLS','TO','TRAILING','TRANSACTION','TRIGGER','TRIGGERS','TRUE', +'TRUNCATE','TYPE','TYPES','UNBOUNDED','UNCOMMITTED','UNDEFINED','UNDO','UNDOFILE', +'UNDO_BUFFER_SIZE','UNICODE','UNINSTALL','UNION','UNIQUE','UNKNOWN','UNLOCK','UNSIGNED', +'UNTIL','UPDATE','UPGRADE','USAGE','USE','USER','USER_RESOURCES','USE_FRM','USING', +'UTC_DATE','UTC_TIME','UTC_TIMESTAMP','VALIDATION','VALUE','VALUES','VARBINARY', +'VARCHAR','VARCHARACTER','VARIABLES','VARYING','VCPU','VIEW','VIRTUAL','VISIBLE','WAIT', +'WARNINGS','WEEK','WEIGHT_STRING','WHEN','WHERE','WHILE','WINDOW','WITH','WITHOUT', +'WORK','WRAPPER','WRITE','X509','XA','XID','XML','XOR','YEAR','YEAR_MONTH','ZEROFILL' + ]; + $parts = preg_split('/\s+/', $string); + foreach($parts as $part) { + $lc = mb_strtoupper($part); + if (in_array($lc, $words)) return true; + } + return false; + } + + private static function try_to_clean_sql(string $string): string { + $bad_items = [ + '.user', '.db','.columns', '.tables', 'load_file', 'table_', + '_schema', 'connection_id', 'binary_checksum', 'cpu_busy', + 'user_id', 'rownum', 'rawtohex', 'lnnvl', 'pg_client_encoding', + 'get_current_ts_config', 'quote_literal', 'current_database', + 'sqlite_version', 'last_insert_rowid', 'last_insert_rowid', + 'group_concat', 'system_user' + + ]; + return str_replace($bad_items, "", $string); + } + + public static function get_safer_sql_text(string $string): array { + $string = str_replace(chr(0), '', $string); // Remove NULL Bytes + if (self::found_sql_injection_cmds($string)) { + /* + * At this Point: Never allow Quotes, @$*=()%` + * Limit allowed chars now, to safe ones! + */ + $cleaner = self::try_to_clean_sql($string); + $safer = preg_replace('/[^a-zA-Z0-9.\s]/', "", $cleaner); + + if (self::found_sql_keyword($safer)) { + return ["text"=>$safer, "status"=>SQL_SAFETY_FLAG::dangerious]; + } + return ["text"=>$safer, "status"=>SQL_SAFETY_FLAG::filtered]; + } + return ["text"=>$string, "status"=>SQL_SAFETY_FLAG::good]; + } + +} \ No newline at end of file diff --git a/src/classes/security.php b/src/classes/security.php index a8144ae..72acb8b 100644 --- a/src/classes/security.php +++ b/src/classes/security.php @@ -76,26 +76,33 @@ final class security { return false; } + private static function is_valid_hash_algo($algo): bool { + return (in_array($algo, password_algos())); + } + /* * 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 { + public static function do_password_hash(#[\SensitiveParameter] string $password): bool | string { $pwd_peppered = self::make_hash($password); - $hash_algo = \main_tts\configure::get( + $hash_algo = bootstrap\configure::get( "security", "hash_algo" - ); - if (! empty($hash_algo)) { - return password_hash($pwd_peppered, $hash_algo); + ) ?? false; + if ($hash_algo === false) { + throw new \Exception("Security Hash Algo not set!"); + } + if (! self::is_valid_hash_algo($hash_algo)) { + throw new \Exception("Invalid Security Hash Alogo set"); } - return password_hash($pwd_peppered, self::find_default_hash_algo()); + return password_hash($pwd_peppered, $hash_algo); } public static function do_password_verify( - string $input_pwd, $db_password + #[\SensitiveParameter] string $input_pwd, #[\SensitiveParameter] $db_password ): bool { $pwd_peppered = self::make_hash($input_pwd); return password_verify($pwd_peppered, $db_password); @@ -107,7 +114,7 @@ final class security { * @param string $level (weak, low, high, max) * @return string new Hashed */ - public static function make_hash(string $text): string { + public static function make_hash(#[\SensitiveParameter] string $text): string { $level = bootstrap\configure::get('security', 'hash_level'); if (empty($level)) { $level = "normal"; diff --git a/src/classes/services/paragon_crypto/crypto.php b/src/classes/services/paragon_crypto/crypto.php index 9a2843d..be9705e 100644 --- a/src/classes/services/paragon_crypto/crypto.php +++ b/src/classes/services/paragon_crypto/crypto.php @@ -18,7 +18,7 @@ class crypto { public function __construct(#[\SensitiveParameter] private string $key) { - $this->rnd = new \tts\random_engine(); + $this->rnd = new \CodeHydrater\random_engine(); } /* * Secret key encryption (or symmetric encryption as it’s also @@ -167,7 +167,7 @@ class crypto { } public static function a_single_key_maker(): string { - $rnd = new \tts\random_engine(); + $rnd = new \CodeHydrater\random_engine(); return base64_encode($rnd->get_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)); } /* @@ -213,26 +213,26 @@ class crypto { /* * Usage: * -$key = tts\services\paragon_crypto\crypto::a_single_key_maker(); -$enc = tts\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String...', $key, \tts\services\paragon_crypto\crypto::single_key); +$key = CodeHydrater\services\paragon_crypto\crypto::a_single_key_maker(); +$enc = CodeHydrater\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String...', $key, \CodeHydrater\services\paragon_crypto\crypto::single_key); echo $enc . "
"; -$dec = \tts\services\paragon_crypto\crypto::safe_decrypt($enc, $key, \tts\services\paragon_crypto\crypto::single_key); +$dec = \CodeHydrater\services\paragon_crypto\crypto::safe_decrypt($enc, $key, \CodeHydrater\services\paragon_crypto\crypto::single_key); echo $dec . "
";; // -------------------- -$key_pair_Alice = \tts\services\paragon_crypto\crypto::make_user_box_kp(); -$secret_Alice = \tts\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Alice); -$public_Alice = \tts\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Alice); +$key_pair_Alice = \CodeHydrater\services\paragon_crypto\crypto::make_user_box_kp(); +$secret_Alice = \CodeHydrater\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Alice); +$public_Alice = \CodeHydrater\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Alice); -$key_pair_Bob = \tts\services\paragon_crypto\crypto::make_user_box_kp(); -$secret_Bob = \tts\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Bob); -$public_Bob = \tts\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Bob); +$key_pair_Bob = \CodeHydrater\services\paragon_crypto\crypto::make_user_box_kp(); +$secret_Bob = \CodeHydrater\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Bob); +$public_Bob = \CodeHydrater\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Bob); -$kA = \tts\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Alice, $public_Bob); -$enc = \tts\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String2...', $kA, \tts\services\paragon_crypto\crypto::multiple_keys); +$kA = \CodeHydrater\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Alice, $public_Bob); +$enc = \CodeHydrater\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String2...', $kA, \CodeHydrater\services\paragon_crypto\crypto::multiple_keys); echo $enc . "
"; -$kB = \tts\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Bob, $public_Alice); -$dec = \tts\services\paragon_crypto\crypto::safe_decrypt($enc, $kB, \tts\services\paragon_crypto\crypto::multiple_keys); +$kB = \CodeHydrater\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Bob, $public_Alice); +$dec = \CodeHydrater\services\paragon_crypto\crypto::safe_decrypt($enc, $kB, \CodeHydrater\services\paragon_crypto\crypto::multiple_keys); echo $dec; */ diff --git a/src/classes/services/paragon_crypto/password_storage.php b/src/classes/services/paragon_crypto/password_storage.php index ee8a899..b68d474 100644 --- a/src/classes/services/paragon_crypto/password_storage.php +++ b/src/classes/services/paragon_crypto/password_storage.php @@ -16,7 +16,7 @@ class password_storage { private $random_engine; public function __construct() { - $this->random_engine = new \tts\random_engine(); + $this->random_engine = new \CodeHydrater\random_engine(); } public function generate_a_key(): string { @@ -120,7 +120,7 @@ class password_storage { } /* -$c = new \tts\services\paragon_crypto\password_storage(); +$c = new \CodeHydrater\services\paragon_crypto\password_storage(); $k = $c->generate_a_key(); $h = $c->hash("HelpMe", $k); var_dump( $c->verify("HelpMe", $h, $k) ); diff --git a/src/classes/services/paragon_crypto/sodium_storage.php b/src/classes/services/paragon_crypto/sodium_storage.php index 5d8d391..ff77311 100644 --- a/src/classes/services/paragon_crypto/sodium_storage.php +++ b/src/classes/services/paragon_crypto/sodium_storage.php @@ -29,7 +29,7 @@ class sodium_storage { * Sets the encryption key */ public function __construct(#[\SensitiveParameter] private string $key) { - $this->random_engine = new \tts\random_engine(); + $this->random_engine = new \CodeHydrater\random_engine(); } public function encrypt(#[\SensitiveParameter] string $plain_text, string $item_name = ""): string { @@ -113,7 +113,7 @@ class sodium_storage { $secretkey = "78a5011b9997cd03a28a3412c66565b7c32715b35e055d7abfc228236308d3b2"; $plain_text = "Hello World!"; -$sc = new \tts\services\paragon_crypto\sodium_storage($secretkey); +$sc = new \CodeHydrater\services\paragon_crypto\sodium_storage($secretkey); $index = "sensitive"; $encoded = $sc->encode($index, $plain_text); setcookie($index, $encoded); diff --git a/src/classes/services/sessions/cookie_session_handler.php b/src/classes/services/sessions/cookie_session_handler.php index 6049e2e..9f75bae 100644 --- a/src/classes/services/sessions/cookie_session_handler.php +++ b/src/classes/services/sessions/cookie_session_handler.php @@ -110,7 +110,7 @@ class cookie_session_handler implements \SessionHandlerInterface { public function write($id, $data): bool { if (headers_sent($file, $line)) { - throw new \CodeHydrater\services\sessions\cookie_sessions_handler_exception("Error headers were already sent by $file on line # $line "); + throw new cookie_sessions_handler_exception("Error headers were already sent by $file on line # $line "); } $data = $this->write_helper($data); /* @@ -119,7 +119,7 @@ class cookie_session_handler implements \SessionHandlerInterface { * as web browsers only support up to 4K of Cookie Data! */ if (strlen($data) > 4000) { - throw new \tts\services\sessions\cookie_sessions_handler_exception("Session data too big (over 4KB)"); + throw new cookie_sessions_handler_exception("Session data too big (over 4KB)"); } $cookie_lifetime = (int) ini_get('session.cookie_lifetime');