Vulnerabilidades Críticas
Crítica
CWE-215
APP_DEBUG=true en producción — fuga total de configuración
.env · config/app.php
Con
⚡ Exposición de credenciales DB
⚡ APP_KEY filtrada → sesiones falsificables
⏱ Fix: 2 min
APP_DEBUG=true cualquier excepción expone en el navegador: stack traces completos, valores de variables de entorno, credenciales de base de datos, APP_KEY y configuración interna. Un atacante puede obtener acceso total a la base de datos con un único error 500.
✗ Vulnerable — .env actual
APP_DEBUG=true # CRÍTICO APP_ENV=local APP_KEY=base64:abc...
✓ Corrección — .env producción
APP_DEBUG=false # NUNCA true APP_ENV=production APP_KEY=base64:abc...
✓ Validación al arrancar — bootstrap/app.php
if (empty(config('app.key'))) { throw new RuntimeException( 'APP_KEY no configurada. Ejecuta: php artisan key:generate' ); } if (config('app.debug') && app()->environment('production')) { throw new RuntimeException('APP_DEBUG no puede ser true en producción.'); }
Crítica
CWE-915
Mass assignment desprotegido — escalada de privilegios
app/Models/User.php · app/Http/Controllers/AppointmentController.php
$guarded = [] + create($request->all()) permite que un atacante inyecte campos como role=admin o is_verified=1 directamente en la petición HTTP. En un SaaS médico esto implica acceso a historiales de pacientes.
✗ User.php vulnerable
class User extends Authenticatable { protected $guarded = []; // PELIGROSO // role, is_admin, is_verified // todos escribibles! }
✓ User.php corregido
final class User extends Authenticatable { protected $fillable = [ 'name', 'email', 'phone', ]; // sin role, is_admin }
✗ AppointmentController
public function store(Request $request) { Appointment::create($request->all()); // ⚠ }
✓ AppointmentController corregido
public function store(StoreAppointmentRequest $r) { Appointment::create($r->validated()); // solo campos validados }
Crítica
CWE-79
XSS Almacenado en notas de paciente
resources/views/patients/show.blade.php
El uso de
⚡ Robo de sesión (cookies HTTPOnly bypass)
⚡ Datos sanitarios expuestos
⏱ Fix: 5 min
{!! $patient->notes !!} renderiza HTML sin escapar. Un médico o paciente puede inyectar <script> en las notas y robar cookies/tokens de sesión de cualquier usuario que visite el perfil. En un entorno clínico esto compromete datos sensibles de salud.
✗ Blade vulnerable
{{-- patients/show.blade.php --}} <div class="notes"> {!! $patient->notes !!} {{-- XSS --}} </div>
✓ Blade corregido
{{-- patients/show.blade.php --}} <div class="notes"> {{ $patient->notes }} {{-- escapado --}} </div> {{-- o si necesitas HTML real: --}} {!! app(Purifier::class)->clean($patient->notes) !!}
Severidad Alta
Alta
CWE-434
Upload de documentos sin validación de MIME — ejecución remota posible
app/Http/Controllers/Api/DocumentUploadController.php
El endpoint
⚡ RCE si la carpeta es pública
⏱ Fix: 20 min
/api/upload-document no valida tipo MIME ni extensión. Un atacante puede subir un .php disfrazado de PDF y si el servidor Apache/Nginx sirve desde el directorio de subidas, se puede ejecutar código arbitrario.
✓ UploadDocumentRequest con validación completa
final class UploadDocumentRequest extends FormRequest { public function rules(): array { return [ 'document' => ['required', 'file', 'mimes:pdf,doc,docx', // whitelist MIME 'extensions:pdf,doc,docx', // whitelist extensión 'max:10240', // 10 MB máx ], ]; } } // Almacenar FUERA del directorio público $path = $request->file('document')->store('documents', 'local'); // NUNCA: store('documents', 'public')
Alta
CWE-307
Sin rate limiting en autenticación — fuerza bruta desprotegida
routes/api.php · routes/web.php
Las rutas de login no tienen throttle. Un bot puede realizar miles de intentos por minuto contra cuentas de médicos y pacientes sin ningún bloqueo.
⚡ Brute force de contraseñas
⏱ Fix: 15 min
✓ RouteServiceProvider — rate limiters
RateLimiter::for('auth', fn (Request $req) => Limit::perMinute(5)->by($req->ip()) ); RateLimiter::for('api', fn (Request $req) => Limit::perMinute(60)->by($req->user()?->id ?: $req->ip()) ); // routes/web.php Route::post('/login', [AuthController::class, 'login']) ->middleware('throttle:auth');
Alta
CWE-1021
Sin cabeceras de seguridad HTTP — clickjacking y MIME sniffing
app/Http/Kernel.php
Sin cabeceras como
⚡ Clickjacking en panel médico
⏱ Fix: 20 min
X-Frame-Options, Content-Security-Policy o X-Content-Type-Options la app es vulnerable a clickjacking (incrustar en iframe malicioso) y MIME sniffing (ejecutar scripts disfrazados de imágenes).
✓ SecurityHeaders middleware
class SecurityHeaders { public function handle($req, Closure $next): mixed { $res = $next($req); $res->headers->set('X-Frame-Options', 'DENY'); $res->headers->set('X-Content-Type-Options', 'nosniff'); $res->headers->set('X-XSS-Protection', '1; mode=block'); $res->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); $res->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self'; frame-ancestors 'none'" ); return $res; } }
Severidad Media
Media
CWE-311
Sesiones en driver file sin cifrado — exposición en servidor comprometido
config/session.php
Las sesiones guardadas en fichero son legibles en texto plano si el servidor se compromete. Para un SaaS médico con datos de pacientes, se recomienda el driver
⏱ Fix: 10 min
database o redis con cifrado activado.
✗ session.php actual
'driver' => 'file', 'encrypt' => false, 'secure' => false,
✓ session.php corregido
'driver' => 'redis', // o database 'encrypt' => true, 'secure' => true, // HTTPS only 'http_only' => true, 'same_site' => 'lax',
Media
CWE-778
Sin log de intentos fallidos de autenticación — ausencia de auditoría
app/Listeners/FailedLoginListener.php (falta)
No hay registro de intentos fallidos de login ni cambios de rol. Imposible detectar intrusiones o cumplir normativa de auditoría (RGPD, esquemas médicos). Implementar un canal
⏱ Fix: 30 min
security en el logger.
✓ SecurityLogger + listener
// app/Listeners/FailedLoginListener.php class FailedLoginListener { public function handle(Failed $event): void { Log::channel('security')->warning('failed_login', [ 'email' => $event->credentials['email'] ?? '?', 'ip' => request()->ip(), 'ua' => request()->userAgent(), ]); } } // EventServiceProvider protected $listen = [ Failed::class => [FailedLoginListener::class], PasswordReset::class => [SecurityAuditListener::class], ];
Severidad Baja
Baja
CWE-1104
composer audit no ejecutado en CI — dependencias con CVEs conocidos
.github/workflows/ci.yml (falta)
Sin
⏱ Fix: 5 min
composer audit en el pipeline CI, los CVEs en paquetes de terceros pasan desapercibidos. Añadir al workflow de GitHub Actions.
✓ .github/workflows/security.yml
name: Security Audit on: [push, pull_request] jobs: audit: runs-on: ubuntu-latest steps: - run: composer audit --no-dev - run: php artisan about --only=environment
Checklist Go-Live — AgendaFlow
APP_DEBUG=false en producción
Cambiar en .env y validar al arrancar
$fillable en todos los modelos
User, Appointment, Patient, Document
XSS: reemplazar {!! !!} por {{ }}
patients/show.blade.php y similares
Validación MIME en upload de documentos
UploadDocumentRequest + storage 'local'
Rate limiting en /login y /api
throttle:auth (5/min), throttle:api (60/min)
Middleware SecurityHeaders registrado
CSP, X-Frame-Options, X-Content-Type
Sesiones → driver redis/database + encrypt
Redis ya en stack, migración sencilla
Log de seguridad + listener Failed::class
Canal 'security' en config/logging.php
CSRF activo por defecto (VerifyCsrfToken)
Solo excluir webhooks con firma propia
Sanctum configurado para API móvil
Añadir expiración + abilities por ruta
composer audit en CI/CD pipeline
GitHub Actions — workflow security.yml
.env no commiteado en git
Verificado en .gitignore
HTTPS forzado en producción
URL::forceScheme('https') + ForceHttps middleware
Bcrypt rounds ≥ 12
config/hashing.php — aumentar de 10 a 12
🚫
Resultado: 8 fallos / 14 checks · AgendaFlow NO está listo para producción. Estimado de remediación: 3–4 horas de desarrollo. Las 3 críticas deben corregirse antes de cualquier prueba con datos reales.
Plan de Remediación — 2 semanas al go-live
Día 1 — URGENTE (hoy mismo)
Corregir las 3 vulnerabilidades críticas
APP_DEBUG=false
$fillable en modelos
XSS notes blade
create($r->validated())
Días 2–3 — Alta prioridad
Corregir vulnerabilidades altas + CI
UploadDocumentRequest
throttle:auth
SecurityHeaders middleware
ForceHttps middleware
composer audit en CI
Días 4–7 — Media prioridad
Hardening y auditoría
Session → redis + encrypt
SecurityLogger
FailedLoginListener
Bcrypt rounds 12
Días 8–14 — Pre-launch
Revisión final y pen-test ligero
Re-check checklist completo
OWASP ZAP scan
Revisar CORS allowed_origins
php artisan about --env