Introduction
The OWASP Top 10 represents critical web application security risks. This comprehensive guide covers mitigation strategies for each vulnerability category with practical Java implementations, focusing on Spring Security and enterprise best practices.
1. Broken Access Control
Authentication & Authorization
@Configuration @EnableWebSecurity public class AccessControlSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authz -> authz // Role-based access control .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/private/**").authenticated() // HTTP method restrictions .requestMatchers(HttpMethod.DELETE, "/api/users/**").hasRole("ADMIN") .requestMatchers(HttpMethod.PUT, "/api/users/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation().migrateSession() .maximumSessions(1) .maxSessionsPreventsLogin(true) ) .csrf(CSRFConfigurer::disable) // Only if properly configured for API .build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("user") .password("{bcrypt}$2a$10$dXJ3SW6G7P.XBLBvanJY.2eZ8L7C3ZJN9YFZJZvZvZvZvZvZvZvZv") .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password("{bcrypt}$2a$10$dXJ3SW6G7P.XBLBvanJY.2eZ8L7C3ZJN9YFZJZvZvZvZvZvZvZvZv") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } } Method-Level Security
@RestController @RequestMapping("/api/users") public class UserController { @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id") @GetMapping("/{userId}") public ResponseEntity<User> getUser(@PathVariable String userId) { // Users can only access their own data, admins can access all return ResponseEntity.ok(userService.findById(userId)); } @PreAuthorize("hasRole('ADMIN')") @DeleteMapping("/{userId}") public ResponseEntity<Void> deleteUser(@PathVariable String userId) { userService.delete(userId); return ResponseEntity.noContent().build(); } @PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')") @GetMapping("/{userId}/documents") public Document getDocument(@PathVariable String userId) { return documentService.findByUserId(userId); } @PreFilter("filterObject.owner == authentication.name or hasRole('ADMIN')") @PostMapping("/documents/batch") public ResponseEntity<List<Document>> createDocuments( @RequestBody List<Document> documents) { List<Document> saved = documentService.saveAll(documents); return ResponseEntity.ok(saved); } } // Custom permission evaluator @Component public class CustomPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { if (targetDomainObject instanceof Document) { Document doc = (Document) targetDomainObject; return doc.getOwner().equals(authentication.getName()) || authentication.getAuthorities().stream() .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")); } return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { if ("Document".equals(targetType)) { Document doc = documentService.findById((String) targetId); return hasPermission(authentication, doc, permission); } return false; } } 2. Cryptographic Failures
Secure Password Hashing
@Service public class PasswordService { private final PasswordEncoder passwordEncoder; public PasswordService() { this.passwordEncoder = new BCryptPasswordEncoder(12); // Strong work factor } public String hashPassword(String plainPassword) { return passwordEncoder.encode(plainPassword); } public boolean verifyPassword(String plainPassword, String hashedPassword) { return passwordEncoder.matches(plainPassword, hashedPassword); } public boolean isPasswordStrong(String password) { // Enforce password policy if (password == null || password.length() < 12) { return false; } Pattern pattern = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{12,}$"); return pattern.matcher(password).matches(); } } // Secure random token generation @Component public class TokenGenerator { private final SecureRandom secureRandom = new SecureRandom(); public String generateSecureToken(int byteLength) { byte[] bytes = new byte[byteLength]; secureRandom.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } public String generateCryptographicNonce() { byte[] nonce = new byte[16]; secureRandom.nextBytes(nonce); return Hex.encodeHexString(nonce); } } Encryption/Decryption Service
@Service public class EncryptionService { private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; private static final int IV_LENGTH_BYTE = 12; private final SecretKey secretKey; public EncryptionService(@Value("${encryption.secret.key}") String base64Key) throws GeneralSecurityException { byte[] keyBytes = Base64.getDecoder().decode(base64Key); this.secretKey = new SecretKeySpec(keyBytes, "AES"); } public String encrypt(String plaintext) throws GeneralSecurityException { byte[] iv = new byte[IV_LENGTH_BYTE]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); byte[] encrypted = new byte[iv.length + cipherText.length]; System.arraycopy(iv, 0, encrypted, 0, iv.length); System.arraycopy(cipherText, 0, encrypted, iv.length, cipherText.length); return Base64.getEncoder().encodeToString(encrypted); } public String decrypt(String encryptedText) throws GeneralSecurityException { byte[] decoded = Base64.getDecoder().decode(encryptedText); byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH_BYTE); byte[] cipherText = Arrays.copyOfRange(decoded, IV_LENGTH_BYTE, decoded.length); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); byte[] plaintext = cipher.doFinal(cipherText); return new String(plaintext, StandardCharsets.UTF_8); } } 3. Injection Prevention
SQL Injection Protection
@Repository public class SecureUserRepository { private final JdbcTemplate jdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; public SecureUserRepository(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } // Safe parameterized query public User findByUsername(String username) { String sql = "SELECT id, username, email FROM users WHERE username = ? AND active = true"; return jdbcTemplate.queryForObject(sql, new Object[]{username}, (rs, rowNum) -> new User( rs.getString("id"), rs.getString("username"), rs.getString("email") )); } // Safe with named parameters public List<User> findUsersByRoleAndStatus(String role, boolean active) { String sql = "SELECT * FROM users WHERE role = :role AND active = :active"; Map<String, Object> params = Map.of("role", role, "active", active); return namedParameterJdbcTemplate.query(sql, params, (rs, rowNum) -> new User( rs.getString("id"), rs.getString("username"), rs.getString("email") )); } // Input validation and sanitization public List<User> searchUsers(String searchTerm) { if (!isValidSearchTerm(searchTerm)) { throw new IllegalArgumentException("Invalid search term"); } // Sanitize input String sanitizedTerm = sanitizeSearchTerm(searchTerm); String sql = "SELECT * FROM users WHERE username LIKE ? OR email LIKE ?"; String likePattern = "%" + sanitizedTerm + "%"; return jdbcTemplate.query(sql, new Object[]{likePattern, likePattern}, (rs, rowNum) -> new User( rs.getString("id"), rs.getString("username"), rs.getString("email") )); } private boolean isValidSearchTerm(String term) { // Allow only alphanumeric and basic search characters return term != null && term.matches("^[a-zA-Z0-9@._\\- ]{1,50}$"); } private String sanitizeSearchTerm(String term) { // Remove potential SQL injection characters return term.replace("'", "''") .replace(";", "") .replace("--", "") .replace("/*", "") .replace("*/", ""); } } NoSQL Injection Prevention
@Repository public class SecureMongoRepository { private final MongoTemplate mongoTemplate; // Safe query with criteria public List<User> findUsersByUsername(String username) { Query query = new Query(); query.addCriteria(Criteria.where("username").is(username) .and("active").is(true)); return mongoTemplate.find(query, User.class); } // Safe aggregation pipeline public List<User> searchUsers(String searchTerm) { if (!isValidSearchTerm(searchTerm)) { throw new IllegalArgumentException("Invalid search term"); } Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("username").regex("^" + searchTerm, "i")), Aggregation.limit(100) ); return mongoTemplate.aggregate(aggregation, "users", User.class).getMappedResults(); } // Prevent operator injection public List<User> findUsersWithFilter(Map<String, Object> filter) { Query query = new Query(); for (Map.Entry<String, Object> entry : filter.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); // Only allow specific fields and simple values if (isAllowedField(key) && !isDangerousValue(value)) { query.addCriteria(Criteria.where(key).is(value)); } } return mongoTemplate.find(query, User.class); } private boolean isAllowedField(String field) { return List.of("username", "email", "active", "role").contains(field); } private boolean isDangerousValue(Object value) { // Prevent MongoDB operator injection if (value instanceof String) { String str = (String) value; return str.startsWith("$") || str.contains("{"); } return false; } } 4. Insecure Design
Secure Design Patterns
// Anti-fragile domain model @Entity @Table(name = "bank_accounts") public class BankAccount { @Id private String id; @Column(nullable = false) private String accountNumber; @Column(nullable = false) private BigDecimal balance; @Column(nullable = false) private String currency; @Column(nullable = false) private boolean active; @Version private Long version; // Optimistic locking // Business logic in domain model public void withdraw(BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("Withdrawal amount must be positive"); } if (amount.compareTo(balance) > 0) { throw new InsufficientFundsException("Insufficient funds for withdrawal"); } if (!active) { throw new IllegalStateException("Cannot withdraw from inactive account"); } // Daily withdrawal limit if (amount.compareTo(new BigDecimal("5000")) > 0) { throw new WithdrawalLimitExceededException("Daily withdrawal limit exceeded"); } this.balance = this.balance.subtract(amount); } public void deposit(BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("Deposit amount must be positive"); } // Anti-money laundering check if (amount.compareTo(new BigDecimal("10000")) > 0) { throw new LargeDepositException("Large deposits require additional verification"); } this.balance = this.balance.add(amount); } } // Secure service layer with business rules @Service @Transactional public class BankAccountService { private final BankAccountRepository accountRepository; private final AuditService auditService; private final FraudDetectionService fraudDetectionService; public BankAccountService(BankAccountRepository accountRepository, AuditService auditService, FraudDetectionService fraudDetectionService) { this.accountRepository = accountRepository; this.auditService = auditService; this.fraudDetectionService = fraudDetectionService; } public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) { BankAccount fromAccount = accountRepository.findById(fromAccountId) .orElseThrow(() -> new AccountNotFoundException(fromAccountId)); BankAccount toAccount = accountRepository.findById(toAccountId) .orElseThrow(() -> new AccountNotFoundException(toAccountId)); // Business rule: Check for suspicious activity if (fraudDetectionService.isSuspiciousTransfer(fromAccount, toAccount, amount)) { auditService.logSuspiciousActivity(fromAccountId, toAccountId, amount); throw new SuspiciousActivityException("Transfer flagged for review"); } // Business rule: Ensure both accounts are in same currency if (!fromAccount.getCurrency().equals(toAccount.getCurrency())) { throw new CurrencyMismatchException("Accounts must have same currency"); } fromAccount.withdraw(amount); toAccount.deposit(amount); accountRepository.save(fromAccount); accountRepository.save(toAccount); auditService.logTransfer(fromAccountId, toAccountId, amount); } } 5. Security Misconfiguration
Secure Application Configuration
@Configuration @EnableConfigurationProperties(SecurityProperties.class) public class SecurityConfiguration { @Bean public FilterRegistrationBean<ErrorLoggingFilter> loggingFilter() { FilterRegistrationBean<ErrorLoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new ErrorLoggingFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(1); return registrationBean; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .headers(headers -> headers .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'")) .frameOptions().deny() .xssProtection().block(true) ) .csrf(csrf -> csrf .ignoringRequestMatchers("/api/public/**") ) .cors(cors -> cors .configurationSource(corsConfigurationSource()) ) .build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("https://trusted-domain.com")); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/**", configuration); return source; } } // Secure properties configuration @ConfigurationProperties(prefix = "app.security") @Data public class SecurityProperties { private boolean debugMode = false; private String allowedOrigins = "https://trusted-domain.com"; private int sessionTimeout = 1800; private boolean httpsOnly = true; private String adminEmail = "[email protected]"; } // Production security checklist @Component public class SecurityStartupValidator implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { validateSecurityConfiguration(); } private void validateSecurityConfiguration() { checkForDefaultPasswords(); checkDebugModeDisabled(); checkSensitiveEndpoints(); checkTLSConfiguration(); } private void checkForDefaultPasswords() { // Implement checks for default credentials } private void checkDebugModeDisabled() { // Ensure debug mode is off in production } private void checkSensitiveEndpoints() { // Verify sensitive endpoints are protected } private void checkTLSConfiguration() { // Verify TLS is properly configured } } 6. Vulnerable and Outdated Components
Dependency Security Management
// pom.xml with security-focused dependencies <!-- OWASP Dependency Check --> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>8.2.1</version> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> <!-- Security scanner integration --> @Component public class DependencySecurityScanner { public void scanDependencies() { try { Process process = Runtime.getRuntime().exec( "mvn org.owasp:dependency-check-maven:check"); int exitCode = process.waitFor(); if (exitCode != 0) { log.warn("Dependency check found vulnerabilities"); // Send alert to security team } } catch (Exception e) { log.error("Dependency scan failed", e); } } } // Automated security updates @Configuration @EnableScheduling public class SecurityUpdateConfig { @Scheduled(cron = "0 0 2 * * MON") // Run every Monday at 2 AM public void checkForSecurityUpdates() { log.info("Checking for security updates..."); // Implement automated update checks } } // Version management utility @Component public class ComponentVersionManager { private final Map<String, String> minimumVersions = Map.of( "spring-security", "5.8.0", "log4j", "2.17.0", "jackson", "2.14.0" ); public void validateComponentVersions() { minimumVersions.forEach((component, minVersion) -> { String currentVersion = getCurrentVersion(component); if (isVersionVulnerable(currentVersion, minVersion)) { throw new SecurityException( String.format("Vulnerable component: %s version %s", component, currentVersion)); } }); } private boolean isVersionVulnerable(String current, String minimum) { // Implement version comparison logic return current.compareTo(minimum) < 0; } private String getCurrentVersion(String component) { // Extract version from classpath try { Package pkg = Package.getPackage("org.springframework.security"); return pkg != null ? pkg.getImplementationVersion() : "unknown"; } catch (Exception e) { return "unknown"; } } } 7. Identification and Authentication Failures
Multi-Factor Authentication
@Service public class MultiFactorAuthenticationService { private final UserRepository userRepository; private final OTPService otpService; private final EmailService emailService; private final SMSService smsService; public MultiFactorAuthenticationService(UserRepository userRepository, OTPService otpService, EmailService emailService, SMSService smsService) { this.userRepository = userRepository; this.otpService = otpService; this.emailService = emailService; this.smsService = smsService; } public void initiateMFA(String username, String sessionId) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UserNotFoundException(username)); // Generate OTP String otp = otpService.generateOTP(username); // Send OTP via preferred method switch (user.getMfaPreference()) { case EMAIL: emailService.sendOTP(user.getEmail(), otp); break; case SMS: smsService.sendOTP(user.getPhoneNumber(), otp); break; case AUTHENTICATOR_APP: // TOTP handled by app break; } // Store MFA session mfaSessionRepository.save(new MFASession(sessionId, username, otp)); } public boolean verifyMFA(String sessionId, String otp) { MFASession session = mfaSessionRepository.findBySessionId(sessionId) .orElseThrow(() -> new InvalidSessionException(sessionId)); if (session.isExpired()) { mfaSessionRepository.delete(session); throw new SessionExpiredException("MFA session expired"); } boolean isValid = otpService.validateOTP(session.getUsername(), otp); if (isValid) { mfaSessionRepository.delete(session); auditService.logMFASuccess(session.getUsername()); } else { auditService.logMFAFailure(session.getUsername()); } return isValid; } } // Secure authentication configuration @Configuration @EnableWebSecurity public class AuthenticationSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .formLogin(form -> form .loginPage("/login") .failureHandler(authenticationFailureHandler()) .successHandler(authenticationSuccessHandler()) ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessHandler(logoutSuccessHandler()) .deleteCookies("JSESSIONID") .invalidateHttpSession(true) ) .sessionManagement(session -> session .sessionFixation().migrateSession() .maximumSessions(1) .maxSessionsPreventsLogin(true) .expiredUrl("/login?expired") ) .rememberMe(rememberMe -> rememberMe .key("uniqueAndSecret") .tokenValiditySeconds(86400) // 24 hours .rememberMeParameter("remember-me") ) .build(); } @Bean public AuthenticationFailureHandler authenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { return new CustomAuthenticationSuccessHandler(); } } // Custom authentication handlers @Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { // Log failed attempt String username = request.getParameter("username"); auditService.logFailedLogin(username, request.getRemoteAddr()); // Implement account lockout int failedAttempts = loginAttemptService.recordFailedAttempt(username); if (failedAttempts >= MAX_FAILED_ATTEMPTS) { userService.lockAccount(username); } response.sendRedirect("/login?error=true"); } } 8. Software and Data Integrity Failures
Integrity Verification
@Service public class IntegrityService { private final Mac hmac; public IntegrityService(@Value("${app.integrity.secret}") String secretKey) throws GeneralSecurityException { SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"); this.hmac = Mac.getInstance("HmacSHA256"); this.hmac.init(keySpec); } public String generateIntegrityHash(String data) { byte[] hash = hmac.doFinal(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(hash); } public boolean verifyIntegrity(String data, String expectedHash) { String actualHash = generateIntegrityHash(data); return MessageDigest.isEqual( actualHash.getBytes(), expectedHash.getBytes() ); } public SignedData signData(Object data) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); String jsonData = mapper.writeValueAsString(data); String signature = generateIntegrityHash(jsonData); long timestamp = System.currentTimeMillis(); return new SignedData(jsonData, signature, timestamp); } public <T> T verifyAndDeserialize(SignedData signedData, Class<T> type) throws JsonProcessingException, IntegrityException { // Verify timestamp (prevent replay attacks) long currentTime = System.currentTimeMillis(); if (currentTime - signedData.getTimestamp() > MAX_ALLOWED_AGE) { throw new IntegrityException("Data is too old"); } // Verify signature if (!verifyIntegrity(signedData.getData(), signedData.getSignature())) { throw new IntegrityException("Data integrity check failed"); } ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(signedData.getData(), type); } } // Secure file upload with integrity checks @RestController public class SecureFileUploadController { @PostMapping("/upload") public ResponseEntity<String> uploadFile( @RequestParam("file") MultipartFile file, @RequestHeader("X-File-Hash") String expectedHash) { try { // Verify file size limits if (file.getSize() > MAX_FILE_SIZE) { return ResponseEntity.badRequest().body("File too large"); } // Verify file type if (!isAllowedFileType(file.getContentType(), file.getOriginalFilename())) { return ResponseEntity.badRequest().body("Invalid file type"); } // Calculate file hash String fileHash = calculateFileHash(file.getBytes()); // Verify integrity if (!fileHash.equals(expectedHash)) { auditService.logIntegrityViolation(file.getOriginalFilename()); return ResponseEntity.badRequest().body("File integrity check failed"); } // Process file fileService.saveFile(file); return ResponseEntity.ok("File uploaded successfully"); } catch (Exception e) { return ResponseEntity.internalServerError().body("Upload failed"); } } private String calculateFileHash(byte[] content) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(content); return Base64.getEncoder().encodeToString(hash); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Hash algorithm not available", e); } } private boolean isAllowedFileType(String contentType, String filename) { Set<String> allowedTypes = Set.of("image/jpeg", "image/png", "application/pdf"); Set<String> allowedExtensions = Set.of(".jpg", ".jpeg", ".png", ".pdf"); String extension = filename.substring(filename.lastIndexOf(".")).toLowerCase(); return allowedTypes.contains(contentType) && allowedExtensions.contains(extension); } } 9. Security Logging and Monitoring
Comprehensive Security Logging
@Component @Slf4j public class SecurityLogger { public void logSecurityEvent(SecurityEvent event) { MDC.put("userId", event.getUserId()); MDC.put("ipAddress", event.getIpAddress()); MDC.put("sessionId", event.getSessionId()); switch (event.getType()) { case LOGIN_SUCCESS: log.info("User login successful: {}", event.getDetails()); break; case LOGIN_FAILURE: log.warn("User login failed: {}", event.getDetails()); break; case UNAUTHORIZED_ACCESS: log.error("Unauthorized access attempt: {}", event.getDetails()); break; case DATA_ACCESS: log.debug("Data access: {}", event.getDetails()); break; case CONFIGURATION_CHANGE: log.info("Configuration change: {}", event.getDetails()); break; } MDC.clear(); } public void logSuspiciousActivity(String username, String activity, String details) { log.warn("Suspicious activity detected - User: {}, Activity: {}, Details: {}", username, activity, details); // Alert security team for critical events if (isCriticalActivity(activity)) { alertSecurityTeam(username, activity, details); } } } // Security event auditing @Entity @Table(name = "security_audit_log") public class SecurityAuditLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private Instant timestamp; @Column(nullable = false) private String eventType; @Column(nullable = false) private String username; private String ipAddress; @Column(length = 1000) private String description; private boolean success; // Constructor, getters, setters } // Real-time security monitoring @Service public class SecurityMonitoringService { private final List<SecurityAlertListener> listeners = new ArrayList<>(); public void monitorRequest(HttpServletRequest request) { String path = request.getRequestURI(); String method = request.getMethod(); String ip = request.getRemoteAddr(); // Detect suspicious patterns if (isSuspiciousPath(path)) { triggerAlert("Suspicious path access", String.format("Path: %s, IP: %s", path, ip)); } if (isRateLimited(ip)) { triggerAlert("Rate limit exceeded", String.format("IP: %s has exceeded rate limits", ip)); } } public void addListener(SecurityAlertListener listener) { listeners.add(listener); } private void triggerAlert(String type, String details) { SecurityAlert alert = new SecurityAlert(type, details, Instant.now()); listeners.forEach(listener -> listener.onSecurityAlert(alert)); } } 10. Server-Side Request Forgery (SSRF)
SSRF Protection
@Service public class SSRFProtectionService { private final Set<String> allowedDomains = Set.of( "api.trusted-service.com", "internal-service.company.com" ); private final Set<String> blockedIpRanges = Set.of( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16" ); public URL validateAndSanitizeUrl(String urlString) throws SSRFException { try { URL url = new URL(urlString); // Validate scheme if (!List.of("http", "https").contains(url.getProtocol())) { throw new SSRFException("Unsupported protocol: " + url.getProtocol()); } // Resolve and validate host String host = url.getHost(); InetAddress address = InetAddress.getByName(host); // Check against blocked IP ranges if (isBlockedIp(address)) { throw new SSRFException("Access to internal IP blocked: " + host); } // Check against allowed domains if (!isAllowedDomain(host)) { throw new SSRFException("Domain not allowed: " + host); } return url; } catch (MalformedURLException | UnknownHostException e) { throw new SSRFException("Invalid URL: " + e.getMessage()); } } private boolean isBlockedIp(InetAddress address) { String ip = address.getHostAddress(); return blockedIpRanges.stream() .anyMatch(range -> isInRange(ip, range)); } private boolean isInRange(String ip, String cidr) { try { SubnetUtils utils = new SubnetUtils(cidr); return utils.getInfo().isInRange(ip); } catch (Exception e) { return false; } } private boolean isAllowedDomain(String host) { return allowedDomains.stream() .anyMatch(allowed -> host.equals(allowed) || host.endsWith("." + allowed)); } public String makeSafeHttpRequest(String urlString) throws SSRFException { URL url = validateAndSanitizeUrl(urlString); try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // Set timeouts connection.setConnectTimeout(5000); connection.setReadTimeout(10000); // Restrict methods connection.setRequestMethod("GET"); // Set user agent connection.setRequestProperty("User-Agent", "SafeInternalClient/1.0"); // Read response int responseCode = connection.getResponseCode(); if (responseCode == 200) { return IOUtils.toString(connection.getInputStream(), StandardCharsets.UTF_8); } else { throw new SSRFException("HTTP error: " + responseCode); } } catch (IOException e) { throw new SSRFException("Request failed: " + e.getMessage()); } } } // SSRF-safe REST client @Component public class SafeRestClient { private final SSRFProtectionService ssrfProtectionService; private final RestTemplate restTemplate; public SafeRestClient(SSRFProtectionService ssrfProtectionService) { this.ssrfProtectionService = ssrfProtectionService; this.restTemplate = new RestTemplate(); // Configure safe RestTemplate restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override public void handleError(ClientHttpResponse response) throws IOException { // Log errors but don't expose internal details log.warn("HTTP request failed with status: {}", response.getStatusCode()); } }); } public <T> T getForObject(String url, Class<T> responseType) throws SSRFException { // Validate URL before making request URL safeUrl = ssrfProtectionService.validateAndSanitizeUrl(url); try { return restTemplate.getForObject(safeUrl.toString(), responseType); } catch (RestClientException e) { throw new SSRFException("Request failed: " + e.getMessage()); } } } Conclusion
Implementing OWASP Top 10 mitigations in Java requires a comprehensive approach covering:
- Strong access control with proper authentication and authorization
- Cryptographic security with modern algorithms and proper key management
- Input validation and parameterized queries to prevent injections
- Secure design principles with business logic validation
- Hardened configurations with security headers and CORS policies
- Dependency management with regular security updates
- Multi-factor authentication and secure session management
- Data integrity verification and secure file handling
- Comprehensive logging and real-time monitoring
- SSRF protection with URL validation and network controls
By implementing these security measures systematically, Java applications can significantly reduce their attack surface and protect against the most critical web application security risks.