Si desarrollas aplicaciones web o APIs, tarde o temprano vas a escuchar hablar del OWASP Top 10. Es la lista de referencia de los riesgos de seguridad más importantes en aplicaciones web, y la usan equipos de desarrollo, auditores y empresas de todo el mundo para priorizar qué revisar primero.
En esta guía te explico cada categoría de la edición OWASP Top 10:2025 en lenguaje claro, con un ejemplo de cómo se ve el problema y, sobre todo, código defensivo para prevenirlo. Los ejemplos usan Node.js (Express) y Python, pero las ideas aplican a cualquier lenguaje.
¿Qué es OWASP y para qué sirve el Top 10?
OWASP (Open Worldwide Application Security Project) es una fundación sin ánimo de lucro que publica guías, herramientas y estándares abiertos de seguridad de software. El OWASP Top 10 se actualiza cada pocos años a partir de datos aportados por la industria y de una encuesta a la comunidad.
Ten en cuenta dos cosas: no es una lista exhaustiva (es un punto de partida) y no es un estándar de verificación; para eso existe el ASVS de OWASP. Aun así, si tu equipo domina estas diez categorías, ya evita una gran parte de los errores más comunes.
A01:2025 - Control de acceso roto
Ocurre cuando un usuario puede hacer o ver algo que no le corresponde: consultar la factura de otro cliente cambiando el número en la URL, entrar a un panel de administración o modificar datos ajenos. En esta edición también incluye la falsificación de solicitudes del lado del servidor (SSRF).
Cómo prevenirlo: deniega por defecto, verifica permisos en el servidor en cada solicitud (nunca solo ocultando botones en el frontend) y comprueba que el recurso pertenezca al usuario.
// Express: el recurso debe pertenecer al usuario autenticado
app.get('/facturas/:id', requireAuth, async (req, res) => {
const factura = await Factura.findById(req.params.id);
if (!factura || factura.ownerId !== req.user.id) {
return res.status(404).json({ error: 'No encontrado' });
}
res.json(factura);
});
A02:2025 - Configuración de seguridad incorrecta
Modo de depuración activo en producción, mensajes de error con trazas completas, cuentas por defecto, servicios en la nube con permisos públicos o cabeceras de seguridad ausentes. Es un error de configuración, no de lógica, y por eso es tan frecuente.
Cómo prevenirlo: configuraciones separadas por entorno, plantillas endurecidas y revisadas, nada de credenciales por defecto y procesos automáticos que verifiquen la configuración antes de desplegar.
# settings.py de Django en producción
DEBUG = False
ALLOWED_HOSTS = ["www.ejemplo.com"]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
A03:2025 - Fallas en la cadena de suministro de software
Tu aplicación depende de cientos de paquetes de terceros, imágenes de contenedores y herramientas de compilación. Si una dependencia tiene una vulnerabilidad conocida, si alguien publica un paquete malicioso con un nombre parecido o si se compromete tu pipeline de CI/CD, el problema llega a producción contigo.
Cómo prevenirlo: mantén un inventario de dependencias (SBOM), usa archivos de bloqueo, elimina paquetes que no uses, revisa alertas de seguridad de forma automática y protege el pipeline con permisos mínimos.
# Instalar exactamente las versiones del archivo de bloqueo
npm ci
# Revisar vulnerabilidades conocidas en dependencias de producción
npm audit --omit=dev
# Python: exigir hashes para cada paquete
pip install --require-hashes -r requirements.txt
A04:2025 - Fallas criptográficas
Datos sensibles sin cifrar en tránsito o en reposo, algoritmos obsoletos, claves escritas en el código o, el clásico, contraseñas guardadas en texto plano o con MD5/SHA-1.
Cómo prevenirlo: HTTPS en todo, cifrado de datos sensibles en reposo, secretos en un gestor de secretos o variables de entorno, y contraseñas almacenadas con un algoritmo de hash lento y diseñado para ello, como Argon2id, scrypt o bcrypt. La guía de almacenamiento de contraseñas de OWASP explica los parámetros recomendados.
# Python con argon2-cffi
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher()
hash_guardado = ph.hash(password) # al registrar al usuario
try:
ph.verify(hash_guardado, intento) # al iniciar sesión
except VerifyMismatchError:
negar_acceso()
A05:2025 - Inyección
Sucede cuando datos del usuario se interpretan como parte de una instrucción: inyección SQL, inyección de comandos del sistema o cross-site scripting (XSS), donde el navegador termina ejecutando código que vino de un comentario o un formulario.
Cómo prevenirlo: consultas parametrizadas u ORM, validación de entradas con listas de valores permitidos y codificación de la salida según el contexto. Nunca construyas consultas concatenando texto. Más detalles en las guías de consultas parametrizadas y prevención de XSS.
# Python (psycopg): el dato viaja separado de la consulta
cursor.execute(
"SELECT id, nombre FROM usuarios WHERE email = %s",
(email,)
)
// JavaScript en el navegador: mostrar texto, no interpretar HTML
// Evita: elemento.innerHTML = comentario;
elemento.textContent = comentario;
A06:2025 - Diseño inseguro
Aquí el código puede estar "bien escrito", pero la lógica es insegura desde el diseño: confiar en el precio que envía el navegador, permitir intentos ilimitados de recuperar una cuenta o no prever que alguien automatice un proceso pensado para humanos.
Cómo prevenirlo: modelado de amenazas en la etapa de diseño, historias de usuario con criterios de seguridad ("¿cómo podría abusarse de esta función?"), límites de negocio y pruebas de casos de abuso.
// El total se calcula en el servidor con datos confiables
const cantidad = Number.parseInt(req.body.cantidad, 10);
if (!Number.isInteger(cantidad) || cantidad < 1 || cantidad > 10) {
return res.status(400).json({ error: 'Cantidad no válida' });
}
const producto = await Producto.findById(req.body.productoId);
const total = producto.precio * cantidad;
A07:2025 - Fallas de autenticación
Permitir contraseñas débiles o ya filtradas, no limitar los intentos de inicio de sesión, sesiones que nunca expiran, identificadores de sesión expuestos o recuperación de cuenta fácil de abusar.
Cómo prevenirlo: ofrece verificación en dos pasos o passkeys, limita los intentos, bloquea contraseñas comunes o filtradas, regenera la sesión al iniciar sesión y configura bien las cookies.
const rateLimit = require('express-rate-limit');
// Máximo 10 intentos de login cada 15 minutos por IP
app.use('/login', rateLimit({ windowMs: 15 * 60 * 1000, limit: 10 }));
// Cookie de sesión protegida
res.cookie('sid', sessionId, {
httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 1000
});
A08:2025 - Fallas de integridad de software o datos
Confiar en código o datos sin verificar su integridad: cargar scripts desde un CDN que podría ser modificado, aceptar actualizaciones sin firma o deserializar objetos que vienen del usuario con formatos que permiten ejecutar código.
Cómo prevenirlo: firmas digitales para actualizaciones y artefactos, Subresource Integrity (SRI) para recursos externos y formatos de datos simples como JSON en lugar de serializaciones nativas para datos no confiables.
<!-- El navegador bloquea el script si su contenido cambia -->
<script src="https://cdn.ejemplo.com/libreria.min.js"
integrity="sha384-HASH_PUBLICADO_POR_EL_PROVEEDOR"
crossorigin="anonymous"></script>
# Python: para datos externos usa JSON, nunca pickle
import json
datos = json.loads(cuerpo_de_la_solicitud)
A09:2025 - Fallas en el registro y las alertas de seguridad
Si nadie registra los inicios de sesión fallidos, los cambios de permisos o los errores de validación, un ataque puede pasar semanas sin que lo notes. Y si se registra pero nadie recibe alertas, el efecto es casi el mismo.
Cómo prevenirlo: registra eventos de seguridad con contexto suficiente, centraliza los logs, define alertas para patrones sospechosos y ten un plan de respuesta. Igual de importante: no registres datos sensibles.
import logging
logger = logging.getLogger("seguridad")
logger.warning("login_fallido usuario=%s ip=%s", usuario_id, ip_cliente)
# Nunca registres contraseñas, tokens de sesión ni números de tarjeta
A10:2025 - Manejo inadecuado de condiciones excepcionales
Es la categoría nueva de esta edición. Agrupa lo que pasa cuando la aplicación no maneja bien los errores y situaciones inesperadas: excepciones que revelan información interna, operaciones que quedan a medias o, lo más peligroso, sistemas que "fallan abiertos" y conceden acceso cuando algo sale mal.
Cómo prevenirlo: captura los errores donde ocurren, muestra mensajes genéricos al usuario, revierte transacciones incompletas y, ante la duda, deniega.
def puede_acceder(usuario, recurso):
try:
return servicio_permisos.verificar(usuario, recurso)
except Exception:
logger.exception("Error al verificar permisos")
return False # fallar cerrado: si hay duda, se deniega
Cómo llevar el OWASP Top 10 a tu día a día
- Incluye seguridad en las revisiones de código: una lista corta basada en estas diez categorías ayuda mucho.
- Automatiza: análisis estático (SAST), revisión de dependencias y escaneo de secretos en el pipeline.
- Prueba tu propia aplicación en entornos de prueba y, si es posible, con pruebas de penetración autorizadas.
- Capacita al equipo con laboratorios intencionalmente vulnerables creados para practicar en local, nunca contra sistemas de terceros.
- Usa los recursos gratuitos de OWASP: las OWASP Cheat Sheets tienen guías concretas para casi cualquier tema.
Preguntas frecuentes
¿Cuál es la diferencia entre el OWASP Top 10 2021 y el 2025?
La edición 2025 sube la configuración incorrecta al segundo lugar, amplía los componentes vulnerables a toda la cadena de suministro de software, integra SSRF dentro del control de acceso roto y agrega la categoría nueva de manejo de condiciones excepcionales. El control de acceso roto sigue en el primer puesto.
¿Cumplir el OWASP Top 10 hace que mi aplicación sea segura?
No por sí solo. Es una lista de concienciación con los riesgos más comunes. Para una verificación más completa, usa el OWASP ASVS, haz modelado de amenazas y realiza pruebas de seguridad periódicas.
¿Por dónde empiezo si tengo una aplicación ya en producción?
Empieza por lo que más impacto tiene: revisa el control de acceso en tus endpoints, actualiza dependencias con vulnerabilidades conocidas, asegúrate de que las contraseñas usen un hash adecuado y desactiva cualquier modo de depuración en producción.
Conclusión
El OWASP Top 10 no es teoría para auditores: es una lista de errores que los desarrolladores cometemos todos los días. Verificar permisos en el servidor, parametrizar consultas, cuidar las dependencias, guardar bien las contraseñas y fallar de forma segura resuelve una gran parte de los problemas. Toma una categoría por semana, revísala en tu código y en pocos meses tu aplicación será mucho más difícil de atacar.

0 comentarios:
Publicar un comentario