¿Qué es el Top 10 de aplicaciones web de OWASP?

El OWASP Top 10 es el documento de concienciación más reconocido en materia de seguridad de aplicaciones web. La edición de 2025 refleja el panorama de amenazas más reciente, introduciendo nuevas categorías como Fallos en la cadena de suministro de software y Mala gestión de condiciones excepcionales.

1️⃣ A01 - Control de acceso roto

Critical

Visión general

El control de acceso aplica la política de forma que los usuarios no puedan actuar fuera de los permisos previstos. Los fallos suelen provocar la divulgación no autorizada de información, la modificación o destrucción de datos, o la realización de una función empresarial fuera de los límites del usuario.

Riesgo

Los atacantes pueden aprovechar los fallos de control de acceso para acceder a las cuentas de otros usuarios, ver archivos confidenciales, modificar los datos de otros usuarios o cambiar los derechos de acceso.

Ejemplo de código vulnerable

Python ❌ Bad
# User ID taken directly from request without authorization check
@app.route('/api/user/<user_id>/profile')
def get_profile(user_id):
    user = db.get_user(user_id)  # No ownership check!
    return jsonify(user.to_dict())

Ejemplo de código seguro

Python ✅ Good
@app.route('/api/user/<user_id>/profile')
@login_required
def get_profile(user_id):
    # Verify the requesting user owns this resource
    if current_user.id != user_id and not current_user.is_admin:
        abort(403)

    user = db.get_user(user_id)
    if not user:
        abort(404)
    return jsonify(user.to_dict())

Lista de control de las medidas paliativas

2️⃣ A02 - Error de configuración de seguridad

High

Visión general

La mala configuración de la seguridad se produce cuando los ajustes de seguridad se definen, implementan o mantienen de forma incorrecta. Esto incluye la falta de refuerzo de la seguridad, la activación de funciones innecesarias, cuentas predeterminadas con contraseñas sin modificar, mensajes de error demasiado detallados y cabeceras de seguridad HTTP mal configuradas.

Riesgo

Los servidores, frameworks o servicios en la nube mal configurados pueden exponer datos sensibles, permitir accesos no autorizados o proporcionar a los atacantes información para planificar nuevos ataques.

Ejemplo de código vulnerable

Python ❌ Bad
# Debug mode enabled in production, verbose errors exposed
app = Flask(__name__)
app.config['DEBUG'] = True  # Exposes stack traces!
app.config['SECRET_KEY'] = 'default-secret'  # Default key!

Ejemplo de código seguro

Python ✅ Good
import os

app = Flask(__name__)
app.config['DEBUG'] = False
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']

