Skip to content

Java Secure Coding Practices: OWASP Top 10 Mitigation Guide

Published: at 12:32 PM

Java Secure Coding Practices: OWASP Top 10 Mitigation Guide

Incorporating secure coding practices is essential to mitigate the OWASP Top 10 vulnerabilities in Java applications. This comprehensive guide provides practical code examples illustrating common vulnerabilities and their secure implementations.

1. Injection Attacks

SQL Injection Prevention

Vulnerable Code:

String userId = request.getParameter("userId");
String query = "SELECT * FROM users WHERE user_id = '" + userId + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);

Secure Code:

String userId = request.getParameter("userId");
String query = "SELECT * FROM users WHERE user_id = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, userId);
ResultSet rs = pstmt.executeQuery();

Explanation: Using PreparedStatement with parameterized queries prevents SQL injection by separating SQL logic from data.

NoSQL Injection Prevention

Vulnerable Code (MongoDB):

// Direct string concatenation
String userQuery = "{ 'username': '" + username + "' }";
BasicDBObject query = BasicDBObject.parse(userQuery);

Secure Code:

// Using proper query construction
BasicDBObject query = new BasicDBObject();
query.put("username", username);

LDAP Injection Prevention

Vulnerable Code:

String filter = "(&(uid=" + username + ")(userPassword=" + password + "))";
NamingEnumeration results = ctx.search("ou=users,dc=example,dc=com", filter, controls);

Secure Code:

// Escape special characters
private static String escapeLDAPSearchFilter(String filter) {
    StringBuilder sb = new StringBuilder();
    for (char c : filter.toCharArray()) {
        switch (c) {
            case '\\': sb.append("\\5c"); break;
            case '*':  sb.append("\\2a"); break;
            case '(':  sb.append("\\28"); break;
            case ')':  sb.append("\\29"); break;
            case '\0': sb.append("\\00"); break;
            default:   sb.append(c);
        }
    }
    return sb.toString();
}

String escapedUsername = escapeLDAPSearchFilter(username);
String filter = "(&(uid=" + escapedUsername + ")(userPassword=" + escapeLDAPSearchFilter(password) + "))";

2. Broken Authentication

Secure Password Storage

Vulnerable Code:

// Password stored in plain text
String storedPassword = getUserPassword(username);
if (inputPassword.equals(storedPassword)) {
    // Authenticate user
}

Secure Code:

import org.mindrot.jbcrypt.BCrypt;

// Password stored as a hashed value
public class SecurePasswordUtil {
    public static String hashPassword(String password) {
        return BCrypt.hashpw(password, BCrypt.gensalt(12));
    }

    public static boolean verifyPassword(String password, String hashedPassword) {
        return BCrypt.checkpw(password, hashedPassword);
    }
}

// Usage
String storedHashedPassword = getUserPassword(username);
if (SecurePasswordUtil.verifyPassword(inputPassword, storedHashedPassword)) {
    // Authenticate user
}

Session Management

Vulnerable Code:

// Weak session ID generation
String sessionId = String.valueOf(System.currentTimeMillis());

Secure Code:

import java.security.SecureRandom;
import java.util.Base64;

public class SecureSessionManager {
    private static final SecureRandom secureRandom = new SecureRandom();

    public static String generateSessionId() {
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
    }

    public static void configureSecureSession(HttpServletResponse response) {
        // Set secure cookie attributes
        Cookie sessionCookie = new Cookie("JSESSIONID", generateSessionId());
        sessionCookie.setHttpOnly(true);  // Prevent XSS
        sessionCookie.setSecure(true);    // HTTPS only
        sessionCookie.setMaxAge(1800);    // 30 minutes
        sessionCookie.setPath("/");
        sessionCookie.setSameSite(Cookie.SameSite.STRICT);
        response.addCookie(sessionCookie);
    }
}

3. Sensitive Data Exposure

Data Encryption

Vulnerable Code:

