Introduction to OWASP Top 10
The OWASP (Open Web Application Security Project) Top 10 is a standard awareness document representing the most critical security risks to web applications. This comprehensive guide covers mitigation strategies for each vulnerability category using Java best practices.
1. Broken Access Control
Prevention Techniques
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.stereotype.Controller; import javax.servlet.http.HttpSession; @Controller public class AccessControlMitigation { // 1.1 Role-Based Access Control (RBAC) @PreAuthorize("hasRole('ADMIN')") public void adminOnlyMethod() { // Only users with ADMIN role can access this method } @PreAuthorize("hasPermission(#userId, 'USER', 'READ')") public User getUserProfile(String userId) { // Users can only access their own profile return userService.findUserById(userId); } // 1.2 Method-Level Security @PreAuthorize("#user.id == authentication.principal.id") public void updateUserProfile(User user) { // Users can only update their own profile userService.updateUser(user); } } // Secure Configuration @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) .and() .sessionManagement() .sessionFixation().migrateSession() .maximumSessions(1) .maxSessionsPreventsLogin(true); } @Bean public AccessDeniedHandler accessDeniedHandler() { return (request, response, accessDeniedException) -> { // Log access denied attempts logger.warn("Access denied for user: " + request.getUserPrincipal().getName() + " to URL: " + request.getRequestURI()); response.sendError(HttpStatus.FORBIDDEN.value(), "Access Denied"); }; } } // Business Logic Access Control @Service public class OrderService { public Order getOrder(String orderId, String currentUserId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException(orderId)); // Verify the current user owns this order if (!order.getUserId().equals(currentUserId) && !hasAdminRole(currentUserId)) { throw new AccessDeniedException("User cannot access this order"); } return order; } private boolean hasAdminRole(String userId) { // Check if user has admin role return userService.getUserRoles(userId).contains("ROLE_ADMIN"); } } 2. Cryptographic Failures
Secure Cryptography Implementation
import javax.crypto.*; import javax.crypto.spec.*; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; public class CryptographicSecurity { // 2.1 Secure Password Hashing public class PasswordService { private static final int SALT_LENGTH = 32; private static final int HASH_LENGTH = 64; private static final int ITERATIONS = 210000; public String hashPassword(String password) { try { // Generate random salt SecureRandom random = new SecureRandom(); byte[] salt = new byte[SALT_LENGTH]; random.nextBytes(salt); // Hash password with salt PBEKeySpec spec = new PBEKeySpec( password.toCharArray(), salt, ITERATIONS, HASH_LENGTH * 8); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = skf.generateSecret(spec).getEncoded(); // Combine salt and hash byte[] combined = new byte[salt.length + hash.length]; System.arraycopy(salt, 0, combined, 0, salt.length); System.arraycopy(hash, 0, combined, salt.length, hash.length); return Base64.getEncoder().encodeToString(combined); } catch (Exception e) { throw new SecurityException("Password hashing failed", e); } } public boolean verifyPassword(String password, String storedHash) { try { byte[] combined = Base64.getDecoder().decode(storedHash); byte[] salt = new byte[SALT_LENGTH]; byte[] storedPasswordHash = new byte[HASH_LENGTH]; System.arraycopy(combined, 0, salt, 0, SALT_LENGTH); System.arraycopy(combined, SALT_LENGTH, storedPasswordHash, 0, HASH_LENGTH); PBEKeySpec spec = new PBEKeySpec( password.toCharArray(), salt, ITERATIONS, HASH_LENGTH * 8); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] testHash = skf.generateSecret(spec).getEncoded(); return MessageDigest.isEqual(storedPasswordHash, testHash); } catch (Exception e) { throw new SecurityException("Password verification failed", e); } } } // 2.2 Secure Encryption public class EncryptionService { private static final String ALGORITHM = "AES/GCM/NoPadding"; private static final int TAG_LENGTH = 128; private static final int IV_LENGTH = 12; public String encrypt(String plaintext, SecretKey key) { try { // Generate random IV SecureRandom random = new SecureRandom(); byte[] iv = new byte[IV_LENGTH]; random.nextBytes(iv); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec); byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); // Combine IV and ciphertext 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); } catch (Exception e) { throw new SecurityException("Encryption failed", e); } } public String decrypt(String encryptedData, SecretKey key) { try { byte[] encrypted = Base64.getDecoder().decode(encryptedData); // Extract IV and ciphertext byte[] iv = new byte[IV_LENGTH]; byte[] ciphertext = new byte[encrypted.length - IV_LENGTH]; System.arraycopy(encrypted, 0, iv, 0, IV_LENGTH); System.arraycopy(encrypted, IV_LENGTH, ciphertext, 0, ciphertext.length); Cipher cipher = Cipher.getInstance(ALGORITHM); GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec); byte[] plaintext = cipher.doFinal(ciphertext); return new String(plaintext, StandardCharsets.UTF_8); } catch (Exception e) { throw new SecurityException("Decryption failed", e); } } } // 2.3 Secure Key Management @Component public class KeyManagementService { private final String KEY_ALIAS = "app-encryption-key"; private final KeyStore keyStore; @PostConstruct public void initialize() throws Exception { keyStore = KeyStore.getInstance("JCEKS"); char[] password = System.getenv("KEYSTORE_PASSWORD").toCharArray(); // Load or create keystore try (InputStream is = getClass().getResourceAsStream("/keystore.jceks")) { if (is != null) { keyStore.load(is, password); } else { keyStore.load(null, password); generateNewKey(); } } } private void generateNewKey() throws Exception { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); SecretKey key = keyGen.generateKey(); KeyStore.SecretKeyEntry keyEntry = new KeyStore.SecretKeyEntry(key); KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection( System.getenv("KEY_PASSWORD").toCharArray()); keyStore.setEntry(KEY_ALIAS, keyEntry, protection); // Save keystore to secure location try (FileOutputStream fos = new FileOutputStream("keystore.jceks")) { keyStore.store(fos, System.getenv("KEYSTORE_PASSWORD").toCharArray()); } } public SecretKey getEncryptionKey() throws Exception { KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection( System.getenv("KEY_PASSWORD").toCharArray()); KeyStore.Entry entry = keyStore.getEntry(KEY_ALIAS, protection); return ((KeyStore.SecretKeyEntry) entry).getSecretKey(); } } } 3. Injection
SQL Injection Prevention
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.RowMapper; import javax.persistence.EntityManager; import javax.persistence.Query; import java.sql.*; @Repository public class InjectionPrevention { // 3.1 Prepared Statements (JDBC) public User findUserById(String userId) { String sql = "SELECT * FROM users WHERE id = ? AND status = 'ACTIVE'"; return jdbcTemplate.queryForObject(sql, new Object[]{userId}, (rs, rowNum) -> { User user = new User(); user.setId(rs.getString("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); return user; }); } // 3.2 JPA/Hibernate Parameterized Queries @PersistenceContext private EntityManager entityManager; public List<User> findUsersByRole(String role) { String jpql = "SELECT u FROM User u WHERE u.role = :role AND u.active = true"; return entityManager.createQuery(jpql, User.class) .setParameter("role", role) .getResultList(); } // 3.3 Stored Procedures public void updateUserStatus(String userId, String status) { String sql = "CALL update_user_status(?, ?)"; jdbcTemplate.update(sql, userId, status); } } // 3.4 Input Validation and Sanitization @Component public class InputValidationService { private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,20}$"); private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile("([';\"\\\\]|(--[\\r\\n]|(;|--|#)[\\r\\n])|\\b(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})\\b)", Pattern.CASE_INSENSITIVE); public ValidationResult validateUserInput(UserInput input) { List<String> errors = new ArrayList<>(); if (!isValidUsername(input.getUsername())) { errors.add("Invalid username format"); } if (!isValidEmail(input.getEmail())) { errors.add("Invalid email format"); } if (containsSQLInjection(input.getSearchQuery())) { errors.add("Potential SQL injection detected"); } return new ValidationResult(errors.isEmpty(), errors); } private boolean isValidUsername(String username) { return username != null && USERNAME_PATTERN.matcher(username).matches(); } private boolean isValidEmail(String email) { return email != null && EMAIL_PATTERN.matcher(email).matches(); } private boolean containsSQLInjection(String input) { return input != null && SQL_INJECTION_PATTERN.matcher(input).find(); } public String sanitizeHTML(String input) { if (input == null) return null; // Basic HTML sanitization return input.replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replaceAll("'", "'") .replaceAll("/", "/"); } } // 3.5 NoSQL Injection Prevention @Repository public class MongoRepositorySecurity { @Autowired private MongoTemplate mongoTemplate; public List<User> findUsersByUsername(String username) { // Use typed queries instead of string concatenation Query query = new Query(); query.addCriteria(Criteria.where("username").is(username) .and("active").is(true)); return mongoTemplate.find(query, User.class); } // Secure aggregation queries public List<UserAggregation> getUserStatistics(String role) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("role").is(role)), Aggregation.group("department").count().as("userCount") ); return mongoTemplate.aggregate(aggregation, "users", UserAggregation.class) .getMappedResults(); } } 4. Insecure Design
Secure Architecture Patterns
// 4.1 Secure by Design Principles @Component public class SecureDesignPatterns { // Principle of Least Privilege @Service public class OrderService { private final PaymentProcessor paymentProcessor; private final AuditLogger auditLogger; @PreAuthorize("hasRole('USER')") @Transactional public Order processOrder(OrderRequest request, String currentUserId) { // Validate business rules if (!isValidOrder(request)) { throw new InvalidOrderException("Order validation failed"); } // Process payment with limited context PaymentResult payment = paymentProcessor.charge( request.getAmount(), request.getPaymentToken(), currentUserId ); if (!payment.isSuccessful()) { throw new PaymentFailedException("Payment processing failed"); } // Create order Order order = createOrder(request, currentUserId, payment); // Audit the transaction auditLogger.logOrderCreation(order, currentUserId); return order; } private boolean isValidOrder(OrderRequest request) { return request.getAmount().compareTo(BigDecimal.ZERO) > 0 && request.getItems() != null && !request.getItems().isEmpty() && isValidPaymentToken(request.getPaymentToken()); } } // 4.2 Fail-Safe Defaults @Configuration public class SecurityDefaults { @Bean public SecurityFilterChain defaultSecurity(HttpSecurity http) throws Exception { return http // Default deny all .authorizeRequests(authz -> authz .anyRequest().denyAll() ) // Then explicitly allow what's needed .authorizeRequests(authz -> authz .antMatchers("/public/**").permitAll() .antMatchers("/api/**").authenticated() ) // Secure headers .headers(headers -> headers .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'") ) .frameOptions().deny() ) .build(); } } // 4.3 Complete Mediation @Component public class AccessMediator { private final Map<String, AccessPolicy> policies = new ConcurrentHashMap<>(); public boolean checkAccess(String resource, String action, User user) { AccessPolicy policy = policies.get(resource); if (policy == null) { // Default deny return false; } return policy.isAllowed(action, user); } public void validateAccess(String resource, String action, User user) { if (!checkAccess(resource, action, user)) { throw new AccessDeniedException( "Access denied to " + resource + " for action " + action); } } } // 4.4 Economy of Mechanism @Service public class SimpleAuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public AuthenticationResult authenticate(String username, String password) { // Simple, clear authentication logic Optional<User> userOpt = userRepository.findByUsername(username); if (userOpt.isEmpty()) { return AuthenticationResult.failure("User not found"); } User user = userOpt.get(); if (!passwordEncoder.matches(password, user.getPasswordHash())) { return AuthenticationResult.failure("Invalid password"); } if (!user.isActive()) { return AuthenticationResult.failure("Account inactive"); } return AuthenticationResult.success(user); } } } // 4.5 Threat Modeling Implementation @Component public class ThreatModelingService { private final List<ThreatDetector> threatDetectors; public SecurityAssessment assessApplication() { SecurityAssessment assessment = new SecurityAssessment(); for (ThreatDetector detector : threatDetectors) { List<Threat> threats = detector.detectThreats(); assessment.addThreats(threats); } return assessment; } public void validateAgainstThreatModel(HttpServletRequest request) { // Check for common attack patterns if (isSuspiciousRequest(request)) { securityMonitor.alertSuspiciousActivity(request); throw new SecurityException("Request blocked by threat model"); } } private boolean isSuspiciousRequest(HttpServletRequest request) { String userAgent = request.getHeader("User-Agent"); String path = request.getRequestURI(); // Detect potential scanning or attack tools return userAgent.contains("nmap") || userAgent.contains("sqlmap") || userAgent.contains("w3af") || path.contains("../") || // Path traversal path.contains("//"); // Double slash attacks } } 5. Security Misconfiguration
Secure Configuration Management
import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; @Configuration public class SecurityConfiguration { // 5.1 Secure Spring Boot Configuration @Configuration @EnableWebSecurity public class WebSecurityConfig { @Value("${app.security.require-ssl:true}") private boolean requireSSL; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { HttpSecurity config = http .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .headers(headers -> headers .contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'") .frameOptions().deny() .xssProtection().block(true) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .maximumSessions(1) .maxSessionsPreventsLogin(true) ); if (requireSSL) { config.requiresChannel().anyRequest().requiresSecure(); } return config.build(); } } // 5.2 Secure Server Configuration @Bean public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() { return factory -> { factory.addConnectorCustomizers(connector -> { connector.setAttribute("relaxedQueryChars", ""); connector.setAttribute("relaxedPathChars", ""); connector.setAttribute("maxParameterCount", "1000"); }); }; } // 5.3 Error Handling Configuration @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex) { // Log the full error internally logger.error("Internal server error", ex); // Return generic error message to client ErrorResponse error = new ErrorResponse( "An unexpected error occurred. Please try again later."); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(error); } @ExceptionHandler(AccessDeniedException.class) public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex) { ErrorResponse error = new ErrorResponse("Access denied"); return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); } } // 5.4 Environment-Specific Configuration @Configuration @Profile("prod") public class ProductionSecurityConfig { @Bean public SecurityFilterChain productionSecurity(HttpSecurity http) throws Exception { return http .headers(headers -> headers .httpStrictTransportSecurity() .includeSubDomains(true) .maxAgeInSeconds(31536000) ) .requiresChannel(channel -> channel .anyRequest().requiresSecure() ) .build(); } } } // 5.5 Security Headers Configuration @Component public class SecurityHeadersFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; // Set security headers httpResponse.setHeader("X-Content-Type-Options", "nosniff"); httpResponse.setHeader("X-Frame-Options", "DENY"); httpResponse.setHeader("X-XSS-Protection", "1; mode=block"); httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); httpResponse.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); httpResponse.setHeader("Feature-Policy", "geolocation 'none'; microphone 'none'; camera 'none'"); chain.doFilter(request, response); } } // 5.6 Secure CORS Configuration @Configuration public class CorsConfiguration { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("https://trusted-domain.com")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); configuration.setExposedHeaders(Arrays.asList("X-CSRF-TOKEN")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/**", configuration); return source; } } 6. Vulnerable and Outdated Components
Dependency Security Management
// 6.1 Dependency Security Scanner @Component public class DependencySecurityScanner { public void scanDependencies() { try { // Integrate with OWASP Dependency Check Process process = Runtime.getRuntime().exec( "dependency-check.sh --project myapp --scan . --format HTML"); int exitCode = process.waitFor(); if (exitCode != 0) { logger.warn("Dependency security scan found vulnerabilities"); securityAlertService.sendAlert("Vulnerable dependencies detected"); } } catch (Exception e) { logger.error("Dependency security scan failed", e); } } } // 6.2 Secure Dependency Configuration // pom.xml security configuration example /* <project> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.7.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>6.5.3</version> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> */ // 6.3 Runtime Component Validation @Component public class ComponentValidator { @PostConstruct public void validateComponents() { validateSpringSecurityVersion(); validateJacksonVersion(); validateLog4jVersion(); } private void validateSpringSecurityVersion() { String version = SpringVersion.getVersion(); if (version != null && isVulnerableVersion(version, "5.7.0")) { throw new SecurityException( "Vulnerable Spring Security version detected: " + version); } } private boolean isVulnerableVersion(String currentVersion, String minSafeVersion) { // Simple version comparison return currentVersion.compareTo(minSafeVersion) < 0; } } // 6.4 Security Update Automation @Service public class SecurityUpdateService { @Scheduled(cron = "0 0 2 * * ?") // Daily at 2 AM public void checkForSecurityUpdates() { try { List<SecurityUpdate> updates = securityFeedService.getSecurityUpdates(); for (SecurityUpdate update : updates) { if (affectsOurDependencies(update)) { securityTeamNotificationService.notify(update); if (update.getSeverity() == Severity.CRITICAL) { emergencyUpdateService.scheduleUpdate(update); } } } } catch (Exception e) { logger.error("Security update check failed", e); } } private boolean affectsOurDependencies(SecurityUpdate update) { // Check if update affects any of our dependencies return dependencyInventoryService.getDependencies().stream() .anyMatch(dep -> dep.getName().equals(update.getComponent()) && isVersionAffected(dep.getVersion(), update.getAffectedVersions())); } } 7. Identification and Authentication Failures
Secure Authentication Implementation
import org.springframework.security.authentication.*; import org.springframework.security.core.userdetails.UserDetailsService; @Service public class AuthenticationSecurity { // 7.1 Secure Authentication Service @Service public class SecureAuthenticationService { private final UserDetailsService userDetailsService; private final PasswordEncoder passwordEncoder; private final LoginAttemptService loginAttemptService; public AuthenticationResult authenticate(LoginRequest request, HttpServletRequest httpRequest) { String clientIP = getClientIP(httpRequest); // Check for brute force attempts if (loginAttemptService.isBlocked(clientIP)) { throw new AuthenticationException("Too many failed attempts. Please try again later."); } try { UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) { loginAttemptService.recordFailedAttempt(clientIP); throw new BadCredentialsException("Invalid credentials"); } if (!userDetails.isEnabled()) { throw new DisabledException("Account is disabled"); } if (!userDetails.isAccountNonLocked()) { throw new LockedException("Account is locked"); } if (!userDetails.isAccountNonExpired()) { throw new AccountExpiredException("Account has expired"); } // Successful login loginAttemptService.recordSuccessfulAttempt(clientIP); return AuthenticationResult.success(userDetails); } catch (UsernameNotFoundException e) { // Don't reveal that username doesn't exist loginAttemptService.recordFailedAttempt(clientIP); throw new BadCredentialsException("Invalid credentials"); } } private String getClientIP(HttpServletRequest request) { String xfHeader = request.getHeader("X-Forwarded-For"); if (xfHeader == null) { return request.getRemoteAddr(); } return xfHeader.split(",")[0]; } } // 7.2 Multi-Factor Authentication @Service public class MFAService { private final Map<String, String> mfaSecrets = new ConcurrentHashMap<>(); private final JavaMailSender mailSender; public void setupMFA(String username) { String secret = generateSecretKey(); mfaSecrets.put(username, secret); // Generate QR code URL for authenticator app String qrCodeUrl = generateQRCodeUrl(username, secret); sendMFASetupEmail(username, qrCodeUrl); } public boolean verifyMFA(String username, String code) { String secret = mfaSecrets.get(username); if (secret == null) { throw new MFAException("MFA not setup for user"); } return verifyCode(secret, code); } private String generateSecretKey() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[20]; random.nextBytes(bytes); Base32 base32 = new Base32(); return base32.encodeToString(bytes); } } // 7.3 Session Management @Component public class SecureSessionManagement { @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } public void configureSessionManagement(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .maximumSessions(1) .maxSessionsPreventsLogin(true) .sessionRegistry(sessionRegistry()) .and() .sessionFixation().migrateSession(); } } // 7.4 Password Policy Enforcement @Component public class PasswordPolicyEnforcer { private static final int MIN_LENGTH = 12; private static final int MAX_LENGTH = 128; private static final Pattern COMPLEXITY_PATTERN = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{" + MIN_LENGTH + "," + MAX_LENGTH + "}$"); public PasswordValidationResult validatePassword(String password) { List<String> errors = new ArrayList<>(); if (password == null || password.length() < MIN_LENGTH) { errors.add("Password must be at least " + MIN_LENGTH + " characters long"); } if (password != null && password.length() > MAX_LENGTH) { errors.add("Password must not exceed " + MAX_LENGTH + " characters"); } if (!COMPLEXITY_PATTERN.matcher(password).matches()) { errors.add("Password must contain uppercase, lowercase, number and special character"); } if (isCommonPassword(password)) { errors.add("Password is too common"); } return new PasswordValidationResult(errors.isEmpty(), errors); } private boolean isCommonPassword(String password) { // Check against common passwords list Set<String> commonPasswords = Set.of( "password", "123456", "qwerty", "letmein", "welcome"); return commonPasswords.contains(password.toLowerCase()); } } } 8. Software and Data Integrity Failures
Integrity Protection
import java.security.*; import java.util.zip.CRC32; @Component public class IntegrityProtection { // 8.1 Data Integrity Verification public class DataIntegrityService { private final Mac hmac; public DataIntegrityService(SecretKey key) throws Exception { this.hmac = Mac.getInstance("HmacSHA256"); this.hmac.init(key); } public String calculateHMAC(byte[] data) { byte[] hash = hmac.doFinal(data); return Base64.getEncoder().encodeToString(hash); } public boolean verifyHMAC(byte[] data, String expectedHMAC) { String actualHMAC = calculateHMAC(data); return MessageDigest.isEqual( actualHMAC.getBytes(StandardCharsets.UTF_8), expectedHMAC.getBytes(StandardCharsets.UTF_8) ); } public SignedData signData(byte[] data, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); byte[] digitalSignature = signature.sign(); return new SignedData(data, digitalSignature); } public boolean verifySignature(SignedData signedData, PublicKey publicKey) throws Exception { Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(signedData.getData()); return signature.verify(signedData.getSignature()); } } // 8.2 Secure Deserialization @Component public class SecureDeserialization { private final Set<String> allowedClasses = Set.of( "com.example.dto.*", "java.util.*", "java.lang.String" ); public Object deserializeSafely(byte[] data) throws Exception { try (ObjectInputStream ois = new SecureObjectInputStream( new ByteArrayInputStream(data), allowedClasses)) { return ois.readObject(); } } private static class SecureObjectInputStream extends ObjectInputStream { private final Set<String> allowedClasses; public SecureObjectInputStream(InputStream in, Set<String> allowedClasses) throws IOException { super(in); this.allowedClasses = allowedClasses; } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); if (!isClassAllowed(className)) { throw new SecurityException("Deserialization of class not allowed: " + className); } return super.resolveClass(desc); } private boolean isClassAllowed(String className) { return allowedClasses.stream() .anyMatch(pattern -> className.matches(pattern.replace("*", ".*"))); } } } // 8.3 Dependency Integrity Verification @Component public class DependencyIntegrityChecker { public boolean verifyDependencyIntegrity(File jarFile, String expectedHash) throws Exception { String actualHash = calculateFileHash(jarFile); return MessageDigest.isEqual( actualHash.getBytes(StandardCharsets.UTF_8), expectedHash.getBytes(StandardCharsets.UTF_8) ); } private String calculateFileHash(File file) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); try (InputStream is = new FileInputStream(file)) { byte[] buffer = new byte[8192]; int read; while ((read = is.read(buffer)) > 0) { digest.update(buffer, 0, read); } } return Base64.getEncoder().encodeToString(digest.digest()); } } // 8.4 Configuration Integrity @Component public class ConfigurationIntegrity { private final String CONFIG_SIGNATURE_HEADER = "X-Config-Signature"; public void validateConfigurationSignature(Properties config, String signature) throws Exception { String configString = propertiesToString(config); String calculatedSignature = calculateSignature(configString); if (!MessageDigest.isEqual( calculatedSignature.getBytes(StandardCharsets.UTF_8), signature.getBytes(StandardCharsets.UTF_8))) { throw new SecurityException("Configuration integrity check failed"); } } private String propertiesToString(Properties props) { return props.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(entry -> entry.getKey() + "=" + entry.getValue()) .collect(Collectors.joining("|")); } private String calculateSignature(String data) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(hash); } } } 9. Security Logging and Monitoring
Comprehensive Security Monitoring
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; @Component public class SecurityLogging { private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY"); // 9.1 Security Event Logging @Component public class SecurityEventLogger { public void logLoginSuccess(Authentication authentication, HttpServletRequest request) { Map<String, String> event = Map.of( "event", "LOGIN_SUCCESS", "username", authentication.getName(), "ip", getClientIP(request), "userAgent", request.getHeader("User-Agent"), "timestamp", Instant.now().toString() ); securityLogger.info("Security event: {}", event); } public void logLoginFailure(String username, HttpServletRequest request, String reason) { Map<String, String> event = Map.of( "event", "LOGIN_FAILURE", "username", username, "ip", getClientIP(request), "reason", reason, "timestamp", Instant.now().toString() ); securityLogger.warn("Security event: {}", event); } public void logAccessDenied(Authentication authentication, HttpServletRequest request) { Map<String, String> event = Map.of( "event", "ACCESS_DENIED", "username", authentication != null ? authentication.getName() : "anonymous", "ip", getClientIP(request), "resource", request.getRequestURI(), "timestamp", Instant.now().toString() ); securityLogger.warn("Security event: {}", event); } private String getClientIP(HttpServletRequest request) { String xfHeader = request.getHeader("X-Forwarded-For"); if (xfHeader == null) { return request.getRemoteAddr(); } return xfHeader.split(",")[0]; } } // 9.2 Audit Logging @Aspect @Component public class AuditLoggingAspect { @AfterReturning(pointcut = "@annotation(auditable)", returning = "result") public void logAuditEvent(JoinPoint joinPoint, Auditable auditable, Object result) { String action = auditable.action(); String resource = auditable.resource(); Object[] args = joinPoint.getArgs(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String username = authentication != null ? authentication.getName() : "system"; Map<String, Object> auditEvent = Map.of( "action", action, "resource", resource, "user", username, "timestamp", Instant.now(), "parameters", Arrays.toString(args), "result", result != null ? "SUCCESS" : "FAILURE" ); securityLogger.info("Audit event: {}", auditEvent); } } // Custom annotation for audit logging @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Auditable { String action(); String resource(); } // 9.3 Security Monitoring Service @Component public class SecurityMonitoringService { private final Map<String, Integer> failedLoginAttempts = new ConcurrentHashMap<>(); private final Map<String, Instant> ipBlockTimes = new ConcurrentHashMap<>(); private static final int MAX_FAILED_ATTEMPTS = 5; private static final Duration BLOCK_DURATION = Duration.ofMinutes(30); public void recordFailedLogin(String ipAddress) { failedLoginAttempts.merge(ipAddress, 1, Integer::sum); if (failedLoginAttempts.get(ipAddress) >= MAX_FAILED_ATTEMPTS) { blockIPAddress(ipAddress); } } public boolean isIPBlocked(String ipAddress) { Instant blockTime = ipBlockTimes.get(ipAddress); if (blockTime == null) { return false; } if (Instant.now().isAfter(blockTime.plus(BLOCK_DURATION))) { ipBlockTimes.remove(ipAddress); failedLoginAttempts.remove(ipAddress); return false; } return true; } private void blockIPAddress(String ipAddress) { ipBlockTimes.put(ipAddress, Instant.now()); securityLogger.warn("IP address blocked due to excessive failed logins: {}", ipAddress); } } // 9.4 Real-time Alerting @Component public class SecurityAlertService { private final List<AlertHandler> alertHandlers; public void sendAlert(SecurityAlert alert) { securityLogger.error("SECURITY ALERT: {}", alert); for (AlertHandler handler : alertHandlers) { try { handler.handleAlert(alert); } catch (Exception e) { securityLogger.error("Failed to send alert via handler: {}", handler.getClass().getSimpleName(), e); } } } public void alertSuspiciousActivity(HttpServletRequest request, String description) { SecurityAlert alert = SecurityAlert.builder() .level(Severity.HIGH) .type("SUSPICIOUS_ACTIVITY") .description(description) .ipAddress(getClientIP(request)) .userAgent(request.getHeader("User-Agent")) .timestamp(Instant.now()) .build(); sendAlert(alert); } } // 9.5 Log Injection Prevention @Component public class LogSanitizer { public String sanitizeLogEntry(String input) { if (input == null) return null; // Remove CRLF characters to prevent log injection return input.replaceAll("[\r\n]", ""); } public Map<String, String> sanitizeLogMap(Map<String, String> input) { return input.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> sanitizeLogEntry(entry.getValue()) )); } } } 10. Server-Side Request Forgery (SSRF)
SSRF Prevention
import java.net.*; import java.util.Set; @Component public class SSRFProtection { // 10.1 URL Validation and Filtering @Component public class URLValidator { private final Set<String> allowedProtocols = Set.of("http", "https"); private final Set<String> blockedIPs = Set.of( "127.0.0.1", "localhost", "0.0.0.0", "::1", "169.254.169.254", // AWS metadata service "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12" // Private networks ); public ValidationResult validateURL(String urlString) { try { URL url = new URL(urlString); // Validate protocol if (!allowedProtocols.contains(url.getProtocol().toLowerCase())) { return ValidationResult.failure("Protocol not allowed: " + url.getProtocol()); } // Resolve and validate host String host = url.getHost(); InetAddress address = InetAddress.getByName(host); // Check for blocked IPs if (isBlockedIP(address)) { return ValidationResult.failure("Access to internal resource blocked"); } // Check for DNS rebinding if (!isConsistentResolution(host, address)) { return ValidationResult.failure("DNS resolution inconsistency detected"); } return ValidationResult.success(); } catch (Exception e) { return ValidationResult.failure("Invalid URL: " + e.getMessage()); } } private boolean isBlockedIP(InetAddress address) { String ip = address.getHostAddress(); // Check exact matches if (blockedIPs.contains(ip)) { return true; } // Check CIDR blocks return blockedIPs.stream() .filter(blocked -> blocked.contains("/")) .anyMatch(cidr -> isInRange(ip, cidr)); } private boolean isInRange(String ip, String cidr) { try { String[] parts = cidr.split("/"); String network = parts[0]; int prefix = Integer.parseInt(parts[1]); // Simplified CIDR check - in production use proper IP math library return ip.startsWith(network.substring(0, network.lastIndexOf('.'))); } catch (Exception e) { return false; } } private boolean isConsistentResolution(String host, InetAddress resolvedAddress) { try { // Double-check DNS resolution InetAddress[] allAddresses = InetAddress.getAllByName(host); return Arrays.stream(allAddresses) .anyMatch(addr -> addr.equals(resolvedAddress)); } catch (Exception e) { return false; } } } // 10.2 Safe HTTP Client @Component public class SafeHttpClient { private final URLValidator urlValidator; private final HttpClient httpClient; public SafeHttpClient(URLValidator urlValidator) { this.urlValidator = urlValidator; this.httpClient = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NEVER) // Don't follow redirects .connectTimeout(Duration.ofSeconds(10)) .build(); } public String makeSafeRequest(String urlString) throws Exception { // Validate URL first ValidationResult validation = urlValidator.validateURL(urlString); if (!validation.isValid()) { throw new SecurityException("URL validation failed: " + validation.getErrorMessage()); } HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(urlString)) .timeout(Duration.ofSeconds(30)) .header("User-Agent", "SafeApp/1.0") // Identify our requests .build(); HttpResponse<String> response = httpClient.send( request, HttpResponse.BodyHandlers.ofString()); // Validate response if (response.statusCode() >= 400) { throw new IOException("HTTP error: " + response.statusCode()); } // Check content type String contentType = response.headers().firstValue("Content-Type").orElse(""); if (!contentType.startsWith("text/") && !contentType.contains("json")) { throw new SecurityException("Unsupported content type: " + contentType); } return response.body(); } } // 10.3 Outbound Request Monitoring @Component public class OutboundRequestMonitor { private static final Logger outboundLogger = LoggerFactory.getLogger("OUTBOUND_REQUESTS"); public void monitorOutboundRequest(String url, String method, String user) { Map<String, String> event = Map.of( "type", "OUTBOUND_REQUEST", "url", url, "method", method, "user", user, "timestamp", Instant.now().toString(), "ip", getOutboundIP() ); outboundLogger.info("Outbound request: {}", event); // Alert on suspicious patterns if (isSuspiciousURL(url)) { securityAlertService.alertSuspiciousOutboundRequest(url, user); } } private boolean isSuspiciousURL(String url) { return url.contains("169.254.169.254") || // AWS metadata url.contains("metadata.google.internal") || // GCP metadata url.contains("localhost") || url.contains("127.0.0.1") || url.contains("::1") || url.contains(".internal"); } private String getOutboundIP() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (Exception e) { return "unknown"; } } } // 10.4 Whitelist-Based URL Access @Component public class URLWhitelistService { private final Set<String> allowedDomains; private final Set<Pattern> allowedPatterns; public URLWhitelistService() { // Load from configuration this.allowedDomains = Set.of( "api.trusted-service.com", "images.safe-cdn.net", "data.legitimate-source.org" ); this.allowedPatterns = Set.of( Pattern.compile("^https://cdn\\d+\\.safedomain\\.com/.*"), Pattern.compile("^https://api\\-\\w+\\.validservice\\.com/v\\d+/.*") ); } public boolean isURLAllowed(String urlString) { try { URL url = new URL(urlString); String host = url.getHost(); // Check exact domain matches if (allowedDomains.contains(host)) { return true; } // Check pattern matches return allowedPatterns.stream() .anyMatch(pattern -> pattern.matcher(urlString).matches()); } catch (Exception e) { return false; } } } } // Utility classes class ValidationResult { private final boolean valid; private final String errorMessage; private ValidationResult(boolean valid, String errorMessage) { this.valid = valid; this.errorMessage = errorMessage; } public static ValidationResult success() { return new ValidationResult(true, null); } public static ValidationResult failure(String errorMessage) { return new ValidationResult(false, errorMessage); } public boolean isValid() { return valid; } public String getErrorMessage() { return errorMessage; } } class SecurityAlert { private final Severity level; private final String type; private final String description; private final String ipAddress; private final String userAgent; private final Instant timestamp; // Builder pattern implementation public static Builder builder() { return new Builder(); } public static class Builder { private Severity level; private String type; private String description; private String ipAddress; private String userAgent; private Instant timestamp; public Builder level(Severity level) { this.level = level; return this; } public Builder type(String type) { this.type = type; return this; } public Builder description(String description) { this.description = description; return this; } public Builder ipAddress(String ipAddress) { this.ipAddress = ipAddress; return this; } public Builder userAgent(String userAgent) { this.userAgent = userAgent; return this; } public Builder timestamp(Instant timestamp) { this.timestamp = timestamp; return this; } public SecurityAlert build() { return new SecurityAlert(this); } } private SecurityAlert(Builder builder) { this.level = builder.level; this.type = builder.type; this.description = builder.description; this.ipAddress = builder.ipAddress; this.userAgent = builder.userAgent; this.timestamp = builder.timestamp; } // Getters public Severity getLevel() { return level; } public String getType() { return type; } public String getDescription() { return description; } public String getIpAddress() { return ipAddress; } public String getUserAgent() { return userAgent; } public Instant getTimestamp() { return timestamp; } } enum Severity { LOW, MEDIUM, HIGH, CRITICAL } Conclusion
Implementing OWASP Top 10 mitigations in Java requires a comprehensive approach covering:
- Defense in Depth: Multiple layers of security controls
- Secure Defaults: Safe configurations out of the box
- Continuous Monitoring: Real-time security event tracking
- Input Validation: Comprehensive data validation at all layers
- Principle of Least Privilege: Minimal required access rights
- Secure Development Lifecycle: Security integrated throughout development
Regular security testing, code reviews, and staying updated with security advisories are essential for maintaining application security.