OWASP Top 10 2025 aplicado a Spring Security

Avanzado
Spring Security
Spring Security
Actualizado: 21/04/2026

El OWASP Top 10 es la lista más influyente de vulnerabilidades web. La edición de 2025 reorganiza algunas categorías y refuerza la importancia del control de acceso a nivel de objeto. Esta lección recorre cada riesgo y muestra cómo Spring Security ayuda a mitigarlo.

A01: Broken Access Control

Sigue siendo el riesgo más grave. Engloba IDOR, BOLA (Broken Object Level Authorization), bypass de URLs administrativas y privilege escalation.

Síntomas en Spring:

  • Endpoints que confían en id del path sin verificar si el usuario es propietario.
  • Configuración con permitAll() por descuido.
  • Olvido de @PreAuthorize en métodos sensibles.

Mitigación con Spring Security:

@PreAuthorize("@permisos.esPropietario(authentication, #pedidoId)")
@GetMapping("/api/pedidos/{pedidoId}")
public Pedido obtener(@PathVariable Long pedidoId) {
    return repo.findById(pedidoId).orElseThrow();
}

Y a nivel de URL:

.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/api/**").authenticated()
    .anyRequest().denyAll())  // por defecto NEGAR

El denyAll() final es vital. Sin él, una nueva ruta sin regla queda accesible por defecto en algunos despliegues.

A02: Cryptographic Failures

Cifrado débil, datos sensibles en transit/rest sin protección, hashes obsoletos.

Mitigaciones:

  • HTTPS obligatorio con requiresChannel().anyRequest().requiresSecure().
  • HSTS activo (Strict-Transport-Security con max-age alto).
  • PasswordEncoder moderno: BCrypt strength 12+ o Argon2id.
  • Cifrado en BD para campos sensibles (Jasypt, Spring Cloud Vault Transit).
@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);
}
server:
  ssl:
    enabled: true
    enabled-protocols: TLSv1.3, TLSv1.2
    ciphers: TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256

A03: Injection

SQL injection, NoSQL injection, LDAP injection, OS command injection.

Spring Boot moderno elimina la mayoría usando JPA y queries parametrizadas. Pero quedan focos:

  • JPQL con concatenación: em.createQuery("SELECT u FROM User u WHERE u.name = '" + name + "'"). NUNCA.
  • Native queries con ? sin bindings.
  • Runtime.exec() con strings construidos por el usuario.

Mitigación:

// SI:
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByName(@Param("name") String name);

// NO:
@Query("SELECT u FROM User u WHERE u.name = '" + name + "'")

Para queries dinámicas, usa Specifications o Querydsl, no concatenación.

A04: Insecure Design

Faltan controles desde la fase de diseño: ausencia de rate limiting, no contemplar CAPTCHA, no separar tenants, no rotar credenciales.

Mitigaciones a nivel de arquitectura:

  • Threat modeling antes de cada feature.
  • Rate limiting con Bucket4j en endpoints sensibles.
  • CAPTCHA en forgot-password y registro tras X fallos.
  • Multi-tenant con AuthenticationManagerResolver.
  • Rotación automatizada de secrets vía Vault.

A04 es la categoría más difícil de "arreglar" con código. Requiere revisar el modelo de amenazas antes de escribir el primer test.

A05: Security Misconfiguration

Defaults inseguros, debug=true en producción, actuator/* expuesto, errores con stack trace al cliente.

Mitigaciones Spring:

spring:
  jpa:
    show-sql: false
  thymeleaf:
    cache: true
server:
  error:
    include-stacktrace: never
    include-message: never
    include-binding-errors: never
management:
  endpoints:
    web:
      exposure:
        include: health, info, prometheus

Y un GlobalExceptionHandler que devuelve mensajes genéricos sin filtrar internals.

@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handle(Exception ex) {
    log.error("Error interno", ex);
    return ResponseEntity.status(500).body(Map.of("error", "internal"));
}

A06: Vulnerable and Outdated Components

Dependencias con CVEs conocidos. Java Spring tiene una superficie enorme: Jackson, Tomcat, log4j (recordar Log4Shell), SnakeYAML.

Mitigaciones:

  • Dependabot o Renovate activos en el repo.
  • OWASP Dependency-Check o Snyk integrados en CI.
  • Spring Boot BOM actualizado al menos cada trimestre.
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>10.0.4</version>
    <executions>
        <execution>
            <goals><goal>check</goal></goals>
        </execution>
    </executions>
</plugin>

Bloquea el pipeline si hay CVEs High o Critical. La política habitual es mergear solo si la build pasa el escaneo.

A07: Identification and Authentication Failures

Sesiones predecibles, no rotación de IDs tras login, ausencia de bloqueo tras N fallos, no MFA.

Mitigaciones:

  • Session fixation: .sessionManagement(s -> s.sessionFixation().migrateSession()) (default activo).
  • Concurrent sessions: .sessionManagement(s -> s.maximumSessions(1).maxSessionsPreventsLogin(false)).
  • Bloqueo de cuenta: LockedException tras N fallos (custom AuthenticationProvider).
  • MFA con OTP/Passkeys: WebAuthn nativo en Spring Security moderno.
public class LockingAuthenticationProvider extends DaoAuthenticationProvider {
    private final FailureCounter counter;

    @Override
    protected void additionalAuthenticationChecks(UserDetails user, UsernamePasswordAuthenticationToken auth) {
        try {
            super.additionalAuthenticationChecks(user, auth);
            counter.reset(user.getUsername());
        } catch (BadCredentialsException ex) {
            int fails = counter.increment(user.getUsername());
            if (fails >= 5) throw new LockedException("Cuenta bloqueada");
            throw ex;
        }
    }
}

A08: Software and Data Integrity Failures

Plugins descargados sin verificar firma, deserialización insegura (clásico Java deserialization), CI/CD comprometido.

Mitigaciones:

  • Configurar Maven con HTTPS obligatorio y verificación de firmas GPG.
  • No usar ObjectInputStream sobre datos no confiables.
  • Verificar integrity de artefactos en CI con maven-enforcer-plugin.

Para JWT, validar siempre el algoritmo. Una técnica clásica de bypass es enviar alg: none.

@Bean
JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
        .jwsAlgorithm(SignatureAlgorithm.RS256)  // limitamos algoritmos aceptados
        .build();
}

A09: Security Logging and Monitoring Failures

Sin logs no hay forensics. Spring Security publica eventos que conviene escuchar y persistir.

Mitigación: suscribirse a AuthenticationSuccessEvent, AuthenticationFailureBadCredentialsEvent, AuthorizationDeniedEvent.

@Component
public class SecurityAuditListener {
    private static final Logger AUDIT = LoggerFactory.getLogger("AUDIT");

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent ev) {
        AUDIT.warn("AUTH FAIL user={} reason={}",
            ev.getAuthentication().getName(),
            ev.getException().getMessage());
    }

    @EventListener
    public void onDenied(AuthorizationDeniedEvent<?> ev) {
        AUDIT.warn("AUTHZ DENY user={} authorities={}",
            ev.getAuthentication().get().getName(),
            ev.getAuthentication().get().getAuthorities());
    }
}

Configura los logs AUDIT con appender separado, append-only, enviado a un SIEM. Los logs de aplicación no deben mezclarse con los de seguridad.

A10: Server-Side Request Forgery (SSRF)

La aplicación hace peticiones HTTP a URLs proporcionadas por el usuario, lo que permite enumerar metadata interna (IMDS de AWS, registros internos).

Mitigaciones en WebClient / RestClient:

  • Whitelist estricta de hosts permitidos.
  • Bloquear rangos IP privados (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254).
  • Tiempos de timeout cortos.
  • No permitir redirecciones automáticas a hosts distintos del original.
public URL validar(String userUrl) throws Exception {
    URL url = new URL(userUrl);
    InetAddress addr = InetAddress.getByName(url.getHost());
    if (addr.isSiteLocalAddress() || addr.isLoopbackAddress()) {
        throw new IllegalArgumentException("URL interna no permitida");
    }
    return url;
}

Checklist rápido

| Riesgo | Configuración Spring mínima | |---|---| | A01 Access Control | @PreAuthorize + denyAll() por defecto | | A02 Crypto | HTTPS + HSTS + BCrypt 12 | | A03 Injection | JPA con @Param | | A04 Insecure Design | Threat modeling + rate limiting | | A05 Misconfig | Actuator restringido, stack traces ocultos | | A06 Outdated | Dependabot + dependency-check | | A07 Auth Failures | Session fixation + MFA + bloqueo | | A08 Integrity | JWT con alg fijo, no deserializar untrusted | | A09 Logging | AuthEvents + SIEM | | A10 SSRF | Whitelist + bloqueo de IPs privadas |

Aplicar todas estas medidas no garantiza estar libre de incidentes, pero reduce el ROI del atacante hasta hacer el sistema poco interesante. La seguridad siempre es una cuestión de coste/beneficio.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Spring Security es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Spring Security

Explora más contenido relacionado con Spring Security y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Conocer las diez vulnerabilidades del OWASP Top 10 2025, identificar cómo se materializan en aplicaciones Spring Boot y aplicar contramedidas con Spring Security.