Compare commits
15 Commits
087b4a128e
...
95537fb60e
Author | SHA1 | Date | |
---|---|---|---|
95537fb60e
|
|||
5130477e4b
|
|||
ae39bdf741
|
|||
d7c89275e7 | |||
2bfc7a0dcd
|
|||
6a6f71cf10
|
|||
8e3d97aa7f
|
|||
aa67de1e54
|
|||
50d3a20b01
|
|||
cb8c786284
|
|||
306efa3769
|
|||
1c86051997
|
|||
2f68acdfc1
|
|||
43bc39d425
|
|||
711bd49048
|
32
.git.template
Normal file
32
.git.template
Normal file
@ -0,0 +1,32 @@
|
||||
# If applied, this commit will ...
|
||||
|
||||
# Why was this change necessary? Improvements brought about by the
|
||||
# change.
|
||||
|
||||
# End
|
||||
|
||||
# Format
|
||||
# --------------------
|
||||
# (If applied, this commit will...) <subject> (Max 72 char)
|
||||
# |<---- Preferably using up to 50 chars --->|<------------------->|
|
||||
# Example:
|
||||
# Implement automated commit messages
|
||||
|
||||
# (Optional) Explain why this change is being made
|
||||
# |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->|
|
||||
|
||||
# (Optional) Provide links or keys to any relevant tickets, articles or other resources
|
||||
# Example: Github issue #23
|
||||
|
||||
# --- COMMIT END ---
|
||||
#
|
||||
# Remember to:
|
||||
# * Capitalize the subject line
|
||||
# * Use the imperative mood in the subject line
|
||||
# * Do not end the subject line with a period
|
||||
# * Separate subject from body with a blank line
|
||||
# * Use the body to explain what and why vs. how
|
||||
# * Can use multiple lines with "-" or "*" for bullet points in body
|
||||
# --------------------
|
||||
|
||||
# Continuous integration messages
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.php text eol=lf diff=php
|
||||
*.css text eol=lf diff=css
|
260
MD_JAIL.php
Normal file
260
MD_JAIL.php
Normal file
@ -0,0 +1,260 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Provides class MD_JAIL.
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* A class that, once initialized, forces the programmer to make security instructions implicit.
|
||||
* If an object of the class has been created, not specifying security instructions
|
||||
* leads to an error.
|
||||
* A restriction on basic file operations is not practical in an md context because of
|
||||
* the way transations are loaded through MDTlLoader.
|
||||
*/
|
||||
final class MD_JAIL {
|
||||
|
||||
const STATUS_NONE = 0;
|
||||
const STATUS_STARTED = 1;
|
||||
const STATUS_SPECIFIED = 2; // Determines that everything is fine.
|
||||
|
||||
/** @var integer */
|
||||
private int $_status = self::STATUS_NONE;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* Maximum execution time in seconds.
|
||||
*/
|
||||
public int $max_execution_time;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* Specifies which paths may be used by this script.
|
||||
*/
|
||||
private array $_open_basedir = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* Specifies the maximum RAM the script may use.
|
||||
*/
|
||||
public string $memory_limit;
|
||||
|
||||
/**
|
||||
* Static function providing an advisory on how to harden the php.ini or
|
||||
* .user.ini.
|
||||
*
|
||||
* @param array{shell_access_whitelist: string[], sys_function_whitelist: string[], file_function_whitelist: string[], file_uploads: bool, allow_url_fopen: bool, max_input_vars: integer, max_input_nesting_level: integer, post_max_size: string, curl: bool} $requested_resources Requested resources.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function check_server_setup(array $requested_resources):string {
|
||||
|
||||
# ini_set("open_basedir", __DIR__ . "/../../");
|
||||
|
||||
$disable_functions = [
|
||||
"dl", # Loads a PHP extension at runtime
|
||||
"opcache_get_status", # Gets opcache status
|
||||
"phpinfo", # For obvious reasons
|
||||
"parse_ini_file", # For obvious reasons
|
||||
"show_source", "highlight_file", # Aliases; serve the content of a PHP file
|
||||
"php_uname", # Returns information about the operating system PHP is running on
|
||||
"phpcredits", # Credits page of PHP authors
|
||||
"php_strip_whitespace", # Return PHP source with stripped comments and whitespace
|
||||
"popen", "pclose", # Similar to fopen, but capable of shell access
|
||||
"virtual", # Perform an Apache sub-request
|
||||
];
|
||||
|
||||
$disable_aliases = [
|
||||
"ini_set" => "ini_alter", # Alias of ini_set(), ini_set is preferred
|
||||
"disk_free_space" => "diskfreespace", # Alias of disk_free_space()
|
||||
"PHP_SAPI" => "php_sapi_name", # Alias of constant PHP_SAPI
|
||||
"stream_set_write_buffer" => "set_file_buffer", # Alias of stream_set_write_buffer()
|
||||
];
|
||||
|
||||
$shell_access_functions = array_diff(["exec", "passthru", "proc_close", "proc_get_status", "proc_nice", "proc_open", "proc_terminate", "shell_exec", "system"], $requested_resources['shell_access_whitelist']);
|
||||
$sys_functions = array_diff(["get_current_user", "getmyuid", "getmygid", "getmypid", "getmyinode", "getlastmod", "getenv", "putenv"], $requested_resources['sys_function_whitelist']);
|
||||
$file_functions = array_diff(["chgrp", "chgrp", "lchgrp", "lchown", "link", "linkinfo", "symlink"], $requested_resources['file_function_whitelist']);
|
||||
|
||||
if ($requested_resources['curl'] === false) {
|
||||
$disable_functions[] = "curl_init";
|
||||
$disable_functions[] = "curl_exec";
|
||||
$disable_functions[] = "curl_multi_init";
|
||||
$disable_functions[] = "curl_multi_exec";
|
||||
}
|
||||
|
||||
$disabledWOAliases = array_merge($disable_functions, $shell_access_functions, $sys_functions, $file_functions);
|
||||
|
||||
$output = '## php.ini' . PHP_EOL . PHP_EOL;
|
||||
$output .= PHP_EOL . "php_value[disable_functions] = \"" . implode(", ", array_merge($disabledWOAliases, $disable_aliases)) . "\"";
|
||||
|
||||
if ($requested_resources['file_uploads'] === false) {
|
||||
$output .= PHP_EOL . "php_value[file_uploads] = 0";
|
||||
}
|
||||
if ($requested_resources['allow_url_fopen'] === false) {
|
||||
$output .= PHP_EOL . "php_value[allow_url_fopen] = 0";
|
||||
}
|
||||
|
||||
$output .= PHP_EOL . PHP_EOL . '## .user.ini' . PHP_EOL;
|
||||
|
||||
if ($requested_resources['file_uploads'] === false) {
|
||||
$output .= PHP_EOL . "upload_max_filesize = 1";
|
||||
}
|
||||
if ($requested_resources['max_input_vars'] != ini_get("max_input_vars")) {
|
||||
$output .= PHP_EOL . "max_input_vars = " . $requested_resources['max_input_vars'];
|
||||
}
|
||||
if ($requested_resources['max_input_nesting_level'] != ini_get("max_input_nesting_level")) {
|
||||
$output .= PHP_EOL . "max_input_nesting_level = " . $requested_resources['max_input_nesting_level'];
|
||||
}
|
||||
if ($requested_resources['post_max_size'] != ini_get("post_max_size")) {
|
||||
$output .= PHP_EOL . "post_max_size = " . $requested_resources['post_max_size'];
|
||||
}
|
||||
|
||||
$output .= PHP_EOL . PHP_EOL . '## PHPStan Directives' . PHP_EOL . PHP_EOL . " disallowedFunctionCalls:" . PHP_EOL;
|
||||
foreach ($disable_aliases as $to_keep => $disabled) {
|
||||
|
||||
$output .= '
|
||||
-
|
||||
function: \'' . $disabled . '()\'
|
||||
message: \'use ' . $to_keep . ' instead\'';
|
||||
|
||||
}
|
||||
foreach ($disabledWOAliases as $disabled) {
|
||||
|
||||
$output .= '
|
||||
- function: \'' . $disabled . '()\'';
|
||||
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an additional accessible directory for open_basedir.
|
||||
*
|
||||
* @param string $dir Directory to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_accessible_dir(string $dir):void {
|
||||
|
||||
$this->_open_basedir[] = $dir;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the memory limit setting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _apply_memory_limit():void {
|
||||
|
||||
if (!isset($this->memory_limit)) {
|
||||
throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->memory_limit = string.");
|
||||
}
|
||||
if (ini_set("memory_limit", $this->memory_limit) === false) {
|
||||
throw new Exception('Failed to change memory_limit to ' . $this->memory_limit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the maximum execution time setting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _apply_time_limit():void {
|
||||
|
||||
if (!isset($this->max_execution_time)) {
|
||||
throw new MDJailSecurityOptionNotSetException("It has not been specified, which maximum execution time the script should hold. Set MD_JAIL->max_execution_time = integer.");
|
||||
}
|
||||
if (set_time_limit($this->max_execution_time) === false) {
|
||||
throw new Exception('Failed to change max_execution_time to ' . $this->max_execution_time);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies basedir restrictions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _apply_basedir_restriction():void {
|
||||
|
||||
if (empty($this->_open_basedir)) {
|
||||
throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->open_basedir = string.");
|
||||
}
|
||||
if (ini_set("open_basedir", implode(':', $this->_open_basedir)) === false) {
|
||||
throw new Exception('Failed to set open_basedir restrictions');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces security options previously set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enforce():void {
|
||||
|
||||
// Special instructions on CLI, so as to not disturb PHPUnit
|
||||
if (PHP_SAPI === 'cli') {
|
||||
|
||||
if (!isset($this->memory_limit)) {
|
||||
throw new MDJailSecurityOptionNotSetException("It has not been specified, which memory limit the script should hold. Set MD_JAIL->memory_limit = string.");
|
||||
}
|
||||
if (!isset($this->max_execution_time)) {
|
||||
throw new MDJailSecurityOptionNotSetException("It has not been specified, which maximum execution time the script should hold. Set MD_JAIL->max_execution_time = integer.");
|
||||
}
|
||||
|
||||
$this->_status = self::STATUS_SPECIFIED;
|
||||
$this->__destruct();
|
||||
}
|
||||
|
||||
$this->_apply_memory_limit();
|
||||
$this->_apply_time_limit();
|
||||
|
||||
// Set accessible file paths
|
||||
$this->_apply_basedir_restriction();
|
||||
|
||||
$this->_status = self::STATUS_SPECIFIED;
|
||||
$this->__destruct();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup function. Registers a shutdown function that throws an error
|
||||
* if the security specifications have not been made.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->_status = self::STATUS_STARTED;
|
||||
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
|
||||
if ($this->_status !== self::STATUS_SPECIFIED) {
|
||||
|
||||
echo "Security specifications need to be set.";
|
||||
|
||||
if (!isset($this->memory_limit)) {
|
||||
echo "Set memory limit";
|
||||
}
|
||||
|
||||
if (!isset($this->max_execution_time)) {
|
||||
echo "Set max_execution_time";
|
||||
}
|
||||
|
||||
if (empty($this->_open_basedir)) {
|
||||
echo "Set open_basedir";
|
||||
}
|
||||
|
||||
throw new MDJailSecurityOptionNotSetException("Security specifications need to be set.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
94
MD_STD.php
94
MD_STD.php
@ -63,7 +63,13 @@ final class MD_STD {
|
||||
throw new MDFileDoesNotExist("There is no file {$filepath}");
|
||||
}
|
||||
|
||||
return \array_values(\array_diff($output, ['.', '..', '.git']));
|
||||
// Remove unwanted files from list
|
||||
$output = \array_diff($output, ['.', '..', '.git']);
|
||||
|
||||
// Return array values, to make it a list rather than an associative array.
|
||||
// This should be done in a separate line, as it observably leads to a
|
||||
// significant reduction in the used RAM.
|
||||
return \array_values($output);
|
||||
|
||||
}
|
||||
|
||||
@ -340,4 +346,90 @@ final class MD_STD {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for minimizing HTML, trimming each line.
|
||||
*
|
||||
* @param string $input Input.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minimizeHTMLString(string $input):string {
|
||||
|
||||
$input = \explode(PHP_EOL, $input);
|
||||
$output = "";
|
||||
foreach ($input as $line) $output .= \trim($line) . PHP_EOL;
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function cuts down a string and adds a period in case it's longer
|
||||
* than length to create a snippet.
|
||||
*
|
||||
* @param string $string Input text to cut down.
|
||||
* @param integer $length Length of the snippet to create.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function createTextSnippet(string $string, int $length = 180):string {
|
||||
|
||||
if (\mb_strlen($string) > $length) {
|
||||
$string = \mb_substr($string, 0, $length);
|
||||
if (($lastWhitespace = \mb_strrpos($string, ' ')) !== false) {
|
||||
$string = \mb_substr($string, 0, $lastWhitespace);
|
||||
}
|
||||
$string .= '...';
|
||||
}
|
||||
return $string;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the finfo functions to get the mime content type of a file.
|
||||
*
|
||||
* @param string $filepath Expected file path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function mime_content_type(string $filepath):string {
|
||||
|
||||
if (!($finfo = \finfo_open(FILEINFO_MIME_TYPE))) {
|
||||
throw new Exception("Cannot open finfo context");
|
||||
}
|
||||
if (!($mime_type = finfo_file($finfo, $filepath))) {
|
||||
throw new MDWrongFileType("Cannot get mime type of file: " . basename($filepath));
|
||||
}
|
||||
\finfo_close($finfo);
|
||||
|
||||
return $mime_type;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file exists, with one of the expected mime types.
|
||||
*
|
||||
* @param string $filepath File path of the file that needs to exist.
|
||||
* @param string[] $accepted_mimetype Mime type the file should have.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ensure_file(string $filepath, array $accepted_mimetype = []) {
|
||||
|
||||
if (!\file_exists($filepath)) {
|
||||
throw new MDFileDoesNotExist("File " . basename($filepath) . " does not exist");
|
||||
}
|
||||
|
||||
// Check for mime type follows. If no check is to be done, ignore this.
|
||||
if (empty($accepted_mimetype)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mime_type = self::mime_content_type($filepath);
|
||||
|
||||
if (!\in_array($mime_type, $accepted_mimetype, true)) {
|
||||
throw new MDWrongFileType("Incorrect mime type of file " . \basename($filepath) . ". Mime type is " . \mime_content_type($filepath) . ", accepted any of ['" . \implode("', '", $accepted_mimetype) . "']");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ final class MD_STD_IN {
|
||||
else $output = self::sanitize_text($default);
|
||||
|
||||
if (!empty($allowed) and !\in_array($output, $allowed, true)) {
|
||||
Throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: " . implode(', ', $allowed));
|
||||
Throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: '" . implode('\', \'', $allowed) . "'");
|
||||
}
|
||||
|
||||
return $output;
|
||||
@ -132,7 +132,7 @@ final class MD_STD_IN {
|
||||
else $output = self::sanitize_text($default);
|
||||
|
||||
if (!empty($allowed) and !\in_array($output, $allowed, true)) {
|
||||
Throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: " . implode(', ', $allowed));
|
||||
Throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: '" . implode('\', \'', $allowed) . "'");
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
48
MD_STD_SEC.php
Normal file
48
MD_STD_SEC.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Gathers wrappers for handling basic security operations.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
$_SESSION['csrf-token'] = null; unset($_SESSION['csrf-token']);
|
||||
|
||||
return $validity;
|
||||
|
||||
}
|
||||
|
||||
}
|
21
exceptions/MDJailSecurityOptionNotSetException.php
Normal file
21
exceptions/MDJailSecurityOptionNotSetException.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?PHP
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Exception thrown by MDJail if a required security option has not been set.
|
||||
*/
|
||||
final class MDJailSecurityOptionNotSetException extends Exception {
|
||||
|
||||
/**
|
||||
* Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorMessage() {
|
||||
//error message
|
||||
$errorMsg = 'A security option of MD_JAIL has not been set: <b>' . $this->getMessage() . '</b>).';
|
||||
return $errorMsg;
|
||||
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user