Seguridad e identidad

OWASP Top 10: las diez categorías de riesgo y cómo corregir cada una

Intermedio10 min de lecturaActualizado

Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés

El OWASP Top 10 es un documento de concienciación que identifica las categorías de riesgo responsables de la mayoría de los compromisos de aplicaciones web. Esta guía recorre cada categoría con un ejemplo realista del fallo y la corrección concreta en un stack Java y web, y después explica cómo usar la lista como punto de partida y no como checklist, y en qué momento toma el relevo el Application Security Verification Standard.

Lo que aprenderás

  • Reconocer cada categoría del OWASP Top 10 en código de aplicaciones reales
  • Aplicar la mitigación concreta frente al control de acceso roto, la inyección y los fallos criptográficos
  • Distinguir el diseño inseguro de los bugs de implementación y responder a cada uno de forma adecuada
  • Gestionar el riesgo de dependencias y de la cadena de suministro con un SBOM y builds reproducibles
  • Usar ASVS para convertir la concienciación en requisitos de seguridad verificables

El OWASP Top 10 es el documento más citado de la seguridad de aplicaciones y también el que más se usa mal. Es una clasificación, revisada periódicamente y basada en datos, de las categorías de debilidades que aparecen con más frecuencia en aplicaciones reales: una forma de dirigir la atención, escrita para concienciar y no para certificar. Leído así, tiene un valor real: las categorías son lo bastante amplias como para sobrevivir a las modas de los frameworks, y cada una nombra un tipo de error que les ha costado muy caro a las organizaciones.

A continuación tienes cada categoría con un fallo concreto, del tipo que aparece en código corriente, y el control específico que lo elimina. El enfoque es defensivo en todo momento: lo justo sobre cómo se explota la debilidad para reconocerla en una revisión, acompañado de la corrección. Cuando un tema merece más profundidad que un resumen, hay un enlace para seguir: la visión arquitectónica está en arquitectura de seguridad de aplicaciones, y el manejo de tokens específico de las APIs, en proteger APIs REST con JWT.

Control de acceso roto

El eterno número uno: un usuario autenticado que llega a datos u operaciones que pertenecen a otra persona. La forma clásica es un identificador tomado directamente de la petición y usado para obtener un registro, sin comprobar que el registro pertenece a quien llama.

// Vulnerable: cualquier usuario autenticado puede leer cualquier factura cambiando el número
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable long id) {
    return invoices.findById(id).orElseThrow(NotFoundException::new);
}

// Corregido: la consulta queda acotada al principal autenticado
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable long id, Authentication auth) {
    return invoices.findByIdAndCustomer(id, auth.getName())
                   .orElseThrow(NotFoundException::new);   // 404 en lugar de 403: no revelar nada
}

Variantes relacionadas: escalada de privilegios a través de una ruta de administración sin proteger, un identificador de rol o de tenant aceptado desde un parámetro de la petición, forced browsing hacia un endpoint que la interfaz nunca enlaza y directory traversal en una descarga de ficheros. Los controles son estructurales: denegar por defecto, para que una ruta nueva esté protegida hasta que se abra; acotar cada consulta al principal; obtener el tenant del token y no del payload; y escribir un test por endpoint que compruebe que el identificador de otro usuario devuelve 404. Las reglas a nivel de método en el servicio, como se muestra en fundamentos de Spring Security, detectan lo que se les escapa a los patrones de URL.

Fallos criptográficos

Esta categoría trata de datos sensibles que no están protegidos como deberían: se transmiten en claro, se almacenan sin cifrar o se protegen con algo que solo parece criptografía. Los hallazgos típicos son un servicio interno que todavía acepta HTTP sin cifrar, copias de seguridad de la base de datos escritas sin cifrar en un almacenamiento compartido, datos de tarjetas o de salud conservados mucho después de que dejaran de hacer falta, contraseñas almacenadas con un hash rápido y un «cifrado» casero hecho con XOR o base64.

Para corregirlo, primero clasifica los datos —no puedes proteger lo que no has identificado—; después exige TLS 1.3 en todas partes, también dentro de la red, cifra en reposo con claves custodiadas en un servicio gestionado de claves, aplica a las contraseñas una función de hash memory-hard y usa cifrado autenticado, como AES-GCM con un nonce único por mensaje, en lugar de algoritmos de cifrado por bloques usados en crudo. Borra lo que ya no necesites: los datos que no tienes no se pueden filtrar. Las primitivas en sí se explican en fundamentos de seguridad de la información.