@app.after_request
def set_security_headers(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    return response

Lista de control de las medidas paliativas

3️⃣ A03 - Fallos en la cadena de suministro de software

High

Visión general

Nuevo en 2025. Se centra en los riesgos relacionados con los componentes de terceros, las dependencias y las canalizaciones CI/CD. Los atacantes atacan la cadena de suministro de software comprometiendo paquetes, inyectando código malicioso en bibliotecas de código abierto o explotando vulnerabilidades de las canalizaciones de compilación.

Riesgo

Una sola dependencia comprometida puede afectar a miles de aplicaciones. Los ataques a la cadena de suministro pueden conducir al robo de datos, la instalación de puertas traseras o el compromiso completo del sistema con una detección mínima.

Ejemplo de código vulnerable

JavaScript ❌ Bad
// package.json with unpinned dependencies
{
  "dependencies": {
    "express": "*",           // Any version!
    "lodash": "^4.0.0",       // Wide range
    "unknown-pkg": "^1.0.0"   // Unvetted package
  }
}

Ejemplo de código seguro

JavaScript ✅ Good
// package.json with pinned versions + lockfile + audit
{
  "dependencies": {
    "express": "4.21.2",
    "lodash": "4.17.21"
  },
  "scripts": {
    "preinstall": "npx npm-audit-resolver",
    "integrity-check": "npm audit signatures"
  }
}
// Also: use package-lock.json, enable Dependabot/Renovate,
// generate SBOM, verify package provenance

Lista de control de las medidas paliativas

4️⃣ A04 - Fallos criptográficos

High

Visión general

Fallos relacionados con la criptografía que a menudo conducen a la exposición de datos sensibles. Esto incluye el uso de algoritmos obsoletos, la generación de claves débiles, la falta de cifrado de datos en tránsito o en reposo y la validación incorrecta de certificados.

Riesgo

Un cifrado deficiente o inexistente puede dejar al descubierto contraseñas, números de tarjetas de crédito, historiales médicos, información personal y secretos comerciales, lo que puede dar lugar a infracciones de la normativa (GDPR, PCI DSS).

Ejemplo de código vulnerable

Python ❌ Bad
import hashlib

# Storing passwords with weak hashing
def store_password(password: str) -> str:
    return hashlib.md5(password.encode()).hexdigest()  # MD5 is broken!

Ejemplo de código seguro

Python ✅ Good
import bcrypt

def store_password(password: str) -> bytes:
    # Use bcrypt with automatic salting
    salt = bcrypt.gensalt(rounds=12)
    return bcrypt.hashpw(password.encode(), salt)

def verify_password(password: str, hashed: bytes) -> bool:
    return bcrypt.checkpw(password.encode(), hashed)

Lista de control de las medidas paliativas

5️⃣ A05 - Inyección

Critical

Visión general

Los fallos de inyección se producen cuando se envían datos no fiables a un intérprete como parte de un comando o consulta. Las formas más comunes son la inyección SQL, la inyección NoSQL, la inyección de comandos OS y el Cross-Site Scripting (XSS). Los datos hostiles pueden engañar al intérprete para que ejecute comandos no deseados.

Riesgo

La inyección puede provocar pérdida de datos, corrupción, acceso no autorizado, toma completa del host o denegación de servicio. La inyección SQL por sí sola sigue siendo uno de los vectores de ataque más peligrosos y frecuentes.

Ejemplo de código vulnerable

Python ❌ Bad
# SQL injection via string concatenation
def get_user(username: str):
    query = f"SELECT * FROM users WHERE name = '{username}'"
    return db.execute(query)  # username = "' OR '1'='1"

Ejemplo de código seguro

Python ✅ Good
# Parameterized queries prevent SQL injection
def get_user(username: str):
    query = "SELECT * FROM users WHERE name = %s"
    return db.execute(query, (username,))

# For XSS prevention, escape output
from markupsafe import escape

def render_comment(comment: str) -> str:
    return f"<p>{escape(comment)}</p>"

Lista de control de las medidas paliativas

6️⃣ A06 - Diseño inseguro

High

Visión general

El diseño inseguro se refiere a los riesgos relacionados con defectos de diseño y arquitectura. Exige el uso de modelos de amenazas, patrones de diseño seguros y arquitecturas de referencia. Un diseño inseguro no puede solucionarse con una implementación perfecta; los controles de seguridad necesarios nunca se crearon para defenderse de ataques específicos.

Riesgo

Los fallos de diseño pueden dar lugar a vulnerabilidades de la lógica empresarial difíciles de detectar con herramientas automatizadas. La omisión de límites de velocidad en operaciones sensibles, la falta de autenticación multifactor para acciones críticas o la insuficiente detección del fraude son fallos a nivel de diseño.

Ejemplo de código vulnerable

Python ❌ Bad
# Password reset with no rate limit or verification
@app.route('/reset-password', methods=['POST'])
def reset_password():
    email = request.form['email']
    new_pass = request.form['new_password']
    user = db.find_by_email(email)
    user.password = hash_password(new_pass)  # No token verification!
    db.save(user)

Ejemplo de código seguro

Python ✅ Good
from datetime import datetime, timedelta

@app.route('/reset-password', methods=['POST'])
@rate_limit("3/hour")
def reset_password():
    token = request.form['token']
    new_pass = request.form['new_password']

    # Verify time-limited, single-use token
    reset_req = db.find_reset_token(token)
    if not reset_req or reset_req.used or reset_req.expires < datetime.utcnow():
        abort(400, "Invalid or expired token")

    # Enforce password complexity
    if not meets_password_policy(new_pass):
        abort(400, "Password does not meet requirements")

    reset_req.user.password = hash_password(new_pass)
    reset_req.used = True
    db.save_all([reset_req.user, reset_req])

Lista de control de las medidas paliativas

7️⃣ A07 - Fallos de autenticación

High

Visión general

La confirmación de la identidad de un usuario, la autenticación y la gestión de sesiones son fundamentales. Los fallos de autenticación se producen cuando las aplicaciones permiten el relleno de credenciales, la fuerza bruta, las contraseñas débiles o tienen una gestión de sesión defectuosa, como la no rotación de los ID de sesión tras el inicio de sesión.

Riesgo

Los atacantes pueden acceder a las cuentas de los usuarios mediante el relleno automático de credenciales, la fuerza bruta o el secuestro de sesiones. Las cuentas comprometidas pueden dar lugar a robos de identidad, fraudes y filtraciones de datos.

Ejemplo de código vulnerable

Python ❌ Bad
# No brute force protection, weak session handling
@app.route('/login', methods=['POST'])
def login():
    user = db.find_by_email(request.form['email'])
    if user and user.password == request.form['password']:  # Plain comparison!
        session['user'] = user.id  # Session ID not rotated
        return redirect('/dashboard')

Ejemplo de código seguro

Python ✅ Good
from flask_limiter import Limiter

limiter = Limiter(app, default_limits=["100/hour"])

@app.route('/login', methods=['POST'])
@limiter.limit("5/minute")
def login():
    user = db.find_by_email(request.form['email'])
    if not user or not bcrypt.checkpw(
        request.form['password'].encode(), user.password_hash
    ):
        # Generic error to prevent user enumeration
        return "Invalid credentials", 401

    # Rotate session ID after authentication
    session.regenerate()
    session['user'] = user.id

    # Check for MFA requirement
    if user.mfa_enabled:
        return redirect('/mfa-verify')
    return redirect('/dashboard')

Lista de control de las medidas paliativas

8️⃣ A08 - Fallos en el software o en la integridad de los datos

High

Visión general

Los fallos de integridad del software y los datos están relacionados con el código y la infraestructura que no protegen contra las violaciones de la integridad. Esto incluye la deserialización insegura, el uso de CDN o plugins no fiables sin verificación de integridad y mecanismos de actualización automática sin actualizaciones firmadas.

Riesgo

La deserialización insegura puede llevar a la ejecución remota de código. La carga de secuencias de comandos de CDN no fiables sin integridad de sub-recursos (SRI) puede permitir a los atacantes inyectar código malicioso en su aplicación.

Ejemplo de código vulnerable

HTML ❌ Bad
<!-- Loading external scripts without integrity checks -->
<script src="https://cdn.example.com/lib.js"></script>

<!-- Insecure deserialization in Python -->
import pickle
data = pickle.loads(user_input)  # Arbitrary code execution!

Ejemplo de código seguro

HTML ✅ Good
<!-- Use Subresource Integrity (SRI) for external scripts -->
<script src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K..."
  crossorigin="anonymous"></script>

# Use safe deserialization in Python
import json
data = json.loads(user_input)  # Safe: only parses JSON data

Lista de control de las medidas paliativas

9️⃣ A09 - Fallos en el registro y las alertas de seguridad

Medium

Visión general

Sin un registro, supervisión y alerta suficientes, las brechas no pueden detectarse a tiempo. Un registro insuficiente, una integración ineficaz con los sistemas de respuesta a incidentes y la falta de alertas en tiempo real permiten a los agresores seguir atacando los sistemas, mantener la persistencia y manipular o extraer datos.

Riesgo

Sin un registro adecuado, los atacantes pueden operar sin ser detectados durante largos periodos. La mayoría de los estudios sobre infracciones muestran que el tiempo medio para detectar una infracción supera los 200 días, a menudo descubierta por partes externas en lugar de por la supervisión interna.

Ejemplo de código vulnerable

Python ❌ Bad
# No logging of security-relevant events
@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)
    if not user:
        return "Login failed", 401  # No record of failure
    return redirect('/dashboard')