// Transmitting data over an unencrypted connection
URL url = new URL("http://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

Secure Code:

// Transmitting data over an encrypted connection
URL url = new URL("https://example.com/api");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

// Additional security: Certificate pinning
conn.setSSLSocketFactory(createPinnedSSLSocketFactory());

Data Masking and Encryption

Secure Implementation:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class DataProtectionUtil {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_IV_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 16;

    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256);
        return keyGenerator.generateKey();
    }

    public static String encrypt(String plainText, SecretKey key) throws Exception {
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom.getInstanceStrong().nextBytes(iv);

        Cipher cipher = Cipher.getInstance(ALGORITHM);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);

        byte[] encryptedData = cipher.doFinal(plainText.getBytes());
        byte[] encryptedWithIv = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, encryptedWithIv, 0, iv.length);
        System.arraycopy(encryptedData, 0, encryptedWithIv, iv.length, encryptedData.length);

        return Base64.getEncoder().encodeToString(encryptedWithIv);
    }

    public static String decrypt(String encryptedText, SecretKey key) throws Exception {
        byte[] decodedData = Base64.getDecoder().decode(encryptedText);

        byte[] iv = new byte[GCM_IV_LENGTH];
        System.arraycopy(decodedData, 0, iv, 0, iv.length);

        byte[] encrypted = new byte[decodedData.length - GCM_IV_LENGTH];
        System.arraycopy(decodedData, GCM_IV_LENGTH, encrypted, 0, encrypted.length);

        Cipher cipher = Cipher.getInstance(ALGORITHM);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);

        byte[] decryptedData = cipher.doFinal(encrypted);
        return new String(decryptedData);
    }
}

4. XML External Entities (XXE)

Vulnerable Code:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("input.xml"));

Secure Code:

import javax.xml.XMLConstants;

public class SecureXMLParser {
    public static Document parseXML(File xmlFile) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        // Disable external entity processing
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        // Secure processing
        dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        dbf.setXIncludeAware(false);
        dbf.setExpandEntityReferences(false);

        DocumentBuilder db = dbf.newDocumentBuilder();
        return db.parse(xmlFile);
    }
}

5. Security Misconfiguration

Error Handling

Vulnerable Code:

// Default error page revealing stack trace
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());

Secure Code:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SecureErrorHandler {
    private static final Logger logger = LoggerFactory.getLogger(SecureErrorHandler.class);

    public static void handleError(HttpServletResponse response, Exception e, String userMessage) {
        // Log detailed error for developers
        logger.error("Application error occurred", e);

        // Return generic message to user
        try {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                userMessage != null ? userMessage : "An unexpected error occurred.");
        } catch (IOException ioException) {
            logger.error("Failed to send error response", ioException);
        }
    }
}

Security Headers

Secure Implementation:

@WebFilter("/*")
public class SecurityHeadersFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // Prevent clickjacking
        httpResponse.setHeader("X-Frame-Options", "DENY");

        // Prevent MIME type sniffing
        httpResponse.setHeader("X-Content-Type-Options", "nosniff");

        // Enable XSS protection
        httpResponse.setHeader("X-XSS-Protection", "1; mode=block");

        // Enforce HTTPS
        httpResponse.setHeader("Strict-Transport-Security",
            "max-age=31536000; includeSubDomains; preload");

        // Content Security Policy
        httpResponse.setHeader("Content-Security-Policy",
            "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");

        // Hide server information
        httpResponse.setHeader("Server", "");

        chain.doFilter(request, response);
    }
}

6. Cross-Site Scripting (XSS)

Vulnerable Code:

out.println("<div>" + userInput + "</div>");

Secure Code:

import org.apache.commons.text.StringEscapeUtils;
import org.owasp.encoder.Encode;

public class XSSPrevention {

    // Using Apache Commons Text
    public static String escapeHtml(String input) {
        return StringEscapeUtils.escapeHtml4(input);
    }

    // Using OWASP Java Encoder
    public static String encodeForHTML(String input) {
        return Encode.forHtml(input);
    }

    public static String encodeForJavaScript(String input) {
        return Encode.forJavaScript(input);
    }

    public static String encodeForCSS(String input) {
        return Encode.forCssString(input);
    }

    public static String encodeForURL(String input) {
        return Encode.forUriComponent(input);
    }
}

// Usage
out.println("<div>" + XSSPrevention.encodeForHTML(userInput) + "</div>");

Content Security Policy Implementation

