October 30, 2026
Authentication & Authorization Eğitim Serisi — Appendix C: OWASP Top 10 — Auth Context…
Ekler

By Sahin Yelkenci
14 min read
"Bir güvenlik problemi, ilk gördüğünde yenidir; ikinci görüldüğünde kategorize edilebilir; üçüncü görüldüğünde Top 10'a girer. OWASP Top 10, sektörün son 20 yıllık ortak hatalarının damıtılmış haritasıdır. Modern bir auth mühendisi, bu haritanın her noktasının auth context'inde nasıl manifest olduğunu bilmelidir."
» İçerikler
» Kullanım Notu » A01:2021 — Broken Access Control » A02:2021 — Cryptographic Failures » A03:2021 — Injection » A04:2021 — Insecure Design » A05:2021 — Security Misconfiguration » A06:2021 — Vulnerable and Outdated Components » A07:2021 — Identification and Authentication Failures » A08:2021 — Software and Data Integrity Failures » A09:2021 — Security Logging and Monitoring Failures » A10:2021 — Server-Side Request Forgery (SSRF) » Hızlı Reference Matrix — OWASP × Bölüm » Kapanış
Kullanım Notu
Bu appendix, OWASP Top 10:2021 kategorilerinin auth ekosistemindeki manifest'lerini detaylandırır. Her bir OWASP item için:
- OWASP description: Resmi tanım
- Auth context'inde manifest: Modern auth sistemlerinde nasıl ortaya çıkıyor
- Seride referans bölümler: İlgili derinleşme noktaları
- Common vulnerable code: Tipik yanlış pattern
- Modern defense pattern: Doğru implementation
- Cross-references: İlgili OWASP item'lar + diğer appendix referansları
OWASP Top 10:2021, 2025–2026 itibariyle hâlâ industry baseline. OWASP Top 10:2025 draft'ı 2026 başında çıkması bekleniyor; bu appendix güncel set'i kullanır.
A01:2021 — Broken Access Control
OWASP Description
"Restrictions on what authenticated users are allowed to do are often not properly enforced. Attackers can exploit these flaws to access unauthorized functionality and/or data."
A01 — Top 10'un en yıksek-rank kategorisi. OWASP 2021 data'sına göre, tested application'ların 94%'ünde broken access control bulunmuş. Modern auth ekosisteminin en endemic problem'i.
Auth Context'inde Manifest
Vertical privilege escalation: User → admin. Klasik örnek: /api/users/{id}/role endpoint'i, normal user'lar tarafından accessible.
Horizontal privilege escalation: User A → User B. Klasik örnek: /api/orders/{orderId} endpoint'i, user'ın owner'ı olmadığı order'ı görebiliyor (IDOR — Insecure Direct Object Reference).
Missing function-level access control: Frontend hide ediliyor admin button; ama backend endpoint hâlâ accessible. "Security through obscurity" anti-pattern'i.
JWT manipulation: Algorithm confusion, signature stripping, scope escalation.
Authorization bypass via path manipulation: /api/users/me yerine /api/users/admin/123 deneme.
Mass assignment: User update endpoint'inde role field'ını body'ye ekleyerek role escalation.
CORS misconfiguration: Access-Control-Allow-Origin: * + Allow-Credentials: true → unauthorized cross-origin access.
Seride İlgili Bölümler
- Bölüm 15: RBAC fundamentals, role hierarchy, role explosion
- Bölüm 16: ABAC dynamic policies, attribute-based authorization
- Bölüm 17: ReBAC relationship-based access (Zanzibar pattern), object-level permissions
- Bölüm 18: OPA policy as code, fine-grained authorization decisions
- Bölüm 19: Spring Security method-level security,
@PreAuthorize, SpEL - Bölüm 27: Zero Trust, least privilege principle
- Bölüm 34: PAM, Zero Standing Privilege
Common Vulnerable Code
// ❌ ANTI-PATTERN: Missing owner check
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
// ⚠ Any authenticated user can access any order
}
// ❌ ANTI-PATTERN: Mass assignment
@PostMapping("/api/users/{userId}")
public User updateUser(@PathVariable Long userId, @RequestBody User userUpdate) {
User existing = userRepository.findById(userId).orElseThrow();
// ⚠ Attacker can include {role: "ADMIN"} in body
existing.setName(userUpdate.getName());
existing.setRole(userUpdate.getRole()); // SHOULD NOT BE ASSIGNABLE
return userRepository.save(existing);
}// ❌ ANTI-PATTERN: Missing owner check
@GetMapping("/api/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
// ⚠ Any authenticated user can access any order
}
// ❌ ANTI-PATTERN: Mass assignment
@PostMapping("/api/users/{userId}")
public User updateUser(@PathVariable Long userId, @RequestBody User userUpdate) {
User existing = userRepository.findById(userId).orElseThrow();
// ⚠ Attacker can include {role: "ADMIN"} in body
existing.setName(userUpdate.getName());
existing.setRole(userUpdate.getRole()); // SHOULD NOT BE ASSIGNABLE
return userRepository.save(existing);
}Modern Defense Pattern
// ✅ Owner check via method security
@GetMapping("/api/orders/{orderId}")
@PreAuthorize("@orderSecurity.canAccess(#orderId, authentication)")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
@Component("orderSecurity")
public class OrderSecurityService {
public boolean canAccess(Long orderId, Authentication auth) {
Order order = orderRepository.findById(orderId).orElseThrow();
// Owner OR admin
return order.getUserId().equals(auth.getName())
|| auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
}
}
// ✅ DTO-based update (no mass assignment)
@PostMapping("/api/users/{userId}")
@PreAuthorize("#userId == authentication.name or hasRole('ADMIN')")
public User updateUser(@PathVariable Long userId, @RequestBody UserUpdateDto update) {
User existing = userRepository.findById(userId).orElseThrow();
// Only allow specific fields; role NOT in DTO
existing.setName(update.getName());
existing.setEmail(update.getEmail());
// role assignment requires separate admin-only endpoint
return userRepository.save(existing);
}// ✅ Owner check via method security
@GetMapping("/api/orders/{orderId}")
@PreAuthorize("@orderSecurity.canAccess(#orderId, authentication)")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
@Component("orderSecurity")
public class OrderSecurityService {
public boolean canAccess(Long orderId, Authentication auth) {
Order order = orderRepository.findById(orderId).orElseThrow();
// Owner OR admin
return order.getUserId().equals(auth.getName())
|| auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
}
}
// ✅ DTO-based update (no mass assignment)
@PostMapping("/api/users/{userId}")
@PreAuthorize("#userId == authentication.name or hasRole('ADMIN')")
public User updateUser(@PathVariable Long userId, @RequestBody UserUpdateDto update) {
User existing = userRepository.findById(userId).orElseThrow();
// Only allow specific fields; role NOT in DTO
existing.setName(update.getName());
existing.setEmail(update.getEmail());
// role assignment requires separate admin-only endpoint
return userRepository.save(existing);
}Cross-references: A04 (Insecure Design), A07 (Authentication Failures). Detay için: Bölüm 15–18, Appendix B Pattern 10.
A02:2021 — Cryptographic Failures
OWASP Description
"Failures related to cryptography… often lead to sensitive data exposure or system compromise."
OWASP'nin önceki adıyla "Sensitive Data Exposure". Modern adlandırma daha doğru: problem data exposure değil; underlying cryptographic failure.
Auth Context'inde Manifest
Plaintext passwords in DB: 2025'te hâlâ rastlanan anti-pattern. DB breach → tüm password'ler compromise.
Weak password hashing: MD5, SHA-1, SHA-256 (unsalted). GPU ile saniyede milyarlarca crack.
Weak JWT signing: HS256 with short secret, or worse — alg: none accepted.
Cleartext token transmission: HTTP (not HTTPS) endpoint'ler.
Hardcoded secrets: Source code'da API key, JWT signing key, DB password.
TLS misconfiguration: TLS 1.0/1.1 desteklenmesi, weak cipher suites, expired certs.
Insufficient entropy: Math.random() based token generation (deterministic).
No encryption at rest: Database backups, log files plaintext.
Algorithm downgrade: Old TLS versions, old crypto algorithms still negotiated.
Seride İlgili Bölümler
- Bölüm 3: Cryptographic fundamentals, symmetric vs asymmetric, hashing
- Bölüm 4: Password storage, Argon2id, salt, work factor calibration
- Bölüm 6: JWT signing algorithms, RS256 vs HS256
- Bölüm 21: JWT validation strict patterns, audience binding
- Bölüm 24: TLS, certificate management, mTLS
- Bölüm 28: Secrets management (Vault, KMS, HSM)
- Bölüm 30: Cryptographic key rotation
Common Vulnerable Code
// ❌ ANTI-PATTERN: SHA-256 unsalted password hashing
public String hashPassword(String password) {
return DigestUtils.sha256Hex(password); // GPU-crackable
}
// ❌ ANTI-PATTERN: Hardcoded JWT signing key
public class JwtService {
private static final String SECRET = "my-secret-key"; // ⚠
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS256, SECRET.getBytes())
.compact();
}
}
// ❌ ANTI-PATTERN: Math.random() for token generation
public String generateResetToken() {
return String.valueOf((long)(Math.random() * 1_000_000_000L)); // Predictable
}// ❌ ANTI-PATTERN: SHA-256 unsalted password hashing
public String hashPassword(String password) {
return DigestUtils.sha256Hex(password); // GPU-crackable
}
// ❌ ANTI-PATTERN: Hardcoded JWT signing key
public class JwtService {
private static final String SECRET = "my-secret-key"; // ⚠
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS256, SECRET.getBytes())
.compact();
}
}
// ❌ ANTI-PATTERN: Math.random() for token generation
public String generateResetToken() {
return String.valueOf((long)(Math.random() * 1_000_000_000L)); // Predictable
}Modern Defense Pattern
// ✅ Argon2id password hashing
@Bean
public PasswordEncoder passwordEncoder() {
return new Argon2PasswordEncoder(16, 32, 1, 65536, 3);
}
// ✅ JWT signing with rotated keys from Vault
@Service
public class JwtService {
private final VaultService vaultService;
public String generateToken(User user) {
SigningKey signingKey = vaultService.getCurrentSigningKey();
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(signingKey.getPrivateKey(), SignatureAlgorithm.RS256)
.compact();
}
}
// ✅ Cryptographically secure random
public String generateSecureToken() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32]; // 256 bits
random.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
# ✅ TLS strict config
server:
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3
ciphers: >
TLS_AES_256_GCM_SHA384,
TLS_AES_128_GCM_SHA256,
ECDHE-ECDSA-AES256-GCM-SHA384,
ECDHE-RSA-AES256-GCM-SHA384// ✅ Argon2id password hashing
@Bean
public PasswordEncoder passwordEncoder() {
return new Argon2PasswordEncoder(16, 32, 1, 65536, 3);
}
// ✅ JWT signing with rotated keys from Vault
@Service
public class JwtService {
private final VaultService vaultService;
public String generateToken(User user) {
SigningKey signingKey = vaultService.getCurrentSigningKey();
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(signingKey.getPrivateKey(), SignatureAlgorithm.RS256)
.compact();
}
}
// ✅ Cryptographically secure random
public String generateSecureToken() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32]; // 256 bits
random.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
# ✅ TLS strict config
server:
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3
ciphers: >
TLS_AES_256_GCM_SHA384,
TLS_AES_128_GCM_SHA256,
ECDHE-ECDSA-AES256-GCM-SHA384,
ECDHE-RSA-AES256-GCM-SHA384Cross-references: A05 (Security Misconfiguration), A07 (Authentication Failures). Detay: Bölüm 3, 4, 24, 28.
A03:2021 — Injection
OWASP Description
"Injection flaws… allow an attacker to relay malicious code through an application to another system."
SQL injection, NoSQL injection, OS command injection, LDAP injection — hepsi bu kategoride.
Auth Context'inde Manifest
SQL injection in authentication: Klasik ' OR '1'='1 login bypass.
LDAP injection: Active Directory authentication context'inde, LDAP query manipulation.
NoSQL injection: MongoDB-based user lookup'larda $ne operator abuse.
JWT kid SQL injection: kid: "1' OR '1'='1" — server kid'i direkt SQL'de kullanıyorsa.
XSS via authentication context: User registration form'unda XSS payload; admin gördüğünde execute.
Command injection via username: Username parameter shell command'a forwarded (rare ama görülmüş).
Seride İlgili Bölümler
- Bölüm 2: Threat modeling, OWASP awareness
- Bölüm 37: XSS-based token theft
- Bölüm 39: JWT
kidinjection attacks
Common Vulnerable Code
// ❌ ANTI-PATTERN: SQL injection in login
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
return jdbcTemplate.queryForObject(sql, userMapper);
// Input: ' OR '1'='1' --
// SQL: SELECT * FROM users WHERE username = '' OR '1'='1' --'
}
// ❌ ANTI-PATTERN: kid header SQL injection
public PublicKey findKey(Jwt jwt) {
String kid = (String) jwt.getHeader().get("kid");
String sql = "SELECT public_key FROM keys WHERE kid = '" + kid + "'";
return jdbcTemplate.queryForObject(sql, keyMapper);
}
// ❌ ANTI-PATTERN: LDAP injection
public boolean authenticate(String username, String password) {
String filter = "(uid=" + username + ")"; // ⚠
DirContext ctx = new InitialDirContext(env);
NamingEnumeration results = ctx.search("dc=example,dc=com", filter, null);
// Input: *)(uid=*))(|(uid=*
}// ❌ ANTI-PATTERN: SQL injection in login
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
return jdbcTemplate.queryForObject(sql, userMapper);
// Input: ' OR '1'='1' --
// SQL: SELECT * FROM users WHERE username = '' OR '1'='1' --'
}
// ❌ ANTI-PATTERN: kid header SQL injection
public PublicKey findKey(Jwt jwt) {
String kid = (String) jwt.getHeader().get("kid");
String sql = "SELECT public_key FROM keys WHERE kid = '" + kid + "'";
return jdbcTemplate.queryForObject(sql, keyMapper);
}
// ❌ ANTI-PATTERN: LDAP injection
public boolean authenticate(String username, String password) {
String filter = "(uid=" + username + ")"; // ⚠
DirContext ctx = new InitialDirContext(env);
NamingEnumeration results = ctx.search("dc=example,dc=com", filter, null);
// Input: *)(uid=*))(|(uid=*
}Modern Defense Pattern
// ✅ Parameterized query (JdbcTemplate)
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = ?";
return jdbcTemplate.queryForObject(sql, userMapper, username);
}
// ✅ JPA - auto-parameterized
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); // Safe
}
// ✅ Strict kid validation (Bölüm 39)
public class StrictKidValidator implements OAuth2TokenValidator<Jwt> {
private static final Pattern VALID_KID = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$");
private final Set<String> allowedKids;
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
String kid = (String) jwt.getHeaders().get("kid");
if (kid == null || !VALID_KID.matcher(kid).matches() || !allowedKids.contains(kid)) {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_kid", "Invalid kid", null));
}
return OAuth2TokenValidatorResult.success();
}
}
// ✅ LDAP injection defense - escape user input
public boolean authenticate(String username, String password) {
String safeUsername = LdapEncoder.filterEncode(username);
String filter = "(uid=" + safeUsername + ")";
// Now safe
}// ✅ Parameterized query (JdbcTemplate)
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = ?";
return jdbcTemplate.queryForObject(sql, userMapper, username);
}
// ✅ JPA - auto-parameterized
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); // Safe
}
// ✅ Strict kid validation (Bölüm 39)
public class StrictKidValidator implements OAuth2TokenValidator<Jwt> {
private static final Pattern VALID_KID = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$");
private final Set<String> allowedKids;
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
String kid = (String) jwt.getHeaders().get("kid");
if (kid == null || !VALID_KID.matcher(kid).matches() || !allowedKids.contains(kid)) {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_kid", "Invalid kid", null));
}
return OAuth2TokenValidatorResult.success();
}
}
// ✅ LDAP injection defense - escape user input
public boolean authenticate(String username, String password) {
String safeUsername = LdapEncoder.filterEncode(username);
String filter = "(uid=" + safeUsername + ")";
// Now safe
}Cross-references: A04 (Insecure Design), A07. Detay: Bölüm 37, 39, Appendix B Pattern 2.
A04:2021 — Insecure Design
OWASP Description
"Missing or ineffective control design. Insecure design refers to risks related to design and architectural flaws."
2021'de eklendi. Implementation bug'ı değil; mimari hata. Daha zor düzeltilir.
Auth Context'inde Manifest
No defense in depth: Tek security layer. Bypass → tam compromise.
No rate limiting: Brute force, DoS, scraping unprevented.
Standing privileges: Mühendisler kalıcı admin. PAM yok.
No segregation of duties: Single person'ın çok yetkisi.
No principle of least privilege: Default'ta over-permissioned.
Trust boundary confusion: Backend gateway header'larına trust olmadan.
No threat modeling: Sistematik risk analysis yok.
No audit trail: Security event'leri kayıt edilmiyor.
Reusable tokens without revocation: Token revocation mechanism yok.
Seride İlgili Bölümler
- Giriş: Manifesto — security as design principle
- Bölüm 2: Threat modeling
- Bölüm 27: Zero Trust Architecture
- Bölüm 34: PAM, Zero Standing Privilege
- Bölüm 36: API Gateway, internal trust boundary
- Bölüm 37–39: Defense in depth across attack categories
Modern Defense Pattern (Architectural Principles)
1. Defense in Depth
Multiple independent security layers
Bypass single layer ≠ full compromise
2. Principle of Least Privilege
Default-deny; opt-in to minimal permissions
PAM with Just-In-Time elevation
3. Zero Trust
Never trust, always verify
Internal network ≠ trusted
4. Threat Modeling (STRIDE)
- Spoofing
- Tampering
- Repudiation
- Information disclosure
- Denial of service
- Elevation of privilege
5. Audit Everything
Immutable audit log
Bölüm 29: hash chain, SIEM integration
6. Segregation of Duties
No single person has full power
Multi-person authorization for critical ops
7. Fail Closed
Errors → deny access, not allow
OPA timeout → false (deny)1. Defense in Depth
Multiple independent security layers
Bypass single layer ≠ full compromise
2. Principle of Least Privilege
Default-deny; opt-in to minimal permissions
PAM with Just-In-Time elevation
3. Zero Trust
Never trust, always verify
Internal network ≠ trusted
4. Threat Modeling (STRIDE)
- Spoofing
- Tampering
- Repudiation
- Information disclosure
- Denial of service
- Elevation of privilege
5. Audit Everything
Immutable audit log
Bölüm 29: hash chain, SIEM integration
6. Segregation of Duties
No single person has full power
Multi-person authorization for critical ops
7. Fail Closed
Errors → deny access, not allow
OPA timeout → false (deny)Cross-references: Tüm OWASP item'larla overlap. Detay: Bölüm 2, 27, 34.
A05:2021 — Security Misconfiguration
OWASP Description
"Missing appropriate security hardening or improperly configured permissions."
Auth Context'inde Manifest
Default credentials: Spring Boot Admin, Actuator endpoint'ler default password'le.
Verbose error messages: Stack trace ile internal details sızıntısı.
Missing security headers: CSP, HSTS, X-Frame-Options yok.
Permissive CORS: Access-Control-Allow-Origin: * + credentials.
Debug mode in production: spring.boot.dev-tools enabled, debug logging.
Actuator endpoints exposed: /actuator/env, /actuator/heapdump public.
Outdated software: Spring Security old version, known CVEs unpatched.
TLS misconfiguration: Weak ciphers, expired certs, no HSTS.
Open S3 buckets: Audit logs publicly readable.
Cloud IAM over-permissioned: Service accounts with admin role.
Seride İlgili Bölümler
- Bölüm 19: Spring Security defaults vs custom config
- Bölüm 24: TLS configuration
- Bölüm 29: Logging configuration, actuator hardening
- Bölüm 37: Security headers (CSP, HSTS, frame options)
- Bölüm 38: Login error messages anti-enumeration
Common Vulnerable Configuration
# ❌ ANTI-PATTERN: Permissive defaults
server:
error:
include-stacktrace: always # ⚠ Stack trace exposed
include-message: always
management:
endpoints:
web:
exposure:
include: "*" # ⚠ All actuator endpoints public
spring:
security:
user:
name: admin
password: admin # ⚠ Default credential
cors:
allowed-origins: "*" # ⚠ Wildcard
allow-credentials: true# ❌ ANTI-PATTERN: Permissive defaults
server:
error:
include-stacktrace: always # ⚠ Stack trace exposed
include-message: always
management:
endpoints:
web:
exposure:
include: "*" # ⚠ All actuator endpoints public
spring:
security:
user:
name: admin
password: admin # ⚠ Default credential
cors:
allowed-origins: "*" # ⚠ Wildcard
allow-credentials: trueModern Defense Configuration
# ✅ Production-hardened defaults
server:
error:
include-stacktrace: never
include-message: never
include-exception: false
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3
management:
endpoint:
health:
show-details: when-authorized
endpoints:
web:
exposure:
include: health,info,prometheus # Explicit minimal set
base-path: /internal/management # Not /actuator (less guessable)
server:
port: 9001 # Separate port, internal-only
spring:
jpa:
open-in-view: false # Prevent N+1 exposure
security:
require-ssl: true
// ✅ Custom error handler (no info leak)
@ControllerAdvice
public class GlobalErrorHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handle(Exception e) {
log.error("Internal error", e); // Internal logging
return ResponseEntity.status(500).body(
new ErrorResponse("internal_error", "An error occurred"));
// No stack trace to client
}
}# ✅ Production-hardened defaults
server:
error:
include-stacktrace: never
include-message: never
include-exception: false
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3
management:
endpoint:
health:
show-details: when-authorized
endpoints:
web:
exposure:
include: health,info,prometheus # Explicit minimal set
base-path: /internal/management # Not /actuator (less guessable)
server:
port: 9001 # Separate port, internal-only
spring:
jpa:
open-in-view: false # Prevent N+1 exposure
security:
require-ssl: true
// ✅ Custom error handler (no info leak)
@ControllerAdvice
public class GlobalErrorHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handle(Exception e) {
log.error("Internal error", e); // Internal logging
return ResponseEntity.status(500).body(
new ErrorResponse("internal_error", "An error occurred"));
// No stack trace to client
}
}Cross-references: A02 (Cryptographic Failures), A09 (Logging Failures). Detay: Bölüm 19, 24, 29.
A06:2021 — Vulnerable and Outdated Components
OWASP Description
"Components with known vulnerabilities used in software."
Auth Context'inde Manifest
Legacy Spring Security: Spring Security 5.x'in SAML extension (deprecated) — XSW vulnerable.
Vulnerable JWT libraries: node-jsonwebtoken, jjwt eski versiyonları — algorithm confusion CVE'ler.
Outdated TLS implementation: OpenSSL eski versiyonları — Heartbleed (CVE-2014–0160) gibi.
Old SAML implementation: OpenSAML 3.x eski versiyonları.
Transitive dependencies: Direct dependency güncel ama transitive dependency vulnerable.
Container base images: Outdated Alpine, Ubuntu base images.
Unmaintained libraries: Maintainer abandoned; CVE fix yok.
Seride İlgili Bölümler
- Bölüm 30: Deployment, version management
- Bölüm 32: SAML legacy → modern migration
- Bölüm 39: JWT library vulnerabilities
Modern Defense Pattern
Automated dependency scanning:
<!-- Maven OWASP Dependency Check Plugin -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.7</version>
<configuration>
<failBuildOnCVSS>7.0</failBuildOnCVSS>
<suppressionFiles>
<suppressionFile>owasp-suppressions.xml</suppressionFile>
</suppressionFiles>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
# GitHub Actions: Snyk, Dependabot
name: Security Audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/maven@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high<!-- Maven OWASP Dependency Check Plugin -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.7</version>
<configuration>
<failBuildOnCVSS>7.0</failBuildOnCVSS>
<suppressionFiles>
<suppressionFile>owasp-suppressions.xml</suppressionFile>
</suppressionFiles>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
# GitHub Actions: Snyk, Dependabot
name: Security Audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/maven@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=highDependency policy:
- Major updates: Quarterly reviewed
- Minor updates: Monthly automated
- Security patches: Immediate (24h SLA)
- CVE in critical library: Emergency response
Container scanning: Trivy, Grype, Snyk Container.
# Trivy scan
trivy image my-app:latest --severity HIGH,CRITICAL# Trivy scan
trivy image my-app:latest --severity HIGH,CRITICALSBOM (Software Bill of Materials): CycloneDX, SPDX. Tüm dependency'leri track + audit.
Cross-references: A02. Detay: Bölüm 30.
A07:2021 — Identification and Authentication Failures
OWASP Description
"Confirmation of the user's identity, authentication, and session management is critical to protect against authentication-related attacks."
A07 — bu serinin core domain'i. Tüm seri büyük ölçüde bu kategorinin derinleşmesi.
Auth Context'inde Manifest
Weak passwords accepted: "password123" gibi breached password'ler kabul.
No MFA: Sensitive operations için tek faktör.
Session fixation: Login öncesi session ID, login sonrası aynı kalıyor.
Predictable session tokens: Sequential, time-based, Math.random()-based.
Long-lived sessions: 30+ gün re-authentication olmadan.
No account lockout: Unlimited brute force attempts.
No credential stuffing defense: HIBP check yok.
Phishing vulnerable: Password + TOTP MFA AiTM-bypassable.
SMS-only MFA: SIM swap risk.
Default credentials in production.
Seride İlgili Bölümler
- Bölüm 4: Password storage, breached password defense
- Bölüm 5: Session management
- Bölüm 13: MFA fundamentals
- Bölüm 14: WebAuthn / Passkey
- Bölüm 27: Continuous verification
- Bölüm 35: CIAM fraud detection
- Bölüm 37: Session fixation, token theft
- Bölüm 38: Credential attacks comprehensive
Modern Defense Pattern (Layered)
// 1. Strong password policy + HIBP
@Service
public class PasswordPolicyService {
public ValidationResult validate(String password) {
if (password.length() < 12) return invalid("Min 12 chars");
if (hibpService.isBreached(password)) return invalid("Breached password");
if (commonPasswords.contains(password)) return invalid("Too common");
return valid();
}
}
// 2. Account lockout + exponential backoff (Bölüm 38)
// LoginAttemptTracker (Appendix B Pattern 19)
// 3. MFA (Bölüm 13, 14)
// Passkey-first config (Appendix B Pattern 7)
// 4. Session security (Bölüm 37)
http.sessionManagement(session -> session
.sessionFixation().changeSessionId()
.maximumSessions(1)
);
// 5. Token TTL (Bölüm 21)
.accessTokenTimeToLive(Duration.ofMinutes(15))
.refreshTokenTimeToLive(Duration.ofDays(90))
.reuseRefreshTokens(false) // Rotation
// 6. Continuous verification (Bölüm 27)
// AdaptiveMfaService risk-based
// ContinuousAuthenticationFilter behavioral// 1. Strong password policy + HIBP
@Service
public class PasswordPolicyService {
public ValidationResult validate(String password) {
if (password.length() < 12) return invalid("Min 12 chars");
if (hibpService.isBreached(password)) return invalid("Breached password");
if (commonPasswords.contains(password)) return invalid("Too common");
return valid();
}
}
// 2. Account lockout + exponential backoff (Bölüm 38)
// LoginAttemptTracker (Appendix B Pattern 19)
// 3. MFA (Bölüm 13, 14)
// Passkey-first config (Appendix B Pattern 7)
// 4. Session security (Bölüm 37)
http.sessionManagement(session -> session
.sessionFixation().changeSessionId()
.maximumSessions(1)
);
// 5. Token TTL (Bölüm 21)
.accessTokenTimeToLive(Duration.ofMinutes(15))
.refreshTokenTimeToLive(Duration.ofDays(90))
.reuseRefreshTokens(false) // Rotation
// 6. Continuous verification (Bölüm 27)
// AdaptiveMfaService risk-based
// ContinuousAuthenticationFilter behavioralCross-references: A01, A02, A04. Tüm serinin core'u. Tüm bölümler relevant.
A08:2021 — Software and Data Integrity Failures
OWASP Description
"Code and infrastructure that does not protect against integrity violations."
Auth Context'inde Manifest
Unsigned auto-updates: Application updates verify edilmeden install.
Unverified plugins/extensions: Third-party JS dependencies CDN'den, integrity check yok.
Insecure deserialization: Java deserialization attacks (CVE-2015–7501).
CI/CD pipeline tampering: Build pipeline'ında inject edilmiş malicious step.
Unsigned configuration: OPA bundle unsigned — attacker policy değiştirebilir.
Unverified container images: docker pull ile registry doğrulaması yok.
Modified audit logs: Audit log immutable değil; attacker silebilir.
Seride İlgili Bölümler
- Bölüm 18: OPA bundle signing
- Bölüm 28: Secret integrity
- Bölüm 29: Immutable audit log, hash chain
- Bölüm 30: Signed deployments, SBOM
Modern Defense Pattern
Subresource Integrity (SRI) — frontend:
<script src="https://cdn.example.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8t0k5XdkjzJBDH1aH3..."
crossorigin="anonymous"></script><script src="https://cdn.example.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8t0k5XdkjzJBDH1aH3..."
crossorigin="anonymous"></script>Signed OPA bundles (Bölüm 18):
services:
opa-bundle-server:
url: https://bundle-server.internal.example.com
bundles:
authz:
service: opa-bundle-server
resource: bundles/authz/bundle.tar.gz
signing:
keyid: prod-bundle-signing-key
scope: "*"services:
opa-bundle-server:
url: https://bundle-server.internal.example.com
bundles:
authz:
service: opa-bundle-server
resource: bundles/authz/bundle.tar.gz
signing:
keyid: prod-bundle-signing-key
scope: "*"Container image signing (Cosign):
# Sign
cosign sign --key cosign.key gcr.io/myorg/myapp:v1.0.0
# Verify in CI/CD
cosign verify --key cosign.pub gcr.io/myorg/myapp:v1.0.0# Sign
cosign sign --key cosign.key gcr.io/myorg/myapp:v1.0.0
# Verify in CI/CD
cosign verify --key cosign.pub gcr.io/myorg/myapp:v1.0.0Immutable audit log (Bölüm 29):
@Entity
public class AuditEvent {
@Id private UUID id;
private String eventType;
private Instant timestamp;
private String previousHash; // ← Hash chain
private String currentHash; // ← SHA-256 of (data + previousHash)
}
@Service
public class AuditService {
public void record(AuditEvent event) {
AuditEvent last = repository.findLatest();
event.setPreviousHash(last != null ? last.getCurrentHash() : "GENESIS");
event.setCurrentHash(computeHash(event));
repository.save(event);
}
public boolean verifyIntegrity() {
// Walk chain, verify hashes
}
}@Entity
public class AuditEvent {
@Id private UUID id;
private String eventType;
private Instant timestamp;
private String previousHash; // ← Hash chain
private String currentHash; // ← SHA-256 of (data + previousHash)
}
@Service
public class AuditService {
public void record(AuditEvent event) {
AuditEvent last = repository.findLatest();
event.setPreviousHash(last != null ? last.getCurrentHash() : "GENESIS");
event.setCurrentHash(computeHash(event));
repository.save(event);
}
public boolean verifyIntegrity() {
// Walk chain, verify hashes
}
}Cross-references: A02, A05. Detay: Bölüm 18, 29, 30.
A09:2021 — Security Logging and Monitoring Failures
OWASP Description
"Insufficient logging and monitoring, coupled with missing or ineffective integration with incident response."
Auth Context'inde Manifest
No authentication logging: Login attempts (success/failure) kayıt edilmiyor.
No authorization logging: "User X accessed resource Y" kayıt yok.
No anomaly detection: Unusual patterns missed.
Insufficient log details: User ID, IP, timestamp, action — eksik.
No SIEM integration: Log'lar local, central aggregation yok.
No alerting: Critical event'ler real-time notify edilmez.
Logs not protected: Audit logs editable.
Insufficient retention: Compliance requirement'larından kısa retention.
No correlation IDs: Distributed system'da request trace impossible.
Seride İlgili Bölümler
- Bölüm 29: Comprehensive observability (logging + metrics + traces + audit)
- Bölüm 34: PAM session recording compliance
- Bölüm 37: Token anomaly detection (UEBA)
- Bölüm 38: Credential attack pattern detection
Modern Defense Pattern
Structured logging (Bölüm 29):
log.atInfo()
.addKeyValue("event_type", "login_success")
.addKeyValue("user_id", user.getId())
.addKeyValue("ip", clientIp)
.addKeyValue("user_agent", userAgent)
.addKeyValue("auth_method", "passkey")
.addKeyValue("trace_id", traceContext.getTraceId())
.log("User authenticated");log.atInfo()
.addKeyValue("event_type", "login_success")
.addKeyValue("user_id", user.getId())
.addKeyValue("ip", clientIp)
.addKeyValue("user_agent", userAgent)
.addKeyValue("auth_method", "passkey")
.addKeyValue("trace_id", traceContext.getTraceId())
.log("User authenticated");Immutable audit trail (Bölüm 29 + 34):
@Service
public class AuditService {
public void record(AuditEvent event) {
// 1. Hash chain (integrity)
// 2. SIEM forward (real-time)
// 3. WORM storage (compliance)
// 4. Alert critical events
}
}@Service
public class AuditService {
public void record(AuditEvent event) {
// 1. Hash chain (integrity)
// 2. SIEM forward (real-time)
// 3. WORM storage (compliance)
// 4. Alert critical events
}
}SIEM integration — Splunk, Elastic Security, Datadog Cloud SIEM:
# Filebeat → Elastic
filebeat.inputs:
- type: log
paths:
- /var/log/app/audit.log
json.keys_under_root: true
fields:
log_type: audit
output.elasticsearch:
hosts: ["siem.internal:9200"]
index: "audit-%{+yyyy.MM.dd}"# Filebeat → Elastic
filebeat.inputs:
- type: log
paths:
- /var/log/app/audit.log
json.keys_under_root: true
fields:
log_type: audit
output.elasticsearch:
hosts: ["siem.internal:9200"]
index: "audit-%{+yyyy.MM.dd}"Real-time alerting:
@EventListener
public void onSecurityEvent(SecurityEvent event) {
if (event.getSeverity() == Severity.CRITICAL) {
alertService.sendCriticalAlert(
event,
Set.of(
AlertChannel.PAGERDUTY,
AlertChannel.SLACK_SECURITY_CHANNEL,
AlertChannel.SMS_CISO
)
);
}
}@EventListener
public void onSecurityEvent(SecurityEvent event) {
if (event.getSeverity() == Severity.CRITICAL) {
alertService.sendCriticalAlert(
event,
Set.of(
AlertChannel.PAGERDUTY,
AlertChannel.SLACK_SECURITY_CHANNEL,
AlertChannel.SMS_CISO
)
);
}
}Compliance retention (Bölüm 34):
Compliance Retention PCI-DSS 1 year minimum, 3 months online HIPAA 6 years SOC2 1 year KVKK 10 years for some categories BDDK Open Banking 10 years
Cross-references: A04, A05. Detay: Bölüm 29, 34.
A10:2021 — Server-Side Request Forgery (SSRF)
OWASP Description
"Web application fetches a remote resource without validating the user-supplied URL."
Auth Context'inde Manifest
OIDC discovery URL manipulation: Attacker, internal admin endpoint'i OIDC discovery URL olarak inject eder.
SAML metadata URL SSRF: Tenant onboarding'de IdP metadata URL kontrolü yetersiz.
JWKS URL manipulation: Attacker, kendi JWKS endpoint'ini inject eder.
Webhook URL SSRF: Application user-defined webhook URL'lere request gönderiyor; internal endpoint'ler accessible.
Cloud metadata service abuse: http://169.254.169.254/ (AWS, GCP metadata) — credential exfiltration.
Avatar/image URL fetching: User profile picture URL'i internal SSRF için.
Seride İlgili Bölümler
- Bölüm 11: OIDC discovery configuration
- Bölüm 32: SAML metadata URL validation
- Bölüm 33: Multi-tenant SAML/OIDC dynamic registration
Common Vulnerable Code
// ❌ ANTI-PATTERN: OIDC discovery without URL validation
@PostMapping("/api/admin/tenant/oidc")
public TenantConfig configureOidc(@RequestBody OidcConfigRequest request) {
// ⚠ Attacker: issuerUri = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
ClientRegistration registration = ClientRegistrations
.fromIssuerLocation(request.getIssuerUri())
.build();
// SSRF — server fetches arbitrary URL
}
// ❌ ANTI-PATTERN: Webhook URL without validation
@PostMapping("/api/webhooks")
public void registerWebhook(@RequestBody WebhookConfig config) {
webhookRepository.save(config);
// Later: restTemplate.postForObject(config.getUrl(), payload);
// Attacker: url = "http://internal-admin.local/dangerous-action"
}// ❌ ANTI-PATTERN: OIDC discovery without URL validation
@PostMapping("/api/admin/tenant/oidc")
public TenantConfig configureOidc(@RequestBody OidcConfigRequest request) {
// ⚠ Attacker: issuerUri = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
ClientRegistration registration = ClientRegistrations
.fromIssuerLocation(request.getIssuerUri())
.build();
// SSRF — server fetches arbitrary URL
}
// ❌ ANTI-PATTERN: Webhook URL without validation
@PostMapping("/api/webhooks")
public void registerWebhook(@RequestBody WebhookConfig config) {
webhookRepository.save(config);
// Later: restTemplate.postForObject(config.getUrl(), payload);
// Attacker: url = "http://internal-admin.local/dangerous-action"
}Modern Defense Pattern
// ✅ SSRF defense: URL allowlist + IP validation
@Component
public class SafeUrlValidator {
private static final Set<String> ALLOWED_PROTOCOLS = Set.of("https");
private static final Set<String> BLOCKED_IP_RANGES = Set.of(
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", // Private
"127.0.0.0/8", "169.254.0.0/16", // Loopback, link-local
"::1/128", "fc00::/7", "fe80::/10" // IPv6
);
public ValidationResult validate(String urlString) {
URL url;
try {
url = new URL(urlString);
} catch (MalformedURLException e) {
return ValidationResult.invalid("Malformed URL");
}
// Protocol check
if (!ALLOWED_PROTOCOLS.contains(url.getProtocol())) {
return ValidationResult.invalid("Only HTTPS allowed");
}
// Resolve host to IP
InetAddress[] addresses;
try {
addresses = InetAddress.getAllByName(url.getHost());
} catch (UnknownHostException e) {
return ValidationResult.invalid("Cannot resolve host");
}
// ⚠ DNS rebinding defense: check ALL resolved addresses
for (InetAddress addr : addresses) {
for (String range : BLOCKED_IP_RANGES) {
if (isInRange(addr, range)) {
return ValidationResult.invalid(
"Internal IP not allowed: " + addr.getHostAddress());
}
}
}
return ValidationResult.valid();
}
}
// ✅ Usage in OIDC config
@PostMapping("/api/admin/tenant/oidc")
public TenantConfig configureOidc(@RequestBody OidcConfigRequest request) {
ValidationResult result = urlValidator.validate(request.getIssuerUri());
if (!result.isValid()) {
throw new BadRequestException("Invalid issuer URI: " + result.getReason());
}
ClientRegistration registration = ClientRegistrations
.fromIssuerLocation(request.getIssuerUri())
.build();
// Safe - URL pre-validated
}
// ✅ Webhook outbound - through dedicated proxy
@Bean
public RestTemplate webhookRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// Proxy that blocks internal IPs
factory.setProxy(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("egress-proxy.internal", 3128)));
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}// ✅ SSRF defense: URL allowlist + IP validation
@Component
public class SafeUrlValidator {
private static final Set<String> ALLOWED_PROTOCOLS = Set.of("https");
private static final Set<String> BLOCKED_IP_RANGES = Set.of(
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", // Private
"127.0.0.0/8", "169.254.0.0/16", // Loopback, link-local
"::1/128", "fc00::/7", "fe80::/10" // IPv6
);
public ValidationResult validate(String urlString) {
URL url;
try {
url = new URL(urlString);
} catch (MalformedURLException e) {
return ValidationResult.invalid("Malformed URL");
}
// Protocol check
if (!ALLOWED_PROTOCOLS.contains(url.getProtocol())) {
return ValidationResult.invalid("Only HTTPS allowed");
}
// Resolve host to IP
InetAddress[] addresses;
try {
addresses = InetAddress.getAllByName(url.getHost());
} catch (UnknownHostException e) {
return ValidationResult.invalid("Cannot resolve host");
}
// ⚠ DNS rebinding defense: check ALL resolved addresses
for (InetAddress addr : addresses) {
for (String range : BLOCKED_IP_RANGES) {
if (isInRange(addr, range)) {
return ValidationResult.invalid(
"Internal IP not allowed: " + addr.getHostAddress());
}
}
}
return ValidationResult.valid();
}
}
// ✅ Usage in OIDC config
@PostMapping("/api/admin/tenant/oidc")
public TenantConfig configureOidc(@RequestBody OidcConfigRequest request) {
ValidationResult result = urlValidator.validate(request.getIssuerUri());
if (!result.isValid()) {
throw new BadRequestException("Invalid issuer URI: " + result.getReason());
}
ClientRegistration registration = ClientRegistrations
.fromIssuerLocation(request.getIssuerUri())
.build();
// Safe - URL pre-validated
}
// ✅ Webhook outbound - through dedicated proxy
@Bean
public RestTemplate webhookRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// Proxy that blocks internal IPs
factory.setProxy(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("egress-proxy.internal", 3128)));
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return new RestTemplate(factory);
}Cloud metadata defense:
# AWS — IMDSv2 mandatory (requires session token)
aws ec2 modify-instance-metadata-options \
--instance-id i-1234 \
--http-tokens required \
--http-put-response-hop-limit 1
# Hop limit 1 - prevents container SSRF reaching IMDS# AWS — IMDSv2 mandatory (requires session token)
aws ec2 modify-instance-metadata-options \
--instance-id i-1234 \
--http-tokens required \
--http-put-response-hop-limit 1
# Hop limit 1 - prevents container SSRF reaching IMDSCross-references: A01 (Access Control), A05 (Misconfiguration). Detay: Bölüm 11, 32, 33.
Hızlı Reference Matrix — OWASP × Bölüm
Kapanış
OWASP Top 10, modern security'nin ortak dilidir. Regulator audit'lerinde, security review'larda, code review'da, threat modeling toplantısında — bu kategoriler ortak referans. Modern bir auth mühendisi, her bir OWASP item'ın kendi sisteminde nasıl manifest olduğunu bilmelidir.
Bu appendix, OWASP Top 10:2021'in auth context'inde derinleşmesini sunar. 2025–2026'da Top 10:2025 yeni versiyon bekleniyor — büyük ihtimalle:
- API security ayrı kategori olabilir (şu an A01-A07'ye dağılmış)
- AI/ML security yeni kategori olarak eklenebilir
- Supply chain security A06'dan ayrı genişleyebilir
Modern bir auth mühendisi, OWASP'yi periodic olarak (yıllık ya da major spec güncellemelerinde) re-read edip kendi sistemini bu kategorilere göre değerlendirmelidir. Top 10, tek seferlik checklist değil; sürekli mental model.
41 bölümlük yolculuğun ardından bu 3 appendix — Glossary, Spring Security Cheatsheet, OWASP Cross-Reference — production'da çalışırken 30 saniyede doğru pattern'i hatırlayabilmenize hizmet etmek için yazıldı. Yolculuk tamamlandı; çalışma devam ediyor.
"Bilgi, ezberlenince fayda etmez; ihtiyaç anında doğru yerde olduğunda fayda eder. Bu üç appendix, o anlarda doğru yerde olmak için yazıldı."
İçindekiler… « Önceki [Appendix B: Spring Security Cheatsheet — Configuration Patterns] » https://gitlab.com/sahin.yelkenci2/auth-authorization-series