Inyección

Hay inyección siempre que una entrada no confiable se concatena en algo que un intérprete analiza: SQL, un comando del sistema operativo, filtros LDAP, XPath, expresiones de plantillas. El caso de la base de datos sigue siendo el más dañino.

// Vulnerable: la entrada pasa a formar parte de la sentencia
String sql = "SELECT * FROM users WHERE email = '" + email + "'";

// Corregido: el valor nunca puede cambiar la estructura de la sentencia
String sql = "SELECT id, email, display_name FROM users WHERE email = ?";
jdbcTemplate.query(sql, ps -> ps.setString(1, email), USER_MAPPER);

Las sentencias parametrizadas son la respuesta siempre que el intérprete las admita; las prácticas que las rodean se tratan en buenas prácticas de SQL. Cuando la estructura realmente varía —una columna de ordenación, un nombre de tabla—, valida contra una allow-list de identificadores conocidos en lugar de escapar. Para las llamadas al sistema operativo, pasa un array de argumentos en vez de una cadena de shell. El cross-site scripting también entra aquí: el intérprete es el navegador, y la corrección es la codificación de salida contextual que hace el motor de plantillas, más una Content Security Policy que prohíba los scripts inline como segunda capa.

Diseño inseguro

Esta categoría existe para separar los fallos de diseño de los fallos de código. Un restablecimiento de contraseña que verifica la identidad con preguntas cuya respuesta puede averiguar un atacante, un flujo de reservas sin límite de cuántas plazas puede retener una sesión, un proceso de reembolso sin segunda aprobación: todo esto puede implementarse sin un solo error y seguir siendo explotable, porque el propio mecanismo es inadecuado.

Nada de lo que se encuentre en una revisión de código lo arregla. La respuesta es el modelado de amenazas antes de la implementación, casos de abuso escritos junto a las historias de usuario, arquitecturas de referencia para problemas recurrentes como la autenticación y los pagos, y límites diseñados dentro de los flujos en lugar de añadidos después de un incidente. Trata «funciona según la especificación» como un hallazgo cuando el problema es la propia especificación.

Configuración de seguridad incorrecta

La causa de exposición más habitual en el mundo real, y la más fácil de corregir: credenciales de administración por defecto todavía activas, páginas de error detalladas que muestran stack traces en producción, listado de directorios activado, un endpoint de actuator o de gestión accesible sin autenticación, una política CORS demasiado permisiva, almacenamiento en la nube abierto a Internet, cabeceras de seguridad ausentes.

# Perfil de producción: exponer lo mínimo y no decirle nada útil a un atacante
server:
  error:
    include-stacktrace: "never"
    include-message: "never"
management:
  endpoints:
    web:
      exposure:
        include: "health,info"
  endpoint:
    health:
      show-details: "when-authorized"

Refuerza la seguridad construyendo los entornos a partir de código, para que la configuración se pueda revisar y sea idéntica en todas partes; eliminando aplicaciones de ejemplo y funcionalidades que no se usan; ejecutando un escaneo de configuración en el pipeline; y definiendo las cabeceras de respuesta estándar: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options y un Referrer-Policy restrictivo. Automatízalo: una checklist aplicada a mano se desvía en menos de un mes.

Componentes vulnerables y obsoletos

Las aplicaciones modernas son, sobre todo, código de otros, y una vulnerabilidad conocida en una librería muy extendida se busca a escala de Internet a las pocas horas de hacerse pública. El riesgo no tiene nada de exótico: es un framework con tres versiones mayores de retraso, una dependencia transitiva que nadie sabía que estaba ahí o una imagen base que lleva un año sin reconstruirse.

Genera un SBOM en tiempo de build, escanea cada commit contra una base de avisos de seguridad y automatiza las actualizaciones rutinarias para que las personas solo se ocupen de las difíciles. Prefiere librerías con un historial de mantenimiento activo, elimina las dependencias que ya no usas y reconstruye las imágenes de contenedor con regularidad para que los paquetes del sistema operativo se mantengan al día. La integración continua es el lugar adecuado para todo esto: consulta integración continua para ver cómo mantener estas comprobaciones lo bastante rápidas como para que nadie las desactive.

