OWASP Top 10 Mitigation in Java

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:

  1. Strong access control with proper authentication and authorization
  2. Cryptographic security with modern algorithms and proper key management
  3. Input validation and parameterized queries to prevent injections
  4. Secure design principles with business logic validation
  5. Hardened configurations with security headers and CORS policies
  6. Dependency management with regular security updates
  7. Multi-factor authentication and secure session management
  8. Data integrity verification and secure file handling
  9. Comprehensive logging and real-time monitoring
  10. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper