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
iddel path sin verificar si el usuario es propietario. - Configuración con
permitAll()por descuido. - Olvido de
@PreAuthorizeen 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-Securitycon max-age alto). - PasswordEncoder moderno:
BCrypt strength 12+oArgon2id. - 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-passwordy 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:
LockedExceptiontras N fallos (customAuthenticationProvider). - 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
ObjectInputStreamsobre 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
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.