Ejemplo de código seguro

Python ✅ Good
import logging
from datetime import datetime

security_log = logging.getLogger('security')

@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)
    if not user:
        security_log.warning(
            "LOGIN_FAILED | ip=%s | email=%s | time=%s",
            request.remote_addr,
            request.form.get('email', 'unknown'),
            datetime.utcnow().isoformat(),
        )
        check_brute_force(request.remote_addr)
        return "Invalid credentials", 401

    security_log.info(
        "LOGIN_SUCCESS | user_id=%s | ip=%s",
        user.id, request.remote_addr,
    )
    return redirect('/dashboard')

Lista de control de las medidas paliativas

🔟 A10 - Mala gestión de las condiciones excepcionales

Medium

Visión general

Nuevo en 2025. Las aplicaciones que manejan incorrectamente errores, excepciones y casos extremos pueden exponer información sensible, entrar en estados inconsistentes o crear condiciones explotables. Esto incluye mensajes de error detallados en producción, excepciones no capturadas que eluden los controles de seguridad y condiciones de carrera.

Riesgo

La gestión inadecuada de errores puede dejar al descubierto rastros de pila, detalles de la base de datos o rutas internas. Las condiciones de carrera en las comprobaciones de seguridad pueden permitir ataques de tiempo de comprobación a tiempo de uso (TOCTOU), eludiendo la autorización o la validación del pago.

