Por framework
Seguridad de Laravel — una referencia de hardening para producción
Una referencia de hardening de Laravel para producción: checklist ordenado por prioridad, valores por defecto peligrosos y guía por área — desde secretos y config hasta autorización y CVE de dependencias — más un checklist de autoverificación. Defensivo, sin pasos de ataque.
Para: cualquiera que gestione Laravel en producción. Aquí no hay pasos de ataque — esta es una referencia de trabajo para el hardening: un checklist ordenado por prioridad, los valores por defecto peligrosos, guía por área y autoverificación. Para el panorama entre frameworks, mira el centro de seguridad por framework.
Checklist de hardening ordenado por prioridad
Trabaja esta tabla de arriba abajo. P0 es un prerrequisito para el resto, P1 es la fuente más frecuente de incidentes, P2 es higiene operativa continua.
P0 ── Prerrequisito (hazlo primero)
Debug apagado en prod / secretos fuera de la superficie pública con permisos 600 / gestiona APP_KEY
P1 ── Principal fuente de incidentes
Autorización (Policy/Gate) / control del Mass Assignment / CVE de dependencias / caché de prod
P2 ── Higiene operativa
Sesiones/CSRF/cookies / validación de subidas / HTTPS, cabeceras, limitación de tasa
| Prioridad | Control | Detalles (Laravel) |
|---|---|---|
| P0 | Desactivar el debug de producción | APP_DEBUG=false / APP_ENV=production, fijado con config:cache. No filtres secretos internos en la página de error |
| P0 | Secretos fuera de la superficie pública | .env, copias de seguridad y claves fuera de public/, permisos 600. storage/logs no público |
| P0 | Gestiona APP_KEY de forma segura | Sustenta el cifrado, las cookies firmadas y las sesiones. Inyéctala desde el entorno; rótala si hay fuga |
| P1 | Autorización explícita | Policy / Gate + middleware authorize() / can, con alcance por propietario/permiso |
| P1 | Controla el Mass Assignment | Declara $fillable; evita asignar en bloque $request->all(), usa validated() |
| P1 | Monitoriza los CVE de Composer | composer audit / osv-scanner; juzga por la versión en ejecución, parchea rápido |
| P1 | Caché de producción | config:cache route:cache view:cache para ajustes fiables + velocidad |
| P2 | Seguridad de sesiones/cookies | Fija secure http_only same_site; regenera la sesión al iniciar sesión |
| P2 | Validación de subidas | Valida tipo/tamaño, almacena fuera de public/, sin permiso de ejecución |
| P2 | HTTPS/cabeceras/limitación de tasa | Fuerza HTTPS, HSTS, throttle en login/API |
1. Secretos y APP_KEY (P0)
Laravel mantiene .env fuera de la raíz del documento (en la raíz del proyecto) por defecto. Los incidentes vienen sobre todo de poner secretos donde no corresponden.
- Nunca coloques
.env, volcados de BD, copias de seguridad o archivos de claves enpublic/. Mantén los secretos fuera de la raíz de la app con permisos 600 (solo el propietario). - No expongas
storage/nistorage/logs/(los logs pueden contener secretos o datos personales). No hagas commit de.enval repositorio. APP_KEYsustenta el cifrado, las URL firmadas, las cookies cifradas y las sesiones. Inyéctala desde el entorno y rótala de inmediato si hay fuga (ten en cuenta que esto invalida los datos/sesiones cifrados existentes).
Para el principio general mira mantén los secretos fuera de los directorios públicos; para un caso real de exposición completa mira una exposición completa de .env.
2. Config de producción: DEBUG, entorno, caché (P0)
APP_DEBUG=false / APP_ENV=production
Fija la config con caché
php artisan config:cache (+ route:cache view:cache) hace que los ajustes se apliquen de forma fiable y acelera la app. Nota: después de config:cache, env() fuera de config/ devuelve null, así que referencia los valores con config('...') en la app.No expongas herramientas de diagnóstico en producción
3. Autorización: Broken Access Control (P1 — la principal fuente)
El incidente de producción más común es «autenticado pero no autorizado». Poder iniciar sesión no significa tener permiso para hacer la acción.
Común (peligroso)
- sin autorización — «con sesión iniciada = puede ver/actualizar»
- el route-model binding recupera el ID de otro usuario
Model::create($request->all())acepta todos los campos- un campo de privilegio como
is_adminasignado desde la entrada del usuario
Correcto
- Policy / Gate + middleware
authorize()/can, dando alcance por propietario/permiso cada vez - las lecturas también tienen alcance de propietario (p. ej.
where('user_id', $me)) - limita la entrada con
$request->validated()(FormRequest) - declara
$fillablepara bloquear el Mass Assignment
Mira qué es IDOR. La clave es una comprobación de propiedad en cada ruta de lectura/actualización/borrado.
4. Inyección y salida
El Eloquent/query builder de Laravel vincula valores con placeholders, y Blade escapa la salida por defecto. La regla es no desactivar esa seguridad tú mismo.
- SQL: no mezcles la entrada del usuario en
whereRaw/DB::rawpor concatenación de cadenas. Incluso cuando necesites SQL crudo, usa bindings (→ qué es la inyección SQL). - XSS:
{{ }}de Blade escapa;{!! !!}es salida cruda. No pases la entrada del usuario a{!! !!}. Si tienes que emitir HTML, hazlo solo después de sanearlo (→ qué es XSS). - Validación: valida el tipo/rango/valores permitidos con FormRequest /
validate()antes de usar.
5. Sesiones, CSRF, cookies (P2)
- CSRF: no desactives globalmente la protección CSRF del middleware web. Usa
@csrfen los formularios y un manejo de token adecuado para las SPA (→ qué es CSRF). - Cookies/sesiones: fija
secure(HTTPS),http_only,same_siteenconfig/session.php. Regenera la sesión al iniciar sesión para prevenir la fijación (la autenticación de Laravel regenera). - Fuerza bruta: aplica
throttle/ límites de intentos de inicio de sesión al login.
6. Subidas de archivos y archivos públicos (P2)
- Valida el tipo mime, la extensión y el tamaño, y no confíes en el nombre de archivo proporcionado por el cliente.
- Almacena fuera de
public/(p. ej.storage/) sin permiso de ejecución. Sirve solo lo que deba ser público, a través de una ruta controlada. - Pon autorización también en la entrega, para que los enlaces directos secuenciales no puedan recuperar el archivo de otro usuario.
7. HTTPS, cabeceras de seguridad, limitación de tasa (P2)
- Fuerza HTTPS (
URL::forceScheme('https'), etc.) + HSTS. Detrás de un balanceador de carga, usaTrustProxiespara que el esquema se detecte correctamente. - Cabeceras de seguridad (X-Content-Type-Options, etc.; diseña la CSP en torno a tu configuración de assets). Comprueba tu propio sitio con el verificador de cabeceras de seguridad.
- Limitación de tasa: aplica
throttleal login, las API y el restablecimiento de contraseña.
8. Dependencias y versiones (P1)
- Monitoriza los CVE de las dependencias de Composer con
composer auditu osv-scanner, juzga por la versión en ejecución y parchea rápido (→ monitorizar los CVE de las dependencias · el manual de respuesta a vulnerabilidades). - Mantén Laravel y PHP en versiones soportadas. No dejes versiones EOL en su sitio (se acumulan agujeros que no se pueden corregir).
Verifica: ¿está tu Laravel realmente endurecido?
Construirlo no es el final — solo está hecho una vez que lo has comprobado. Estas son autoverificaciones defensivas contra tu propio sitio/entorno.
Los secretos no se pueden obtener por URL
/.env y /storage/logs/laravel.log y confirma que devuelven 404 (si son accesibles, corrige de inmediato y rota las claves).Sin exposición de debug en producción
La autorización se sostiene
Cookies/sesiones y dependencias
composer audit está limpio.La visión de este sitio: aun con valores por defecto sólidos, la autorización y las dependencias corren por tu cuenta
Laravel tiene muchos buenos valores por defecto, pero la autorización — quién puede hacer qué — y la frescura de las dependencias son específicas de la app y de la operación, así que ningún framework puede protegerlas de forma automática. Los incidentes que seguimos viendo son menos ataques elaborados que patrones de configuración/operación: «autenticado pero sin comprobación de propiedad», «debug abierto en producción», «un secreto expuesto». Por eso el centro de gravedad es trabajar la tabla de arriba de arriba abajo y hacer lo poco vistoso — dar alcance a cada lectura/actualización por propietario, y monitorizar los CVE de las dependencias.
Sigue leyendo
- Centro: seguridad por framework · seguridad de Next.js
- Secretos: mantén los secretos fuera de los directorios públicos · Caso: una exposición completa de .env
- Autorización / práctica: qué es IDOR · el manual de respuesta a vulnerabilidades · monitorizar los CVE de las dependencias
- Glosario: inyección SQL · XSS · CSRF
FAQ
Q¿Qué debo hacer primero para asegurar Laravel?
Los tres puntos P0: (1) asegura APP_DEBUG=false en producción y fija la config con config:cache; (2) mantén .env y los archivos de secretos fuera del directorio público con permisos estrictos; (3) gestiona APP_KEY de forma segura (sustenta el cifrado, las cookies firmadas y las sesiones, así que rótala si hay fuga). Son prerrequisitos para todo lo demás. Después, pasa a la autorización (Policy/Gate) y a la monitorización de CVE de dependencias de Composer.
Q¿Qué tiene de peligroso desplegar con APP_DEBUG=true?
Con debug activado, la página de error puede mostrar no solo una traza de pila sino secretos internos como valores de config, variables de entorno e info de conexión. Un atacante puede provocar errores a propósito para extraerlos. En producción, pon APP_DEBUG=false y APP_ENV=production y hazlo persistir con config:cache. Además, no expongas herramientas de diagnóstico como Telescope o Debugbar en producción.
Q¿Por qué env() devuelve null después de config:cache?
config:cache compila tu config en un solo archivo por velocidad, y a partir de ahí env() llamado fuera de los archivos de config devuelve null (solo se lee el .env del momento de la caché). La solución es usar env() únicamente dentro de config/ y referenciar los valores en la app con config('...'). En producción, cachear config/route/view es la norma — hace que los ajustes se apliquen de forma fiable y acelera la app.
Q¿Cómo evito el 'si has iniciado sesión, está permitido'?
La autenticación (inicio de sesión) y la autorización (si la acción está permitida) son distintas. En Laravel, implementa Policy/Gate y usa authorize() o el middleware can para dar alcance a cada lectura/actualización/borrado por propietario o permiso. Además, controla el Mass Assignment declarando $fillable y evita asignar en bloque $request->all(). Sin esto, cambiar un ID en la URL alcanza los datos de otro usuario (IDOR).
Q¿Cómo gestiono las vulnerabilidades de las dependencias de Composer?
Monitoriza de forma automática los CVE conocidos con composer audit u osv-scanner, juzga por la versión en ejecución y parchea rápido (decide sobre lo que está realmente instalado, no sobre la declaración de composer.json). Además, mantén Laravel y PHP en versiones soportadas y no dejes versiones EOL en su sitio. La frescura de las dependencias es un factor más realista en los incidentes que los ataques elaborados.