Endurecimiento de una escuela de idiomas con PHP en hosting compartido
Una escuela de idiomas vende cursos en línea con PHP 8.1 y MySQL en un hosting compartido. Tiene 4.200 cuentas. El login usa contraseñas con MD5 sin sal; la cookie de sesión se llama PHPSESSID y se emite sin HttpOnly, Secure ni SameSite. El buscador del catálogo refleja el parámetro q en la página sin escapar y el formulario de contacto envía un correo con los datos. El panel administrativo está en /admin y comparte la misma sesión que los alumnos. La carga de comprobantes acepta cualquier archivo y los guarda en /uploads con el nombre original. Los errores de MySQL se muestran al usuario con la consulta completa. La escuela pide arreglarlo en una semana sin reescribir la aplicación y sin romper a los usuarios existentes; además, debe cumplir con no almacenar datos de tarjetas y con hacer copias de seguridad diarias.
1. Ordena los riesgos por impacto y explica las mitigaciones inmediatas para XSS, sesiones y contraseñas.
Read the worked answer
Prioridad por impacto y explotabilidad: (1) XSS reflejado en el buscador: escapar salida con htmlspecialchars($q, ENT_QUOTES, 'UTF-8') o mejor no reflejar; validar entrada no sustituye el escape. (2) Sesión: session_set_cookie_params(['httponly'=>true,'secure'=>true,'samesite'=>'Lax']), regenerar ID al login y usar HTTPS. (3) Contraseñas: MD5 sin sal es crítico; migrar a password_hash con Argon2id o bcrypt. No se pueden rehashear todas sin la contraseña: al siguiente login, verificar MD5 y rehash; o forzar reset. (4) Errores SQL: display_errors=Off y log interno; usar consultas preparadas. (5) Panel /admin: separar rol y sesión, no confiar en ocultar la URL; exigir autorización en cada acción. Una semana permite mitigar lo crítico, no rediseñar todo.
2. ¿Cómo protegerías el panel administrativo y la carga de comprobantes sin romper la aplicación existente?
Read the worked answer
CSRF: el panel y el formulario de contacto deben llevar un token por sesión en campo oculto y validarlo en cada POST; SameSite=Lax ayuda pero no sustituye el token. Para acciones sensibles, reautenticar. Separar la sesión de alumno y la de administrador con un rol verificado en servidor y comprobar permisos en cada endpoint, no solo en el menú.
Subida: validar extensión y MIME real con finfo, tamaño máximo, nombre aleatorio con bin2hex(random_bytes(16)), guardar fuera del webroot o en un bucket, y denegar ejecución en /uploads (php_flag engine off o regla equivalente). Servir con Content-Disposition: attachment si aplica. Nunca confiar en el nombre original ni en $_FILES['type']. Backups diarios: automatizar, cifrar y probar restauración; no es opcional si se almacenan comprobantes.
3. Completa el plan con endurecimiento del servidor, manejo de errores y mínimo privilegio en la base de datos.
Read the worked answer
Checklist: HTTPS con HSTS y redirección 301; cabeceras Content-Security-Policy compatible (empezar en report-only), X-Content-Type-Options: nosniff, Referrer-Policy y frame-ancestors. Usuario MySQL con permisos mínimos (SELECT/INSERT/UPDATE solo en tablas necesarias), nunca root. Consultas preparadas en todo acceso. Manejo de errores genérico al usuario y log detallado en servidor. Copias y restauración probadas. Escaneo con OWASP ZAP y revisión de dependencias. No almacenar tarjetas: usar pasarela.
El orden es: detener el sangrado (XSS, sesiones, subida), luego migración de contraseñas y por último endurecimiento; medir con pruebas antes de publicar. En hosting compartido, verificar que las directivas se puedan aplicar y documentar las que dependan del proveedor.