En resumen
- No vas a construir algo irrompible. El objetivo es reducir la superficie explotable y limitar el radio de daño cuando algo sí se cuela.
- Un puñado de problemas causa la mayoría de las brechas reales: control de acceso roto, inyección a través de código que esquiva el framework, secretos filtrados y dependencias desactualizadas.
- Los frameworks bloquean casi toda la inyección gratis —hasta que recurres a SQL crudo o a salida sin escapar. El peligro está en el código que se sale del camino seguro.
- Autoriza cada acción en el servidor. Ocultar un botón no es control de acceso; un atacante llama al endpoint directamente.
El peor incidente de seguridad que he tenido que ayudar a limpiar no fue un exploit ingenioso. Fue una contraseña de base de datos en un archivo .env commiteado en un repositorio que alguien suponía privado. Sin zero-day, sin atacante experto —solo una credencial que nunca debió estar en git, encontrada y usada. Así es como se ven la mayoría de las brechas reales: no hacking de película, sino una brecha aburrida y evitable de la que nadie se hizo cargo.
«La seguridad es importante» no es un consejo. Esta es la versión que de verdad le daría a un desarrollador de PHP, Laravel o WordPress: un modelo de amenazas corto y las brechas concretas por las que comprometen los sitios, más o menos en el orden en que ocurren.
Piensa en términos de superficie y radio de daño
La seguridad perfecta no es el objetivo, porque no existe. Dos preguntas te dan la mayor parte del valor. Primera: ¿cuál es la superficie explotable —cada lugar donde entrada no confiable llega a tu código, cada credencial, cada dependencia que no escribiste? Segunda: cuando una de esas falla, ¿hasta dónde se extiende el daño? Una clave de API de solo lectura filtrada es un mal día; una contraseña root de base de datos filtrada es una catástrofe. La mayoría de las buenas decisiones de seguridad vienen de reducir lo primero y contener lo segundo, no de perseguir una puntuación perfecta.
Control de acceso roto: el número uno silencioso
El fallo grave más común es también el menos dramático: código que comprueba si has iniciado sesión pero no qué tienes permitido tocar. La forma clásica es un endpoint que se fía de un ID de la petición:
// Anyone logged in can read anyone's invoice
public function show($id)
{
return Invoice::findOrFail($id);
}Cambia el ID en la URL y estás leyendo los datos de otra persona. Ocultar el enlace en la interfaz no cambia nada, porque el atacante llama al endpoint directamente. La autorización tiene que ocurrir en el servidor, en cada acción, contra el usuario actual:
public function show(Invoice $invoice)
{
$this->authorize('view', $invoice); // policy checks ownership
return $invoice;
}Ocultar la interfaz no es control de acceso
Si lo único que impide a un usuario una acción es que el botón no se renderiza, no hay control alguno. Cada endpoint debe verificar que el usuario actual tiene permitido actuar sobre el recurso concreto —en el servidor, cada vez.
Inyección: los frameworks te protegen hasta que te sales
La inyección SQL y el cross-site scripting son viejos, y los frameworks modernos los bloquean por defecto —que es justo por lo que los casos restantes vienen de código que se sale del camino seguro. Eloquent y los bindings de consulta parametrizan el SQL por ti; la vulnerabilidad aparece cuando alguien concatena entrada en una consulta cruda:
// Vulnerable: input concatenated into SQL
DB::select("SELECT * FROM users WHERE email = '$email'");
// Safe: parameter binding
DB::select('SELECT * FROM users WHERE email = ?', [$email]);El XSS es la misma historia en la salida. {{ }} en Blade escapa automáticamente; el peligro es {!! !!}, que imprime HTML crudo. En WordPress es imprimir datos de usuario sin esc_html() o esc_attr(). La regla que previene la mayor parte: nunca confíes en la entrada al entrar, siempre escapa al salir, y trata cada {!! !!} o echo sin escapar como un lugar que necesita justificación.
Secretos y dependencias: las brechas aburridas
Las dos brechas detrás de una gran parte de los compromisos del mundo real no tienen nada que ver con la lógica de la aplicación. La primera son los secretos filtrados —credenciales en un .env commiteado, una clave de API pegada en un repositorio o un chat. Mantén los secretos fuera del control de versiones, y si uno se filtra, rótalo de inmediato y trátalo como totalmente comprometido; un repositorio privado no es un lugar seguro para una credencial en vivo. Escribí sobre exactamente este modo de fallo en subir secretos a git por accidente.
La segunda son las dependencias desactualizadas —y en WordPress en concreto, los plugins son el mayor vector de brecha. Una vulnerabilidad conocida en un plugin sin parchear es un exploit publicado y scriptable. Trata las actualizaciones como trabajo de seguridad, no como tareas pesadas: ejecuta composer audit, mantén los plugins al día y elimina los que no usas. En un montaje tipo Bedrock, bloquear las modificaciones de archivos desde el navegador en producción (DISALLOW_FILE_MODS) mantiene el conjunto de plugins bajo control de versiones, donde puedes auditarlo.
No confíes en ningún payload de fuera
Cualquier petición de un sistema externo no es de fiar hasta que se demuestre lo contrario, incluidos los webhooks. Un webhook de pago o de integración sobre el que actúas sin verificar su firma es un endpoint abierto al que cualquiera puede enviar eventos falsificados. Verifica la firma antes de hacer nada con el cuerpo —el mismo punto que planteo sobre Stripe en la guía de Laravel Cashier. Esto se vuelve mucho más fácil cuando las integraciones externas viven tras un único límite que puedes auditar, en lugar de esparcidas por los controladores —el argumento estructural está en inyección e inversión de dependencias.
Cabeceras y transporte: reales, pero defensa en profundidad
HTTPS en todas partes, HSTS y una política de seguridad de contenido vale la pena tenerlas —son una capa significativa, y la CSP en particular limita el daño de un XSS que se cuela. Pero son defensa en profundidad, no el evento principal. Una CSP estricta no salva a un endpoint con control de acceso roto. Pon las cabeceras en su sitio y luego gasta tu atención en autorización, inyección, secretos y dependencias, donde ocurren las brechas reales.
Errores que llevan a brechas reales
Autorizar solo en la interfaz
Ocultar un control no es una comprobación. Verifica permisos en el servidor en cada endpoint contra el recurso concreto.
SQL crudo con entrada interpolada
Concatenar datos de la petición en una consulta reabre la inyección SQL que el framework había cerrado. Usa bindings o el query builder.
Salida sin escapar
{!! !!} en Blade o un echo sin escapar en WordPress imprime entrada de usuario cruda. Escapa en la salida por defecto.
Secretos en el repositorio
Una credencial commiteada está comprometida, sea el repositorio privado o no. Mantén los secretos fuera de git y rota cualquiera que se filtre.
Plugins y paquetes obsoletos
Las dependencias sin parchear son exploits publicados. Actualiza con regularidad y elimina lo que no uses.
Confiar en los payloads de webhook
Un endpoint de webhook sin verificar acepta eventos falsificados. Verifica la firma antes de actuar sobre el cuerpo.
La seguridad también es un proceso
Nada de esto sobrevive como un «repaso de seguridad» único antes del lanzamiento. Los equipos que se mantienen seguros lo integran en la rutina: secretos en un gestor y fuera de git, actualizaciones de dependencias programadas, acceso de mínimo privilegio para las credenciales y copias de seguridad cuya restauración has probado de verdad. Las restauraciones probadas importan —una copia sin probar es una conjetura, y descubres cuál era durante el incidente. Integrado en la entrega normal, esto produce productos más seguros sin una fase de seguridad aparte que lo ralentiza todo.
FAQ
- ¿Cuál es la vulnerabilidad más común en aplicaciones web?
- El control de acceso roto —endpoints que confirman que un usuario está autenticado pero no que está autorizado a actuar sobre el recurso concreto. Cambiar un ID en una petición para leer datos de otra persona es el caso clásico. La autorización debe aplicarse en el servidor en cada acción.
- ¿Usar Laravel o WordPress hace mi sitio seguro por defecto?
- Previenen mucho —Eloquent parametriza el SQL, Blade escapa la salida— pero solo mientras te quedas en el camino seguro. Las consultas crudas con entrada interpolada, la salida sin escapar con
{!! !!}, los secretos filtrados y los plugins obsoletos esquivan esas protecciones. El framework reduce el riesgo; no elimina tu responsabilidad. - ¿Qué debo hacer si commiteo un secreto a git por accidente?
- Trátalo como totalmente comprometido y rótalo de inmediato —cambia la contraseña o revoca la clave, aunque el repositorio sea privado. Eliminar el archivo en un commit posterior no ayuda, porque el valor permanece en el historial de git y puede haber sido copiado ya.
- ¿Bastan las cabeceras de seguridad como CSP para proteger un sitio?
- No. Cabeceras como HSTS y una política de seguridad de contenido son una valiosa defensa en profundidad y limitan el daño de algunos ataques, pero no arreglan el control de acceso roto, la inyección ni las credenciales filtradas. Úsalas además de la autorización en el servidor y el manejo seguro de la entrada, no en su lugar.
Artículos relacionados
- Subir secretos a git por accidente recorre el modo de fallo de credenciales filtradas y cómo recuperarse de él.
- Laravel Cashier y Stripe cubre la verificación de firmas de webhook para que los payloads externos no puedan falsificarse.