@WebServlet("/secure-page")
public class SecurePageServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // Set CSP header
        response.setHeader("Content-Security-Policy",
            "default-src 'self'; " +
            "script-src 'self' 'unsafe-inline' https://cdn.example.com; " +
            "style-src 'self' 'unsafe-inline'; " +
            "img-src 'self' data: https:; " +
            "font-src 'self' https://fonts.googleapis.com; " +
            "connect-src 'self' https://api.example.com");

        // Process request...
    }
}

7. Insecure Deserialization

Vulnerable Code:

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"));
Object obj = ois.readObject();

Secure Code:

import java.io.*;
import java.util.Set;
import java.util.HashSet;

public class SecureDeserialization {
    private static final Set<String> ALLOWED_CLASSES = new HashSet<>();

    static {
        ALLOWED_CLASSES.add("com.example.SafeClass");
        ALLOWED_CLASSES.add("com.example.AnotherSafeClass");
        // Add other safe classes
    }

    public static Object deserializeSecurely(InputStream input) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(input) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass desc)
                    throws IOException, ClassNotFoundException {
                String className = desc.getName();

                if (!ALLOWED_CLASSES.contains(className)) {
                    throw new InvalidClassException("Unauthorized deserialization attempt", className);
                }

                return super.resolveClass(desc);
            }
        };

        return ois.readObject();
    }

    // Alternative: Using JSON instead of Java serialization
    public static <T> T deserializeFromJSON(String json, Class<T> clazz) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
        try {
            return mapper.readValue(json, clazz);
        } catch (Exception e) {
            throw new RuntimeException("Deserialization failed", e);
        }
    }
}

8. Using Components with Known Vulnerabilities

Dependency Management

Maven Security Plugin Configuration:

<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>8.4.0</version>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
        <suppressionFiles>
            <suppressionFile>suppression.xml</suppressionFile>
        </suppressionFiles>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Vulnerable Dependency Example:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.0.RELEASE</version> <!-- Outdated version -->
</dependency>

Secure Dependency:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>6.0.11</version> <!-- Updated version -->
</dependency>

9. Insufficient Logging and Monitoring

Vulnerable Code:

// No logging of authentication attempts
public void authenticate(String username, String password) {
    // Authentication logic
}

Secure Code:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class SecureAuthenticationService {
    private static final Logger logger = LoggerFactory.getLogger(SecureAuthenticationService.class);
    private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");

    public boolean authenticate(String username, String password) {
        MDC.put("username", username);
        MDC.put("remoteIP", getCurrentUserIP());

        try {
            logger.info("Authentication attempt for user: {}", username);

            // Authentication logic
            boolean isAuthenticated = performAuthentication(username, password);

            if (isAuthenticated) {
                securityLogger.info("Successful authentication for user: {}", username);
            } else {
                securityLogger.warn("Failed authentication attempt for user: {}", username);
                // Implement rate limiting/account lockout here
            }

            return isAuthenticated;

        } catch (Exception e) {
            securityLogger.error("Authentication error for user: {}", username, e);
            return false;
        } finally {
            MDC.clear();
        }
    }

    private String getCurrentUserIP() {
        // Implementation to get current user's IP
        return "192.168.1.1"; // placeholder
    }

    private boolean performAuthentication(String username, String password) {
        // Actual authentication logic
        return true; // placeholder
    }
}

Security Event Monitoring

@Component
public class SecurityEventLogger {
    private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");

    public void logSecurityEvent(String eventType, String details, String severity) {
        Map<String, Object> event = new HashMap<>();
        event.put("timestamp", Instant.now());
        event.put("eventType", eventType);
        event.put("details", details);
        event.put("severity", severity);
        event.put("source", "application");

        // Log as structured data
        securityLogger.info("Security Event: {}", new ObjectMapper().writeValueAsString(event));
    }

    @EventListener
    public void handleAuthenticationFailure(AuthenticationFailureEvent event) {
        logSecurityEvent("AUTHENTICATION_FAILURE",
            "Failed login attempt for user: " + event.getUsername(),
            "HIGH");
    }

