/
home
/
suroeste
/
public_html
/
payments.transportessuroeste.com
/
src
/
Middleware
/
/home/suroeste/public_html/payments.transportessuroeste.com/src/Middleware
mkdir
upload
Name
Size
Mode
Actions
AuthMiddleware.php
5521
0644
edit
dl
rm
RateLimiter.php
6723
0644
edit
dl
rm
SecurityMiddleware.php
9705
0644
edit
dl
rm
Edit:
/home/suroeste/public_html/payments.transportessuroeste.com/src/Middleware/SecurityMiddleware.php
(9705B)
<?php /** * ============================================================================ * SECURITY MIDDLEWARE - Protección de la API * ============================================================================ */ namespace TransportesSuroeste\Middleware; use TransportesSuroeste\Services\LogService; use TransportesSuroeste\Exceptions\SecurityException; class SecurityMiddleware { private static ?string $requestBody = null; /** * Get the raw request body, cached to avoid reading php://input multiple times */ private static function getRequestBody(): string { if (self::$requestBody === null) { self::$requestBody = file_get_contents('php://input') ?: ''; } return self::$requestBody; } /** * Aplicar todas las protecciones de seguridad */ public function apply(): void { $this->setSecurityHeaders(); $this->validateRequest(); $this->detectSuspiciousPatterns(); } /** * Establecer headers de seguridad */ private function setSecurityHeaders(): void { // Prevenir clickjacking header('X-Frame-Options: DENY'); // Prevenir MIME sniffing header('X-Content-Type-Options: nosniff'); // XSS Protection header('X-XSS-Protection: 1; mode=block'); // Referrer Policy header('Referrer-Policy: strict-origin-when-cross-origin'); // Content Security Policy header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://checkout.epayco.co; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.epayco.co https://checkout.epayco.co;"); // Strict Transport Security (solo en producción con HTTPS) if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); } // Cache control para APIs header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); // Tipo de contenido header('Content-Type: application/json; charset=utf-8'); } /** * Validar la petición */ private function validateRequest(): void { // Validar método HTTP $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']; if (!in_array($_SERVER['REQUEST_METHOD'], $allowedMethods)) { throw new SecurityException('Método HTTP no permitido', 405); } // Validar Content-Type para POST/PUT if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT'])) { $contentType = $_SERVER['CONTENT_TYPE'] ?? ''; // Permitir form-data para callbacks de ePayco $isFormData = strpos($contentType, 'application/x-www-form-urlencoded') !== false || strpos($contentType, 'multipart/form-data') !== false; $isJson = strpos($contentType, 'application/json') !== false; // Solo validar si hay body $body = self::getRequestBody(); if (!empty($body) && !$isJson && !$isFormData) { // Permitir si es callback de ePayco (sin Content-Type correcto a veces) $uri = $_SERVER['REQUEST_URI'] ?? ''; if (strpos($uri, 'callback') === false && strpos($uri, 'webhook') === false) { throw new SecurityException('Content-Type debe ser application/json', 415); } } } // Validar tamaño del body $contentLength = $_SERVER['CONTENT_LENGTH'] ?? 0; $maxSize = 1024 * 1024; // 1MB if ($contentLength > $maxSize) { throw new SecurityException('Payload demasiado grande', 413); } } /** * Detectar patrones sospechosos */ private function detectSuspiciousPatterns(): void { $uri = $_SERVER['REQUEST_URI'] ?? ''; $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $body = self::getRequestBody(); // Patrones de SQL Injection $sqlPatterns = [ '/(\bunion\b.*\bselect\b)/i', '/(\bselect\b.*\bfrom\b)/i', '/(\binsert\b.*\binto\b)/i', '/(\bdelete\b.*\bfrom\b)/i', '/(\bdrop\b.*\btable\b)/i', '/(\bexec\b|\bexecute\b)/i', '/(\'|\").*(--)/', '/(\bor\b|\band\b).*(\b1\b\s*=\s*\b1\b)/i' ]; foreach ($sqlPatterns as $pattern) { if (preg_match($pattern, $uri) || preg_match($pattern, $body)) { LogService::security('sql_injection_attempt', 'Posible SQL Injection detectado', [ 'uri' => $uri, 'ip' => LogService::getClientIp() ]); throw new SecurityException('Solicitud bloqueada por seguridad', 403); } } // Patrones de XSS $xssPatterns = [ '/<script\b[^>]*>/i', '/javascript:/i', '/on\w+\s*=/i', '/<iframe/i', '/<object/i', '/<embed/i' ]; foreach ($xssPatterns as $pattern) { if (preg_match($pattern, $uri) || preg_match($pattern, $body)) { LogService::security('xss_attempt', 'Posible XSS detectado', [ 'uri' => $uri, 'ip' => LogService::getClientIp() ]); throw new SecurityException('Solicitud bloqueada por seguridad', 403); } } // Detectar bots maliciosos $badBots = ['sqlmap', 'nikto', 'nessus', 'nmap', 'masscan', 'zgrab']; foreach ($badBots as $bot) { if (stripos($userAgent, $bot) !== false) { LogService::security('malicious_bot', 'Bot malicioso detectado', [ 'user_agent' => $userAgent, 'ip' => LogService::getClientIp() ]); throw new SecurityException('Acceso denegado', 403); } } // Detectar path traversal if (preg_match('/\.\.\/|\.\.\\\\/', $uri)) { LogService::security('path_traversal', 'Path traversal detectado', [ 'uri' => $uri, 'ip' => LogService::getClientIp() ]); throw new SecurityException('Solicitud bloqueada por seguridad', 403); } } /** * Sanitizar array de datos */ public static function sanitizeArray(array $data): array { $sanitized = []; foreach ($data as $key => $value) { // Sanitizar key $key = preg_replace('/[^a-zA-Z0-9_]/', '', $key); if (is_array($value)) { $sanitized[$key] = self::sanitizeArray($value); } elseif (is_string($value)) { $sanitized[$key] = self::sanitizeString($value); } else { $sanitized[$key] = $value; } } return $sanitized; } /** * Sanitizar string para almacenamiento * NOTA: NO aplica htmlspecialchars aqui. El escape HTML se hace en output (vistas). * Aqui solo se eliminan caracteres peligrosos para almacenamiento seguro. */ public static function sanitizeString(string $value): string { // Remover caracteres null $value = str_replace(chr(0), '', $value); // Remover caracteres de control excepto newline y tab $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value); // Limitar longitud if (mb_strlen($value, 'UTF-8') > 10000) { $value = mb_substr($value, 0, 10000, 'UTF-8'); } return trim($value); } /** * Escapar string para output HTML (usar en vistas) */ public static function escapeHtml(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** * Validar y sanitizar email */ public static function sanitizeEmail(string $email): ?string { $email = filter_var(trim($email), FILTER_SANITIZE_EMAIL); return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : null; } /** * Validar y sanitizar número de documento */ public static function sanitizeDocument(string $document): string { return preg_replace('/[^0-9a-zA-Z]/', '', $document); } /** * Validar y sanitizar teléfono */ public static function sanitizePhone(string $phone): string { return preg_replace('/[^0-9+]/', '', $phone); } /** * Generar token CSRF */ public static function generateCsrfToken(): string { if (session_status() === PHP_SESSION_NONE) { session_start(); } $token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $token; $_SESSION['csrf_time'] = time(); return $token; } /** * Validar token CSRF */ public static function validateCsrfToken(string $token): bool { if (session_status() === PHP_SESSION_NONE) { session_start(); } if (!isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_time'])) { return false; } // Token expira en 1 hora if (time() - $_SESSION['csrf_time'] > 3600) { return false; } return hash_equals($_SESSION['csrf_token'], $token); } }
Save
cmd:
run