diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8776284 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "fedir/hoptransfert", + "description": "A minimalist, secure PHP application for anonymous file sharing", + "type": "project", + "authors": [ + { + "name": "Fedir RYKHTIK", + "email": "fedir@users.noreply.github.com" + } + ], + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "autoload": { + "files": [ + "index.php" + ] + }, + "scripts": { + "test": "phpunit", + "test-coverage": "phpunit --coverage-html coverage" + } +} \ No newline at end of file diff --git a/index.php b/index.php index 81729cf..8fe3f1b 100644 --- a/index.php +++ b/index.php @@ -29,6 +29,7 @@ // Security const PASSWORD_MIN_LENGTH = 6; const HASH_SALT = 'your-secret-salt-here'; // change this +const CSRF_TOKEN_LENGTH = 16; // Ressources control const MAX_LOG_LINES = 5; // prevent log bloat @@ -92,6 +93,49 @@ function sanitize_input($data) { return htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8'); } +/** + * Generate CSRF token + */ +function generate_csrf_token() { + if (session_status() === PHP_SESSION_NONE) { + // Configure secure session settings + ini_set('session.cookie_httponly', 1); + ini_set('session.cookie_secure', 1); + ini_set('session.cookie_samesite', 'Strict'); + session_start(); + } + if (!isset($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(CSRF_TOKEN_LENGTH)); + } + return $_SESSION['csrf_token']; +} + +/** + * Validate CSRF token + */ +function validate_csrf_token($token) { + if (session_status() === PHP_SESSION_NONE) { + // Configure secure session settings + ini_set('session.cookie_httponly', 1); + ini_set('session.cookie_secure', 1); + ini_set('session.cookie_samesite', 'Strict'); + session_start(); + } + return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); +} + +/** + * Sanitize filename for headers to prevent HTTP Response Splitting + */ +function sanitize_filename_for_header($filename) { + // Remove any control characters and limit to ASCII printable chars + $filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $filename); + // Remove quotes and backslashes to prevent header injection + $filename = str_replace(['"', '\\', "\r", "\n"], '', $filename); + // Limit length to prevent excessively long headers + return mb_substr($filename, 0, 255); +} + /** * Generate a UUID v4 */ @@ -230,6 +274,12 @@ function display_success($message) { * Render HTML page */ function render_page($title, $content) { + // Set security headers + header('X-Content-Type-Options: nosniff'); + header('X-Frame-Options: DENY'); + header('X-XSS-Protection: 1; mode=block'); + header('Content-Security-Policy: default-src \'self\' cdn.tailwindcss.com; script-src \'self\' cdn.tailwindcss.com; style-src \'self\' \'unsafe-inline\' cdn.tailwindcss.com; img-src \'self\' data:; connect-src \'self\''); + $base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']); return << @@ -388,6 +445,7 @@ function show_download_form($uuid) {