diff --git a/htdocs/admin.php b/htdocs/admin.php index b48b8558..3efe4b8a 100644 --- a/htdocs/admin.php +++ b/htdocs/admin.php @@ -1,4 +1,5 @@ messages[] = sprintf('No page found to %s %s%s%s', $action, ($action == 'browse' ? '' : 'a '), $component, ($action == 'browse' ? 's' : '')); + $c->messages[] = sprintf( + 'No page found to %s %s%s%s', + htmlspecialchars($action), + ($action == 'browse' ? '' : 'a '), $component, + ($action == 'browse' ? 's' : '') + ); include('page-header.php'); include('page-footer.php'); @ob_flush(); exit(0); diff --git a/htdocs/always.php b/htdocs/always.php index 3e457bee..ca75b8c2 100644 --- a/htdocs/always.php +++ b/htdocs/always.php @@ -8,6 +8,47 @@ if ( preg_match('{/always.php$}', $_SERVER['SCRIPT_NAME'] ) ) header('Location: index.php'); +// XSS Protection +function filter_post(&$val, $index) { + if(in_array($index, ["newpass1", "newpass2"])) return; + + switch (gettype($val)) { + case "string": + $val = htmlspecialchars($val); + break; + + case "array": + array_walk_recursive($val, function(&$v) { + if (gettype($v) == "string") { + $v = htmlspecialchars($v); + } + }); + break; + } +} + +function clean_get() { + $temp = []; + + foreach($_GET as $key => $value) { + // XSS is possible in both key and values + $k = htmlspecialchars($key); + $v = htmlspecialchars($value); + $temp[$k] = $v; + } + + return $temp; +} + +// Before anything else is executed we filter all the user input, a lot of code in this project +// relies on variables that are easily manipulated by the user. These lines and functions filter all those variables. +if(isset($_POST)) array_walk($_POST, 'filter_post'); +$_GET = clean_get(); +$_SERVER['REQUEST_URI'] = str_replace("&", "&", htmlspecialchars($_SERVER['REQUEST_URI'])); +$_SERVER['HTTP_REFERER'] = htmlspecialchars($_SERVER['HTTP_REFERER']); + + + // Ensure the configuration starts out as an empty object. $c = (object) array(); $c->script_start_time = microtime(true); diff --git a/inc/csrf_tokens.php b/inc/csrf_tokens.php new file mode 100644 index 00000000..c515c269 --- /dev/null +++ b/inc/csrf_tokens.php @@ -0,0 +1,119 @@ +')) { + return session_id() !== ''; + } else { + return session_status() === PHP_SESSION_ACTIVE; + } +} + +/** + * Generate a CSRF token, it chooses from 3 different functions based on PHP version and modules + * @return bool|string + */ +function generateCsrf() { + if (version_compare(phpversion(), '7.0.0', '>=')) { + $random = generateRandom(); + if($random !== false) return $random; + } + + if (function_exists('mcrypt_create_iv')) { + return generateMcrypt(); + } + + return generateOpenssl(); +} + +/** + * Generate a random string using the PHP built in function random_bytes + * @version 7.0.0 + * @return bool|string + */ +function generateRandom() { + try { + return bin2hex(random_bytes(32)); + } catch (Exception $e) { + return false; + } +} + +/** + * Generate a random string using MCRYPT + * @return string + */ +function generateMcrypt() { + return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); +} + +/** + * Generate a random string using OpenSSL + * @return string + */ +function generateOpenssl() { + return bin2hex(openssl_random_pseudo_bytes(32)); +} + +/** + * Checks for session and the existence of a key + * after ensuring both are present it returns the + * current CSRF token + * @return string + */ +function getCsrf() { + if(!sessionExists()) { + session_start(); + } + + if(!array_key_exists('csrf_token', $_SESSION)) { + updateCsrf(); + } + + return $_SESSION['csrf_token']; +} + +/** + * Get a hidden CSRF input field to be used in forms + * @return string + */ +function getCsrfField() { + return sprintf("", getCsrf()); +} + +/** + * Verify a given CSRF token + * @param $csrf_token + * @return bool + */ +function verifyCsrf($csrf_token) { + $current_csrf = getCsrf(); + // Prefer hash_equals over === because the latter is vulnerable to timing attacks + if(function_exists('hash_equals')) { + return hash_equals($current_csrf, $csrf_token); + } + + return false; +} + +/** + * Uses the global $_POST variable to check if the CSRF token is valid + * @return bool + */ +function verifyCsrfPost() { + return (isset($_POST['csrf_token']) && verifyCsrf($_POST['csrf_token'])); +} \ No newline at end of file diff --git a/inc/interactive-page.php b/inc/interactive-page.php index 86c88898..c0e87389 100644 --- a/inc/interactive-page.php +++ b/inc/interactive-page.php @@ -20,6 +20,9 @@ if ( isset($_SERVER['SCRIPT_NAME']) ) { if ( $wiki_help == 'admin' ) { $wiki_help .= '/' . $_GET['t'] . '/' . $_GET['action']; } + + $wiki_help = htmlspecialchars($wiki_help); + $wiki_help = 'w/Help/'.$wiki_help; } diff --git a/inc/ui/collection-edit.php b/inc/ui/collection-edit.php index 4fa778c9..2aebf52c 100644 --- a/inc/ui/collection-edit.php +++ b/inc/ui/collection-edit.php @@ -1,4 +1,5 @@ Value('collection_id'); $template = << $prompt_description: ##description.textarea.78x6## $submit_row +$csrf_field