El OWASP Top 10 en mi propia web de 20 años

Audité mis propias webs antiguas con el OWASP Top 10:2025 y encontré nueve de las diez categorías. Qué encontré y cómo lo corregí.

Los arquitectos de seguridad pasamos los días revisando sistemas ajenos. En otoño de 2026 por fin hice lo que recomiendo a todos mis clientes: auditar los míos. Mi web personal nació en 1999 y creció durante dos décadas: un portal ASP.NET basado en XML, experimentos en PHP, varias instalaciones de WordPress, un CMS antiguo en .NET y muchos proyectos pequeños que simplemente había olvidado.

El resultado fue una cura de humildad y muy instructivo. Estaban representadas nueve de las diez categorías del OWASP Top 10:2025 (se abre en una pestaña nueva). Esto es lo que encontré, clasificado según OWASP, y lo que hice al respecto.

La lección en una frase: el mayor riesgo de una web no es el código que mantienes, sino el que has olvidado.

Cómo lo hice

  1. Primero, una copia. Descargué todo el servidor por FTPS a un repositorio Git local. Así tenía una copia en la que buscar y un botón de deshacer. El repositorio es solo local, porque los archivos de configuración contienen credenciales.
  2. Buscar, no leer. Con decenas de miles de archivos no se puede leer todo. Con ayuda de un asistente de programación con IA busqué patrones: phpinfo(, eval(base64_decode, contraseñas escritas en el código, archivos .sql, .bak y .zip, y carpetas de instaladores. El asistente hizo las búsquedas; cada decisión y cada cambio en el servidor fueron míos.
  3. Comprobar qué es realmente público. Un archivo peligroso en el disco es una cosa; un archivo peligroso que responde 200 OK en internet es otra. Probé cada hallazgo contra la web en producción.
  4. Cuarentena, no borrado. Todo lo que retiré se movió primero a una carpeta fuera de la web, se probó y solo después se borró.

Qué encontré

HallazgoOWASP Top 10:2025Corrección
Un diccionario en PHP de 2007 con inyección SQL y una página que permitía a cualquiera modificar entradas sin iniciar sesiónA05 Inyección, A01 Control de acceso rotoReescrito: consultas preparadas, salida escapada, solo lectura
Varias páginas phpinfo() públicas que mostraban la versión de PHP, rutas y configuraciónA02 Configuración de seguridad incorrectaRetiradas de la web
Instalaciones de WordPress de 2019–2020, un CMS antiguo en .NET, un sistema operativo web abandonado y un editor de texto enriquecido antiguoA03 Fallos en la cadena de suministro de softwareSacados de la web
Una redirección a HTTPS que, por una condición errónea, solo se aplicaba al robot de un buscadorA04 Fallos criptográficosRedirección para todos, más HSTS
Contraseñas del portal guardadas en texto plano y un archivo de estadísticas público con los nombres de usuarioA07 Fallos de autenticaciónCarpetas de datos bloqueadas, contraseñas cambiadas, portal retirado
Una cookie de "recordarme" con la contraseña del usuario cifrada de forma reversibleA04 / A07Retirada junto con el portal
Credenciales de la base de datos escritas en el código de la aplicaciónA07 Fallos de autenticaciónMovidas a un archivo de configuración aparte que nunca se sirve
Un script antispam de 2013 que mi antivirus detectó como PHP malicioso ofuscadoA08 Fallos de integridad de software o datosRetirado
Un archivo de registro público con direcciones IP de visitantesA09 Fallos de registro y alertas de seguridad (y RGPD)Retirado de la web
Páginas de error de ASP.NET detalladas, con traza de la pila, más compilación en modo depuración y trazado activadoA10 Gestión incorrecta de condiciones excepcionales, A02Páginas de error genéricas, debug="false", trazado desactivado
Datos de API externas (nombres de servidores de juego, usuarios de chat) escritos en el HTML sin escaparA05 Inyección (XSS)Codificación de la salida con htmlspecialchars

La única categoría que no encontré fue A06 Diseño inseguro, aunque guardar contraseñas en texto plano también fue, en el fondo, una decisión de diseño.

Cinco lecciones

1. Tu superficie de ataque es todo lo que responde en el puerto 443

Ninguno de los peores hallazgos estaba en las páginas que visito. Estaban en carpetas que no había abierto en diez años. El inventario va antes que todo lo demás.

2. La configuración depende de la plataforma

Varios proyectos antiguos traían archivos .htaccess escritos para Apache. Mi alojamiento usa IIS, que los ignora por completo. Sean cuales sean las reglas que crees tener, compruébalas en la plataforma en la que realmente funcionan.

3. Prueba el control, no la configuración

La redirección a HTTPS existía en la configuración, así que parecía correcta. Solo al probarla como un visitante normal vi que nunca se activaba. Lo mismo vale para cortafuegos, políticas de MFA y copias de seguridad: prueba el resultado.

4. Las contraseñas en texto plano nunca son "solo una web de pruebas"

Contraseñas cortas guardadas en texto plano, más una lista pública de usuarios, convierten una web de pruebas olvidada en una puerta de entrada. Guarda las contraseñas con un algoritmo moderno (Argon2id, scrypt o bcrypt) o, mejor aún, no las guardes.

5. El código más seguro es el que borras

Sustituí el portal antiguo por una web estática: sin base de datos, sin inicio de sesión, sin código en el servidor, sin cookies y con una política de seguridad de contenidos estricta. La edito con un pequeño editor privado, protegido con una contraseña larga y un código de un solo uso, que publica HTML sencillo. En la web pública ya casi no queda nada que atacar, y además es más rápida y más barata de mantener.

Lista de comprobación para tu propia web

  • Descarga una copia completa y ponla bajo control de versiones (en privado).
  • Busca phpinfo, instaladores, copias de seguridad, volcados de bases de datos y eval(base64_decode(.
  • Haz una lista de todas las aplicaciones y sus versiones; elimina lo que nadie usa.
  • Desactiva los errores detallados y el modo de depuración en producción.
  • Fuerza HTTPS para todos y añade cabeceras de seguridad (HSTS, CSP, X-Content-Type-Options).
  • Comprueba que ninguna carpeta de datos, registros o configuración se sirve a internet.
  • Saca las credenciales del código y cambia todas las contraseñas que alguna vez se guardaron en texto plano.

A mí me llevó un fin de semana encontrar todo esto. El escáner de un atacante necesita unos minutos.