El OWASP Top 10 es la referencia más usada sobre riesgos de seguridad en aplicaciones web, y el OWASP Top 10 2025 es su octava edición. Se presentó en noviembre de 2025 en OWASP Global AppSec y está publicado en top10.owasp.org. Según OWASP, esta edición analizó 589 CWEs con datos de más de 2,8 millones de aplicaciones, y dos de las diez categorías salieron de una encuesta a la comunidad en lugar de los datos crudos.
En esta guía recorremos las diez categorías. Para cada una vas a ver qué significa en la práctica, un snippet vulnerable, la versión corregida y cómo detectarla en tu propio código.
La lista OWASP Top 10 2025
| 2025 | Categoría | Cambio vs 2021 |
|---|---|---|
| A01 | Broken Access Control | Sigue #1, ahora incluye SSRF |
| A02 | Security Misconfiguration | Sube desde el #5 |
| A03 | Software Supply Chain Failures | Nueva, amplía Vulnerable and Outdated Components |
| A04 | Cryptographic Failures | Baja desde el #2 |
| A05 | Injection | Baja desde el #3 |
| A06 | Insecure Design | Baja desde el #4 |
| A07 | Authentication Failures | Renombrada (antes Identification and Authentication Failures) |
| A08 | Software or Data Integrity Failures | Misma posición |
| A09 | Security Logging and Alerting Failures | Renombrada, foco en alertas |
| A10 | Mishandling of Exceptional Conditions | Nueva |
La gran lectura de 2025: el riesgo se movió de "mi código tiene un bug" a "mi configuración, mis dependencias y mi pipeline tienen un bug". Dos de las tres primeras categorías son cosas que un code review tradicional casi no mira.
A01: Broken Access Control
Usuarios que pueden ver o hacer cosas que no deberían: leer la factura de otro cliente, llamar un endpoint de admin o cambiar un ID en la URL y obtener datos ajenos (IDOR). En 2025 OWASP también metió acá el Server-Side Request Forgery (SSRF), porque en el fondo es el servidor accediendo a un recurso al que no debería.
// Vulnerable: cualquier usuario logueado lee cualquier factura
app.get("/api/invoices/:id", auth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
res.json(invoice);
});
// Corregido: la query se limita al usuario actual
app.get("/api/invoices/:id", auth, async (req, res) => {
const invoice = await Invoice.findOne({ _id: req.params.id, userId: req.user.id });
if (!invoice) return res.status(404).end();
res.json(invoice);
});Cómo detectarlo: escribí tests que se logueen como el usuario A y pidan recursos del usuario B. Los escáneres que solo buscan patrones sufren con esto, porque el bug es un chequeo que falta. Necesitás herramientas que entiendan el flujo entre request, sesión y query, más pasadas de pentest con IA o manual sobre los endpoints sensibles.
A02: Security Misconfiguration
Debug activado en producción, credenciales por defecto, CORS demasiado permisivo, páginas de error verbosas, buckets públicos. Subió al #2 porque las apps modernas son en gran parte configuración: recursos cloud, contenedores, archivos de CI y settings del framework.
# Vulnerable (Terraform): un bucket que cualquiera en internet puede leer
resource "aws_s3_bucket_acl" "reports" {
bucket = aws_s3_bucket.reports.id
acl = "public-read"
}
# Corregido: privado y con el acceso público bloqueado explícitamente
resource "aws_s3_bucket_public_access_block" "reports" {
bucket = aws_s3_bucket.reports.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}Cómo detectarlo: escaneo de Infrastructure as Code en cada pull request (Terraform, manifiestos de Kubernetes, Dockerfiles, workflows de GitHub Actions), más un DAST básico para headers y páginas de error en la app corriendo.
A03: Software Supply Chain Failures
La categoría nueva que reemplaza y amplía "Vulnerable and Outdated Components". Cubre toda la cadena: dependencias con CVEs conocidos, paquetes maliciosos o con typosquatting, pipelines de build comprometidos y actions de terceros que corrés con acceso a tus secretos.
# Vulnerable: referencias mutables, cualquier cosa puede cambiar sin que te enteres
"dependencies": { "some-lib": "*" }
- uses: some-org/deploy-action@main
# Corregido: lockfile + versiones exactas, actions fijadas a un commit SHA
"dependencies": { "some-lib": "4.2.1" } # commiteá package-lock.json e instalá con npm ci
- uses: some-org/deploy-action@3f1c2a9e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3aCómo detectarlo: el Software Composition Analysis (SCA) compara tus lockfiles contra bases de CVEs conocidos. Sumalo al CI para que una versión vulnerable no se mergee en silencio, y revisá los workflows buscando actions sin fijar y permissions demasiado amplios.
A04: Cryptographic Failures
Datos sensibles expuestos por criptografía débil o inexistente: contraseñas hasheadas con MD5 o SHA-1, datos por HTTP plano, claves hardcodeadas, números aleatorios predecibles para tokens.
# Vulnerable (Python): hash rápido y sin salt, trivial de crackear
import hashlib
stored = hashlib.md5(password.encode()).hexdigest()
# Corregido: algoritmo de hashing de contraseñas lento y con salt
from argon2 import PasswordHasher
ph = PasswordHasher()
stored = ph.hash(password)
ph.verify(stored, attempt) # lanza excepción si no coincideCómo detectarlo: el SAST encuentra algoritmos débiles, Math.random() usado para tokens y claves hardcodeadas. Un secret scanner encuentra claves commiteadas, incluso las que borraste después pero siguen en el historial de git.
A05: Injection
Input no confiable interpretado como código o query: SQL injection, NoSQL injection, command injection y cross-site scripting (XSS), que OWASP incluye acá. El caso SQL lo vemos a fondo en nuestra guía de inyección SQL.
# Vulnerable: input del usuario concatenado en el SQL
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")
# Corregido: query parametrizada, el driver se encarga del escape
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))Cómo detectarlo: acá el SAST brilla, porque puede seguir el input desde un parámetro del request hasta un sink peligroso. El DAST lo confirma desde afuera. Mirá SAST vs DAST para saber cuándo usar cada uno.
A06: Insecure Design
Fallas en la lógica, no en la implementación. El código hace exactamente lo que se diseñó, y el diseño es explotable. Ejemplo clásico: un reset de contraseña con un código numérico corto y sin límite de intentos.
# Diseño vulnerable: código de 4 dígitos, sin vencimiento, intentos ilimitados
code = random.randint(1000, 9999)
if request.form["code"] == str(user.reset_code):
allow_reset(user)
# Diseño corregido: token largo y aleatorio, vencimiento, límite de intentos
token = secrets.token_urlsafe(32)
user.reset_token_hash = sha256(token)
user.reset_expires_at = now() + timedelta(minutes=15)
# al verificar: chequear vencimiento, comparar hashes en tiempo constante, bloquear tras 5 fallosCómo detectarlo: ningún escáner encuentra fallas de diseño de forma confiable mirando solo la sintaxis. Threat modeling antes de construir, revisión de flujos como registro, reset y checkout, y pentesting (mirá qué es el pentesting) son las defensas realistas.
A07: Authentication Failures
Login y sesiones débiles: credential stuffing sin rate limit, reglas de contraseña flojas, sesiones que nunca vencen y JWTs que se decodifican sin verificar.
# Vulnerable (PyJWT): la firma nunca se verifica
payload = jwt.decode(token, options={"verify_signature": False})
# Corregido: verificar la firma y fijar el algoritmo permitido
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])Cómo detectarlo: el SAST marca verificaciones desactivadas y settings de sesión débiles. Probá los endpoints de login buscando rate limiting y enumeración de cuentas (mensajes distintos para "usuario inexistente" y "contraseña incorrecta").
A08: Software or Data Integrity Failures
Confiar en código o datos sin verificar su integridad: deserialización insegura, auto-updates sin chequeo de firma, plugins o scripts cargados desde fuentes no confiables.
# Vulnerable: deserializar bytes del atacante puede ejecutar código
data = pickle.loads(request.data)
# Corregido: formato de solo datos y validación del esquema
data = json.loads(request.data)
order = OrderSchema().load(data) # rechaza campos y tipos inesperadosCómo detectarlo: el SAST detecta deserializadores peligrosos (pickle, ObjectInputStream en Java, loaders de YAML inseguros). En el CI, verificá checksums o firmas de los artefactos que descargás.
A09: Security Logging and Alerting Failures
Si no ves un ataque, no podés responder. El nombre 2025 pone el foco en las alertas: los logs que nadie lee no cuentan. El error opuesto también es común: loguear secretos.
# Vulnerable: loguea la contraseña y no registra los logins fallidos
logger.info(f"login attempt {email} {password}")
# Corregido: evento estructurado, sin secretos, alimenta una regla de alerta
logger.warning("auth.login_failed", extra={"email": email, "ip": ip})
# alerta: más de 20 auth.login_failed desde una IP en 5 minutosCómo detectarlo: revisá qué eventos de seguridad se loguean (logins, accesos denegados, acciones de admin), buscá secretos en los statements de log y dispará una alerta real en staging para comprobar que funciona.
A10: Mishandling of Exceptional Conditions
Nueva en 2025. Programas que no previenen, detectan ni responden bien a situaciones inusuales: excepciones sin capturar, mensajes de error que filtran detalles internos, transacciones a medias y la peor de todas, fallar abierto (fail open).
# Vulnerable: si el servicio de permisos se cae, todos pasan
try:
allowed = permissions.check(user, "delete_project")
except Exception:
allowed = True
# Corregido: fallar cerrado y loguearlo
try:
allowed = permissions.check(user, "delete_project")
except Exception:
logger.exception("permission check failed")
allowed = FalseCómo detectarlo: buscá bloques except/catch genéricos que cambian decisiones de seguridad, stack traces devueltos al cliente y rollbacks que faltan. Los tests de inyección de fallas (hacé que una dependencia dé timeout y mirá qué pasa) funcionan muy bien.
OWASP API Security Top 10
Si lo que entregás es sobre todo una API, revisá también el OWASP API Security Top 10 (lo que mucha gente busca como "owasp top 10 api"). Su última edición es de 2023 y se enfoca en riesgos propios de APIs:
- API1: Broken Object Level Authorization (BOLA)
- API2: Broken Authentication
- API3: Broken Object Property Level Authorization
- API4: Unrestricted Resource Consumption
- API5: Broken Function Level Authorization
- API6: Unrestricted Access to Sensitive Business Flows
- API7: Server Side Request Forgery
- API8: Security Misconfiguration
- API9: Improper Inventory Management
- API10: Unsafe Consumption of APIs
Fijate que la autorización aparece tres veces. En APIs, los bugs de control de acceso son lejos el hallazgo grave más común, y eso coincide con que Broken Access Control siga #1 en el OWASP Top 10 principal.
Cómo cubrir el OWASP Top 10 en la práctica
| Técnica | Mejor para |
|---|---|
| SAST | A01, A04, A05, A07, A08, A10 en el código |
| SCA | A03, dependencias con CVEs conocidos |
| Secret scanning | A04, claves y tokens filtrados |
| Escaneo de IaC | A02, malas configuraciones de cloud, contenedores y CI |
| DAST | A02, A05, A07 desde afuera, con la app corriendo |
| Pentest / threat modeling | A01 y A06, lógica de negocio y diseño |
La respuesta práctica es correr la parte automatizada en cada cambio, que es de lo que se trata DevSecOps, y reservar el tiempo humano para diseño y lógica. Un escáner de vulnerabilidades que lee tu repo cubre la mayor parte de la tabla en una sola pasada.
Por ejemplo, con Nurbak conectás GitHub y escaneás un repositorio: el modelo de IA propio y self-hosted de Nurbak analiza el código (el análisis no manda tu código a OpenAI ni a Anthropic), reporta vulnerabilidades explotables con archivo y línea, cruza las dependencias contra CVEs conocidos, marca malas configuraciones de GitHub Actions, Docker, Terraform y Kubernetes, y encuentra secretos en el historial de git. El escaneo gratis te muestra completos los 3 hallazgos más importantes, así ves dónde está parado tu repo frente al OWASP Top 10 antes de decidir nada.
Resumen
- El OWASP Top 10 2025 lo encabezan Broken Access Control, Security Misconfiguration y Software Supply Chain Failures.
- SSRF ahora es parte de A01, y A10 (Mishandling of Exceptional Conditions) es nueva: nunca falles abierto.
- Cada categoría necesita una técnica de detección distinta; ninguna herramienta sola cubre las diez.
- Si tenés APIs, revisá también el OWASP API Security Top 10.
- Arrancá con una base automatizada: escaneá tu repositorio y corregí primero los hallazgos de mayor impacto.