    @EventListener
    public void handleUnauthorizedAccess(UnauthorizedAccessEvent event) {
        logSecurityEvent("UNAUTHORIZED_ACCESS",
            "Unauthorized access attempt to: " + event.getResource(),
            "CRITICAL");
    }
}

10. Unvalidated Redirects and Forwards

Vulnerable Code:

String url = request.getParameter("url");
response.sendRedirect(url);

Secure Code:

import java.net.URL;
import java.util.Set;

public class SecureRedirectHandler {
    private static final Set<String> ALLOWED_DOMAINS = Set.of(
        "example.com",
        "subdomain.example.com",
        "api.example.com"
    );

    public static boolean isValidRedirectUrl(String url) {
        try {
            URL redirectUrl = new URL(url);
            String host = redirectUrl.getHost().toLowerCase();

            // Check if it's a relative URL (safe)
            if (url.startsWith("/") && !url.startsWith("//")) {
                return true;
            }

            // Check if host is in allowed domains
            return ALLOWED_DOMAINS.contains(host) ||
                   ALLOWED_DOMAINS.stream().anyMatch(domain -> host.endsWith("." + domain));

        } catch (Exception e) {
            return false;
        }
    }

    public static void safeRedirect(HttpServletResponse response, String url) throws IOException {
        if (isValidRedirectUrl(url)) {
            response.sendRedirect(url);
        } else {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid redirect URL");
        }
    }
}

// Usage in servlet
String url = request.getParameter("url");
SecureRedirectHandler.safeRedirect(response, url);

Additional Security Best Practices

Input Validation Framework

import javax.validation.constraints.*;
import org.hibernate.validator.constraints.SafeHtml;

public class UserInput {

    @NotNull
    @Size(min = 3, max = 50)
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username must contain only alphanumeric characters and underscores")
    private String username;

    @NotNull
    @Email
    private String email;

    @NotNull
    @Size(min = 8, max = 100)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]",
             message = "Password must contain uppercase, lowercase, digit and special character")
    private String password;

    @SafeHtml
    @Size(max = 1000)
    private String description;

    // Getters and setters...
}

Secure Random Number Generation

import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class SecureRandomGenerator {
    private static final SecureRandom secureRandom;

    static {
        try {
            secureRandom = SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Strong SecureRandom not available", e);
        }
    }

    public static String generateSecureToken(int length) {
        byte[] randomBytes = new byte[length];
        secureRandom.nextBytes(randomBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
    }

    public static int generateSecureInt(int bound) {
        return secureRandom.nextInt(bound);
    }
}

Security Testing

Security Unit Test Example

@Test
public void testSQLInjectionPrevention() {
    String maliciousInput = "'; DROP TABLE users; --";

    // This should not cause SQL injection
    assertDoesNotThrow(() -> {
        userService.findUserById(maliciousInput);
    });

    // Verify that no actual SQL injection occurred
    assertTrue(userService.getAllUsers().size() > 0);
}

@Test
public void testXSSPrevention() {
    String xssPayload = "<script>alert('XSS')</script>";
    String encoded = XSSPrevention.encodeForHTML(xssPayload);

    assertFalse(encoded.contains("<script>"));
    assertTrue(encoded.contains("&lt;script&gt;"));
}

@Test
public void testPasswordHashing() {
    String password = "TestPassword123!";
    String hashedPassword = SecurePasswordUtil.hashPassword(password);

    assertNotEquals(password, hashedPassword);
    assertTrue(SecurePasswordUtil.verifyPassword(password, hashedPassword));
    assertFalse(SecurePasswordUtil.verifyPassword("WrongPassword", hashedPassword));
}

Conclusion

Implementing these secure coding practices helps protect Java applications against the OWASP Top 10 vulnerabilities. Key takeaways include:

  1. Always validate and sanitize input before processing
  2. Use parameterized queries to prevent injection attacks
  3. Implement proper authentication and session management
  4. Encrypt sensitive data both in transit and at rest
  5. Configure security headers and error handling properly
  6. Keep dependencies updated and monitor for vulnerabilities
  7. Implement comprehensive logging for security events
  8. Validate all redirects and forwards against allowed destinations

Regular security testing, code reviews, and staying updated with the latest security practices are essential for maintaining secure Java applications.