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

2025CategoríaCambio vs 2021
A01Broken Access ControlSigue #1, ahora incluye SSRF
A02Security MisconfigurationSube desde el #5
A03Software Supply Chain FailuresNueva, amplía Vulnerable and Outdated Components
A04Cryptographic FailuresBaja desde el #2
A05InjectionBaja desde el #3
A06Insecure DesignBaja desde el #4
A07Authentication FailuresRenombrada (antes Identification and Authentication Failures)
A08Software or Data Integrity FailuresMisma posición
A09Security Logging and Alerting FailuresRenombrada, foco en alertas
A10Mishandling of Exceptional ConditionsNueva

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@3f1c2a9e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3a

Có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 coincide

Có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 fallos

Có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 inesperados

Có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 minutos

Có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 = False

Có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:

  1. API1: Broken Object Level Authorization (BOLA)
  2. API2: Broken Authentication
  3. API3: Broken Object Property Level Authorization
  4. API4: Unrestricted Resource Consumption
  5. API5: Broken Function Level Authorization
  6. API6: Unrestricted Access to Sensitive Business Flows
  7. API7: Server Side Request Forgery
  8. API8: Security Misconfiguration
  9. API9: Improper Inventory Management
  10. 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écnicaMejor para
SASTA01, A04, A05, A07, A08, A10 en el código
SCAA03, dependencias con CVEs conocidos
Secret scanningA04, claves y tokens filtrados
Escaneo de IaCA02, malas configuraciones de cloud, contenedores y CI
DASTA02, A05, A07 desde afuera, con la app corriendo
Pentest / threat modelingA01 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.