Saltar al contenido
>_ITDITDPlataforma de seguridad web

Guías de seguridad

El .env de apps Laravel era legible por todo el mundo — el error más común del hosting compartido

En hosting compartido, las apps Laravel acaban con .env, .git, volcados de BD y secretos OAuth legibles por HTTP. La causa: todo el proyecto bajo la raíz web en lugar de solo public/. La corrección en tres pasos, y por qué es un mal patrón de toda la industria.

Publicado 2026-06-07 7 min de lectura

Una mala configuración que sí ocurre en hosting compartido, con dominios y valores eliminados y convertida en lección. Esto no trata de encontrar los sitios de otras personas — trata de asegurar tu propio despliegue (o uno heredado).

Expediente del caso — INCIDENT FILE
Clase
Exposición de .env / .git / volcados de BD (mala configuración)
Gravedad
Crítica (todos los secretos legibles por HTTP)
Causa
Cuerpo de Laravel colocado directamente bajo public_html (solo public/ debería exponerse)
Propagación
Cada nueva app copiaba el mismo agujero → decenas de ellas
Corrección permanente
Cuerpo fuera del docroot + solo public/ enlazado simbólicamente

Los peores archivos son .env y .git

.env es un llavero: autenticación de BD, correo, API externas, la clave de cifrado de la app (APP_KEY). Con .git público, los atacantes recuperan no solo los valores actuales sino cada rotación pasada recorriendo el historial de git. Véase qué es .env.

Qué estaba pasando

En el host compartido, muchas apps Laravel estaban dispuestas así. Aquí están las disposiciones peligrosa y correcta una al lado de la otra:

Disposición peligrosa (común)

~/public_html/
└── app/          ← todo Laravel (incorrecto)
    ├── .env      ← legible en /app/.env
    ├── .git/     ← clonar restaura el historial completo
    ├── vendor/  config/  storage/
    └── public/   ← lo ÚNICO que debería estar aquí

Disposición correcta

~/laravel/app/    ← cuerpo fuera de la raíz web
├── .env  .git/   ← inalcanzable por HTTP = seguro
└── public/       ← solo esto, expuesto vía enlace simbólico
        └── index.php

Archivos típicos que devolvían 200 OK al mundo exterior:

ArchivoPor qué es peligroso
.env / .env.bk_*autenticación de BD, correo, claves de API externas, APP_KEY — todo en texto plano
el directorio .git/git clone restaura el código fuente y el historial completo
composer.lock / package-lock.jsonversiones exactas de dependencias = una lista de objetivos con CVE conocidos
credentials.jsonsecretos de cliente OAuth, empaquetados
*.sql / db-*.sql.gzun volcado completo de la base de datos, decenas de MB comprimidos

La parte que asusta: la propagación

Una vez que lo dispones así, cada nueva app del mismo tipo copia el mismo agujero. Una mala configuración se vuelve decenas. La regla: en el momento en que lo notes, inspecciónalas todas.

La corrección en tres pasos

Paso 1 — Primeros auxilios: bloquea los archivos sensibles a nivel HTTP

Primero, detén «que se filtre más». Es reversible y no afecta a las páginas normales. Añade un bloque deny al principio de public_html/.htaccess (un ejemplo de configuración defensiva):

# === SECURITY BLOCK ===
# 404 a cualquier cosa bajo .env / .git
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
 
# denegar copias de seguridad, volcados, archivos de credenciales
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
    Require all denied
</FilesMatch>
 
# si las internas de Laravel están visibles en /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===

Pero una lista negra es fundamentalmente un juego del topo. Olvidarás composer.lock; nuevos nombres de archivo añaden nuevos agujeros. Trátalo como primeros auxilios de mejor esfuerzo, y pon la corrección real en el Paso 3.

Paso 2 — Rotar claves (asume que las vieron)

Incluso tras bloquear con .htaccess, trata los secretos como ya vistos. Por orden de prioridad:

  1. Claves de API externas (máxima prioridad, inmediato): se asocian a facturación y toma de control de cuentas. Revoca + reemite el CLIENT_SECRET de OAuth, las claves de API y las credenciales de la nube, con alcance de mínimo privilegio.
  2. Secretos de cliente OAuth.
  3. APP_KEY: regenerarlo puede romper sesiones y dejar indescifrables columnas de BD cifradas — comprueba primero el radio de impacto.
  4. Correo/SMTP, credenciales de BD.

Una trampa de rotación (sufrida de verdad en el campo)