Fallos de identificación y autenticación

Debilidades a la hora de demostrar quién es alguien: credential stuffing contra un login sin rate limiting, identificadores de sesión que sobreviven a un cambio de privilegios, tokens que nunca caducan, un enlace de restablecimiento de contraseña válido durante días y reutilizable, autenticación multifactor que se puede saltar llamando a un endpoint legacy.

Los controles son bien conocidos: comparar las contraseñas con listas de filtraciones en lugar de imponer reglas de composición, ofrecer autenticación multifactor resistente al phishing, regenerar el identificador de sesión al iniciar sesión, hacer caducar e invalidar las sesiones al cerrar sesión y al cambiar la contraseña, hacer que los tokens de restablecimiento sean de un solo uso y de corta duración, aplicar retardos crecientes en lugar de bloqueos permanentes y devolver mensajes idénticos exista o no la cuenta. El panorama completo está en gestión de identidades y accesos.

Fallos de integridad del software y de los datos

Esta categoría cubre confiar en código o datos cuya procedencia no has verificado: un build que descarga un plugin de una fuente sin versión fijada, un pipeline con acceso de escritura a producción y sin revisión de su propia configuración, un mecanismo de actualización automática sin comprobación de firmas y —el caso clásico a nivel de aplicación— deserializar datos controlados por un atacante en tipos arbitrarios.

// Vulnerable: la deserialización nativa de bytes no confiables puede instanciar cualquier cosa que permita el classpath
Object payload = new ObjectInputStream(request.getInputStream()).readObject();

// Corregido: un formato de datos sin resolución de tipos, ligado a una estructura conocida
OrderRequest payload = jsonMapper.readValue(request.getInputStream(), OrderRequest.class);

Además, fija las versiones de las dependencias con checksums, firma los artefactos y verifica las firmas antes del despliegue, somete los sistemas de build al mismo control de acceso que producción y desactiva el manejo polimórfico de tipos en las librerías JSON salvo que una necesidad concreta lo justifique y una allow-list lo restrinja.

Fallos de registro y monitorización de seguridad

No es una vía de entrada, pero sí la razón de que una brecha dure meses en lugar de horas. Los síntomas son reconocibles: inicios de sesión fallidos que no se registran, ninguna alerta ante denegaciones de autorización o volúmenes de datos inusuales, logs guardados solo en la máquina que los generó y ninguna sincronización de relojes para correlacionar entre servicios.

Registra los intentos de autenticación, las denegaciones de control de acceso, los cambios de privilegios, las acciones administrativas y los fallos de validación de entrada en la frontera, con el actor, la acción, el objetivo, el resultado, la marca de tiempo y un identificador de correlación. Envíalos fuera del host a un almacén que la aplicación no pueda reescribir, consérvalos el tiempo suficiente para investigar y alerta sobre patrones en lugar de sobre eventos aislados. Después, ensaya: una alerta a la que nadie ha respondido nunca no es un control. Y ten cuidado con lo que escribes: contraseñas, tokens y datos personales en los logs crean una segunda copia de esos datos con una protección más débil.

Server-side request forgery

Hay SSRF cuando una aplicación obtiene una URL proporcionada por un usuario —el destino de un webhook, un importador de imágenes, un conversor de documentos— y el atacante la dirige hacia dentro, a servicios internos o a un endpoint de metadatos de la nube que entrega credenciales a cualquiera que las pida desde la instancia.

Mitiga por capas, porque la validación de URLs por sí sola es famosa por sus fugas: valida contra una allow-list de hosts permitidos en lugar de una deny-list, resuelve el nombre de host y comprueba que la dirección resultante es pública antes de conectar, desactiva el seguimiento de redirecciones o vuelve a validar cada salto, exige el esquema esperado y aplica al componente que hace las peticiones reglas de salida de red que solo permitan lo que legítimamente necesita. En instancias en la nube, exige el servicio de metadatos reforzado que requiere un token de sesión. Devuelve un resultado genérico a quien llama, para que el contenido y el tiempo de respuesta no se conviertan en un escáner de puertos interno.

Usar bien la lista

