<?php
// SECURE: File management web application with proper security measures
require_once 'vendor/autoload.php';
class SecureFileManager {
private const UPLOAD_DIR = '/var/www/uploads';
private const BACKUP_DIR = '/var/www/backups';
private const ALLOWED_EXTENSIONS = ['txt', 'pdf', 'jpg', 'png', 'gif', 'doc', 'docx'];
private const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
private const RATE_LIMIT_REQUESTS = 10;
private const RATE_LIMIT_WINDOW = 300; // 5 minutes
private $sessionHandler;
private $logger;
public function __construct() {
$this->sessionHandler = new SecureSessionHandler();
$this->logger = new SecurityLogger();
// Enable secure session handling
$this->sessionHandler->startSecureSession();
// CSRF protection
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$this->validateCSRFToken()) {
throw new SecurityException('CSRF token validation failed');
}
// Rate limiting
if (!$this->checkRateLimit()) {
throw new SecurityException('Rate limit exceeded');
}
}
public function handleRequest() {
$action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_STRING) ??
filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING) ?? '';
// Action allowlist
$allowedActions = ['backup', 'compress', 'analyze', 'network_test'];
if (!in_array($action, $allowedActions, true)) {
throw new InvalidArgumentException('Invalid action specified');
}
// Log all requests for security monitoring
$this->logger->logRequest($action, $_SERVER['REMOTE_ADDR']);
try {
switch ($action) {
case 'backup':
$filename = filter_input(INPUT_POST, 'filename', FILTER_SANITIZE_STRING);
return $this->backupFileSafe($filename);
case 'compress':
$directory = filter_input(INPUT_POST, 'directory', FILTER_SANITIZE_STRING);
$format = filter_input(INPUT_POST, 'format', FILTER_SANITIZE_STRING);
return $this->compressDirectorySafe($directory, $format);
case 'analyze':
$filepath = filter_input(INPUT_POST, 'filepath', FILTER_SANITIZE_STRING);
return $this->analyzeFileSafe($filepath);
case 'network_test':
$host = filter_input(INPUT_POST, 'host', FILTER_SANITIZE_STRING);
$port = filter_input(INPUT_POST, 'port', FILTER_VALIDATE_INT);
return $this->testNetworkConnectivitySafe($host, $port);
}
} catch (Exception $e) {
$this->logger->logError($action, $e->getMessage(), $_SERVER['REMOTE_ADDR']);
throw $e;
}
}
// SECURE: File backup using PHP functions and proper validation
private function backupFileSafe($filename) {
// Comprehensive input validation
$this->validateFilename($filename);
$sourceFile = realpath(self::UPLOAD_DIR . '/' . $filename);
// Ensure file exists and is in allowed directory
if (!$sourceFile || !$this->isInAllowedDirectory($sourceFile, self::UPLOAD_DIR)) {
throw new InvalidArgumentException('File not found or access denied');
}
// Check file extension
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($extension, self::ALLOWED_EXTENSIONS, true)) {
throw new InvalidArgumentException('File type not allowed');
}
// Check file size
if (filesize($sourceFile) > self::MAX_FILE_SIZE) {
throw new InvalidArgumentException('File too large');
}
// SECURE: Use PHP's copy() function instead of system commands
$backupFile = self::BACKUP_DIR . '/' . $filename . '.bak.' . date('Y-m-d-H-i-s');
// Ensure backup directory exists
if (!is_dir(self::BACKUP_DIR) && !mkdir(self::BACKUP_DIR, 0755, true)) {
throw new RuntimeException('Cannot create backup directory');
}
if (!copy($sourceFile, $backupFile)) {
throw new RuntimeException('Backup operation failed');
}
// Verify backup integrity
if (md5_file($sourceFile) !== md5_file($backupFile)) {
unlink($backupFile);
throw new RuntimeException('Backup integrity check failed');
}
$this->logger->logSuccess('backup', $filename);
return [
'success' => true,
'message' => 'File backed up successfully',
'backup_file' => basename($backupFile),
'original_size' => filesize($sourceFile),
'backup_size' => filesize($backupFile)
];
}
// SECURE: Directory compression using PHP's ZipArchive
private function compressDirectorySafe($directory, $format) {
$this->validateDirectoryPath($directory);
// Validate compression format
$allowedFormats = ['zip'];
if (!in_array($format, $allowedFormats, true)) {
throw new InvalidArgumentException('Compression format not supported');
}
$sourceDir = realpath(self::UPLOAD_DIR . '/' . $directory);
if (!$sourceDir || !$this->isInAllowedDirectory($sourceDir, self::UPLOAD_DIR)) {
throw new InvalidArgumentException('Directory not found or access denied');
}
if (!is_dir($sourceDir)) {
throw new InvalidArgumentException('Path is not a directory');
}
// SECURE: Use PHP's ZipArchive instead of system commands
$outputFile = self::BACKUP_DIR . '/' . basename($directory) . '_' . date('Y-m-d-H-i-s') . '.zip';
$zip = new ZipArchive();
if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
throw new RuntimeException('Cannot create zip archive');
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($sourceDir) + 1);
// Additional security check
if ($this->isSafeFileForCompression($filePath)) {
$zip->addFile($filePath, $relativePath);
}
}
}
$fileCount = $zip->numFiles;
$zip->close();
$this->logger->logSuccess('compress', $directory);
return [
'success' => true,
'message' => 'Directory compressed successfully',
'archive_file' => basename($outputFile),
'files_compressed' => $fileCount,
'archive_size' => filesize($outputFile)
];
}
// SECURE: File analysis using PHP functions
private function analyzeFileSafe($filepath) {
$this->validateFilepath($filepath);
$realPath = realpath(self::UPLOAD_DIR . '/' . $filepath);
if (!$realPath || !$this->isInAllowedDirectory($realPath, self::UPLOAD_DIR)) {
throw new InvalidArgumentException('File not found or access denied');
}
if (!is_file($realPath)) {
throw new InvalidArgumentException('Path is not a file');
}
// SECURE: Use PHP functions instead of system commands
$fileInfo = [
'filename' => basename($realPath),
'size' => filesize($realPath),
'size_human' => $this->formatBytes(filesize($realPath)),
'mime_type' => mime_content_type($realPath),
'extension' => strtolower(pathinfo($realPath, PATHINFO_EXTENSION)),
'permissions' => substr(sprintf('%o', fileperms($realPath)), -4),
'last_modified' => date('Y-m-d H:i:s', filemtime($realPath)),
'is_readable' => is_readable($realPath),
'is_writable' => is_writable($realPath),
'md5_hash' => md5_file($realPath)
];
// Additional security analysis
$fileInfo['is_executable'] = is_executable($realPath);
$fileInfo['has_suspicious_content'] = $this->scanForSuspiciousContent($realPath);
$this->logger->logSuccess('analyze', $filepath);
return [
'success' => true,
'message' => 'File analyzed successfully',
'file_info' => $fileInfo
];
}
// SECURE: Network connectivity test using PHP sockets
private function testNetworkConnectivitySafe($host, $port) {
// Validate hostname
if (!$this->isValidHostname($host)) {
throw new InvalidArgumentException('Invalid hostname format');
}
// Validate port range
if ($port < 1 || $port > 65535) {
throw new InvalidArgumentException('Port must be between 1 and 65535');
}
// Restrict to common service ports for security
$allowedPorts = [21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995];
if (!in_array($port, $allowedPorts, true)) {
throw new InvalidArgumentException('Port not in allowed list');
}
// SECURE: Use PHP's fsockopen instead of system commands
$startTime = microtime(true);
$socket = @fsockopen($host, $port, $errno, $errstr, 5); // 5 second timeout
$endTime = microtime(true);
$result = [
'host' => $host,
'port' => $port,
'response_time' => round(($endTime - $startTime) * 1000, 2) . ' ms'
];
if ($socket) {
fclose($socket);
$result['status'] = 'open';
$result['message'] = 'Connection successful';
} else {
$result['status'] = 'closed';
$result['message'] = "Connection failed: $errstr ($errno)";
}
$this->logger->logSuccess('network_test', "$host:$port");
return [
'success' => true,
'message' => 'Network test completed',
'test_result' => $result
];
}
// Helper validation methods
private function validateFilename($filename) {
if (empty($filename) || !is_string($filename)) {
throw new InvalidArgumentException('Invalid filename');
}
if (strlen($filename) > 255) {
throw new InvalidArgumentException('Filename too long');
}
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $filename)) {
throw new InvalidArgumentException('Filename contains invalid characters');
}
if (strpos($filename, '..') !== false || $filename[0] === '.') {
throw new InvalidArgumentException('Invalid filename format');
}
}
private function validateDirectoryPath($directory) {
if (empty($directory) || !is_string($directory)) {
throw new InvalidArgumentException('Invalid directory path');
}
if (!preg_match('/^[a-zA-Z0-9._/-]+$/', $directory)) {
throw new InvalidArgumentException('Directory path contains invalid characters');
}
if (strpos($directory, '..') !== false) {
throw new InvalidArgumentException('Directory traversal not allowed');
}
}
private function validateFilepath($filepath) {
if (empty($filepath) || !is_string($filepath)) {
throw new InvalidArgumentException('Invalid file path');
}
if (strpos($filepath, '..') !== false) {
throw new InvalidArgumentException('Path traversal not allowed');
}
if (!preg_match('/^[a-zA-Z0-9._\/-]+$/', $filepath)) {
throw new InvalidArgumentException('File path contains invalid characters');
}
}
private function isValidHostname($hostname) {
if (empty($hostname) || strlen($hostname) > 253) {
return false;
}
// Check for valid hostname format
if (!preg_match('/^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/', $hostname)) {
return false;
}
// Additional validation using filter_var
return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false;
}
private function isInAllowedDirectory($path, $allowedDir) {
$realAllowedDir = realpath($allowedDir);
return $realAllowedDir && strpos($path, $realAllowedDir) === 0;
}
private function isSafeFileForCompression($filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
return in_array($extension, self::ALLOWED_EXTENSIONS, true);
}
private function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, $precision) . ' ' . $units[$i];
}
private function scanForSuspiciousContent($filePath) {
$suspiciousPatterns = [
'/(?:system|exec|shell_exec|passthru|eval)\s*\(/i',
'/(?:<\?php|<script)/i',
'/(?:rm\s+-rf|\/bin\/sh|nc\s+-e)/i'
];
$content = file_get_contents($filePath, false, null, 0, 8192); // Read first 8KB
foreach ($suspiciousPatterns as $pattern) {
if (preg_match($pattern, $content)) {
return true;
}
}
return false;
}
private function validateCSRFToken() {
$token = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['csrf_token'] ?? '';
return !empty($token) && !empty($sessionToken) && hash_equals($sessionToken, $token);
}
private function checkRateLimit() {
$clientIP = $_SERVER['REMOTE_ADDR'];
$key = 'rate_limit_' . md5($clientIP);
$requests = $_SESSION[$key] ?? [];
$now = time();
// Remove old requests outside the window
$requests = array_filter($requests, function($timestamp) use ($now) {
return ($now - $timestamp) < self::RATE_LIMIT_WINDOW;
});
if (count($requests) >= self::RATE_LIMIT_REQUESTS) {
return false;
}
$requests[] = $now;
$_SESSION[$key] = $requests;
return true;
}
}
// Supporting security classes
class SecurityLogger {
private $logFile = '/var/log/filemanager_security.log';
public function logRequest($action, $ip) {
$this->writeLog('REQUEST', "Action: $action, IP: $ip");
}
public function logSuccess($action, $target) {
$this->writeLog('SUCCESS', "Action: $action, Target: $target");
}
public function logError($action, $error, $ip) {
$this->writeLog('ERROR', "Action: $action, Error: $error, IP: $ip");
}
private function writeLog($level, $message) {
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[$timestamp] [$level] $message\n";
file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
}
}
class SecureSessionHandler {
public function startSecureSession() {
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_strict_mode', 1);
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
}
}
class SecurityException extends Exception {}
// Usage with proper error handling and security headers
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Content-Type: application/json');
try {
$fileManager = new SecureFileManager();
$result = $fileManager->handleRequest();
echo json_encode($result);
} catch (SecurityException $e) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Security violation']);
} catch (InvalidArgumentException $e) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Internal server error']);
}
?>