Ejemplo de código vulnerable

Python ❌ Bad
# Exposing internal details in error messages
@app.route('/api/data')
def get_data():
    try:
        result = db.query(request.args['q'])
        return jsonify(result)
    except Exception as e:
        return jsonify({"error": str(e)}), 500  # Leaks DB details!

Ejemplo de código seguro

Python ✅ Good
import uuid, logging

logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_exception(e):
    error_id = str(uuid.uuid4())
    logger.error("Unhandled exception [%s]: %s", error_id, e, exc_info=True)

    # Return generic error with reference ID for support
    return jsonify({
        "error": "An internal error occurred",
        "reference": error_id,
    }), 500

@app.route('/api/data')
def get_data():
    q = request.args.get('q')
    if not q or not is_valid_query(q):
        return jsonify({"error": "Invalid query parameter"}), 400

    result = db.query(q)
    return jsonify(result)

Lista de control de las medidas paliativas

📊 Cuadro recapitulativo

ID Vulnerabilidad Gravedad Mitigación clave
A01Control de acceso defectuosoCriticalComprobaciones de acceso en el servidor, denegación por defecto, registro de propiedad
A02Desconfiguración de la seguridadHighProceso de endurecimiento, cabeceras de seguridad, desactivar valores por defecto
A03Fallos en la cadena de suministro de softwareHighDependencias ancladas, SBOM, exploración de dependencias
A04Fallos criptográficosHighAlgoritmos robustos, TLS 1.2+, gestión de claves
A05InyecciónCriticalConsultas parametrizadas, codificación de la salida, validación de la entrada
A06Diseño inseguroHighModelado de amenazas, patrones seguros, pruebas de casos de abuso
A07Fallos de autenticaciónHighMFA, protección por fuerza bruta, rotación de sesión
A08Fallos en el software o en la integridad de los datosHighSRI, actualizaciones firmadas, deserialización segura
A09Fallos en el registro y las alertas de seguridadMediumRegistro centralizado, alertas en tiempo real, respuesta a incidentes
A10Mala gestión de las condiciones excepcionalesMediumGestor global de errores, mensajes genéricos, prevención de TOCTOU