Tres hábitos separan a los equipos que sacan partido del Top 10 de los que simplemente lo citan.

Trátalo como un punto de partida, no como la meta. Es una clasificación de categorías frecuentes, no una especificación de lo que es suficiente. La ausencia de estas diez debilidades no significa que una aplicación sea segura; tu lógica de negocio tiene riesgos que ninguna lista genérica puede nombrar.

Convierte la concienciación en requisitos. El Application Security Verification Standard es el documento complementario para eso: varios cientos de requisitos verificables organizados por tema, con tres niveles de rigor para que cada equipo elija el que corresponde a su riesgo. Elige un nivel, adopta los requisitos pertinentes como criterios de aceptación y habrás sustituido «hemos tenido en cuenta OWASP» por afirmaciones que un revisor puede comprobar. El API Security Top 10 cumple la misma función para los riesgos específicos de las APIs.

Automatiza lo que se puede automatizar y revisa lo que no. El escaneo de dependencias y de configuración va en el pipeline, junto al análisis estático; los fallos de control de acceso y de lógica de negocio necesitan personas que entiendan el dominio. Sobre todo, escribe un test de regresión para cada defecto de seguridad que corrijas, para que la misma debilidad no pueda volver de la mano de una nueva funcionalidad.

Conclusiones clave

  • El Top 10 es una clasificación de concienciación de categorías de debilidades, no un estándar: usa ASVS cuando necesites requisitos verificables.
  • El control de acceso roto encabeza la lista porque depende del dominio y es invisible para los escáneres; deniega por defecto, acota las consultas al principal y prueba con un segundo usuario.
  • La inyección se resuelve manteniendo los datos fuera de la gramática del intérprete: sentencias parametrizadas, arrays de argumentos y codificación de salida contextual.
  • El diseño inseguro no se arregla a base de código; el modelado de amenazas y los casos de abuso son la única solución.
  • La configuración incorrecta y los componentes obsoletos son las categorías en las que más rinde automatizar, mediante infraestructura como código, SBOM y escaneo de dependencias en cada commit.
  • El registro, la monitorización y una respuesta ensayada deciden cuánto dura una brecha, y la SSRF exige una defensa por capas tanto a nivel de URL como de red.

Fuentes y lecturas recomendadas

Preguntas frecuentes

¿El OWASP Top 10 es un estándar de seguridad?
No, y OWASP lo dice de forma explícita. Es un documento de concienciación elaborado a partir de datos sobre qué categorías de debilidades aparecen con más frecuencia, pensado para abrir conversaciones y fijar prioridades. Un equipo que haya corregido las diez todavía tiene trabajo por delante. Cuando necesitas requisitos verificables —algo con lo que un revisor pueda aprobar o rechazar un build—, el documento escrito para ese fin es el Application Security Verification Standard.
¿Por qué el control de acceso roto ocupa siempre el primer puesto?
Porque no se puede detectar buscando patrones. Un escáner automático ve una petición que devuelve datos y no tiene forma de saber si ese usuario tenía derecho a ellos; solo alguien que entiende el dominio puede decirlo. Además, el control de acceso está repartido por todos los endpoints, así que basta una ruta nueva añadida sin la comprobación para reabrir el riesgo, por muy bueno que sea el resto de la aplicación.
¿Cómo mantengo bajo control las dependencias vulnerables?
Genera una lista de materiales de software (SBOM) en tiempo de build, ejecuta un escáner de dependencias en cada commit contra una base de avisos de seguridad mantenida y automatiza las actualizaciones rutinarias para que solo las difíciles requieran atención humana. Fija versiones y verifica checksums para que el build sea reproducible, elimina las librerías que ya no usas y controla el grafo de dependencias transitivas, no solo las declaraciones directas.
¿Qué diferencia hay entre un diseño inseguro y un bug de seguridad?
Un bug es un diseño correcto mal implementado —una comprobación que falta, una comparación errónea— y se corrige en el código. Un diseño inseguro es fiel a su especificación, pero no hay forma de hacerlo seguro, como un restablecimiento de contraseña basado en preguntas cuya respuesta cualquiera puede averiguar. Ninguna cantidad de código cuidadoso lo arregla; hay que cambiar el mecanismo, y por eso el modelado de amenazas debe hacerse antes de la implementación.