Con algunos proveedores OAuth, reemitir el secreto de cliente también invalida los refresh_token existentes — el refresco con tokens guardados falla y provoca una caída de producción. Ten siempre un flujo de reautorización listo antes de reemitir. Y actualiza también tu .env / configuración local, o el siguiente despliegue reintroduce el valor antiguo.

Paso 3 — Reestructurar: mover el cuerpo fuera del docroot (la corrección real)

Vuelve a disponer cada app así:

# 1. Mover el cuerpo fuera de la raíz web (mismo sistema de archivos → mv es instantáneo, atómico)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Poner solo un bootstrap mínimo en la raíz web
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copiar public/.htaccess de Laravel, enlazar simbólicamente los activos estáticos, limpiar la caché de config

En esta forma, .env, .git y vendor/ simplemente no existen en la raíz web, así que estás a salvo sin depender de reglas de .htaccess. Los pasos completos están en mantener .env fuera de la web pública en hosting compartido.

✗ Peligroso: cuerpo dentro de la raíz web

— frontera web (legible por HTTP) —

.env · .git · vendor · public

↑ todo dentro de la línea = totalmente expuesto

✓ Seguro: cuerpo fuera de la raíz web

— frontera web (legible por HTTP) —

solo public/ (enlace simbólico)

.env · .git están fuera = inalcanzables

Una línea lo decide — dentro o fuera de la raíz web. Si .env y .git están dentro, cualquiera puede leerlos por HTTP.

Trampas «que no están en el manual»

  • Problema de alcance de subdominio: reapuntar el docroot de un subdominio registrado a otro árbol vía enlace simbólico puede no seguirse y devolver 500. → Mantén el docroot como un directorio real y haz require por ruta absoluta desde un pequeño index.php dentro de él (la forma «bootstrap-redirect») para estabilidad.
  • opcache recuerda un estado roto: opcache puede memorizar una carga fallida de index.php y seguir devolviendo 500 incluso tras reconstruir. Último recurso: cambia el nombre del archivo (entry.php, etc.).
  • Un docroot de subdominio «no registrado» legible vía el padre: https://sub.example/ puede ser inválido mientras https://example/sub/ aún muestra el contenido — una entrada fácil de pasar por alto. Al inspeccionar, bloquea también la ruta del dominio padre.
  • Secretos incrustados en config/*.php: combinado con un .git público, el historial revela cada rotación. Pasa siempre por env() y nunca pongas valores reales en los defaults de respaldo.

La mayor lección: esto no es «el error de una persona»

La cultura temprana del hosting compartido puso todo directamente bajo public_html. Laravel (que espera exponer solo public/) fluyó hacia eso, y una disposición peligrosa de «proyecto bajo public_html/<app>/, exponer su public/» se convirtió en el estándar de facto — la propia documentación de los proveedores usa ejemplos basados en public_html, lo que lo empeora. Así que corrígelo con proceso, no con vigilancia:

Previene la recurrencia con proceso

  • Haz que el valor por defecto del despliegue sea «cuerpo fuera del docroot, solo public/ enlazado simbólicamente».
  • Comprobaciones de CI/lint: nunca hacer commit de .env; nunca colocar un proyecto directamente bajo public_html/<app>.
  • Autocomprobación tras el despliegue: confirma que /.env y /.git/config no se pueden obtener desde fuera.

Este sitio integra este hábito de «inspecciona tu propio sitio tú mismo» en su itinerario de aprendizaje. → qué es lo peligroso de .env y las claves de API

Leer a continuación

FAQ

QMi .env está expuesto — ¿qué hago primero?
A

Detén la hemorragia (bloquea la ruta vía .htaccess), luego rota las claves de API externas y los secretos OAuth del .env por orden de prioridad (asume que los vieron), y después mueve el cuerpo de la app fuera de la raíz web para estar a salvo sin depender de reglas.

Q¿Por qué es peligroso poner Laravel bajo public_html?
A

Laravel está hecho para exponer solo public/. Pon todo el proyecto en el docroot y todo lo que hay por encima — .env (todos tus secretos), .git (historial recuperable), vendor/ — se vuelve legible por HTTP.

Q¿Basta con bloquear con una lista negra en .htaccess?
A

Funciona como primeros auxilios pero es fundamentalmente un juego del topo — cada nuevo nombre de archivo añade un agujero. La corrección real es el cambio estructural: pon el cuerpo de la app fuera del docroot y expón solo public/ mediante un enlace simbólico.