Article
In Java application security, the focus often falls on code-level vulnerabilities while configuration weaknesses remain a prevalent attack vector. Configuration Hardening is the process of securing an application's settings, dependencies, and runtime environment to minimize the attack surface. For Java applications, this involves systematically addressing security gaps in frameworks, servers, dependencies, and JVM settings to create a robust, defense-in-depth security posture.
What is Java Configuration Hardening?
Configuration hardening involves securing every configurable aspect of your Java application stack:
- JVM Security Settings: Memory, execution, and security manager configurations
- Framework Security: Spring Security, Jakarta EE, and microframework settings
- Server Configuration: Tomcat, Jetty, Undertow, and application server security
- Dependency Management: Secure dependency versions and vulnerability management
- Runtime Environment: Container, OS, and cloud platform security settings
JVM Security Hardening
1. Secure JVM Launch Parameters
Configure JVM for security-first operation:
# Secure JVM launch configuration java \ # Memory and execution settings -Xmx1024m -Xms512m \ -XX:MaxMetaspaceSize=256m \ -XX:ReservedCodeCacheSize=128m \ # Security manager and policy (if needed) -Djava.security.manager \ -Djava.security.policy==/app/conf/security.policy \ # Cryptographic strength configuration -Djava.security.properties=/app/conf/java.security \ -Djdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA \ -Djdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer \ # RMI and serialization security -Dsun.rmi.dgc.server.gcInterval=3600000 \ -Dsun.rmi.dgc.client.gcInterval=3600000 \ -Djava.rmi.server.hostname=localhost \ # Logging and debugging security -Djava.awt.headless=true \ -Dlog4j2.formatMsgNoLookups=true \ # Application-specific settings -Dfile.encoding=UTF-8 \ -Duser.timezone=UTC \ -jar application.jar
2. Java Security Policy File
Create a restrictive security policy:
// security.policy grant { // Basic permissions for all code permission java.util.PropertyPermission "user.timezone", "read"; permission java.util.PropertyPermission "file.encoding", "read"; permission java.util.PropertyPermission "java.io.tmpdir", "read"; // Application-specific permissions permission java.io.FilePermission "/app/logs/-", "read,write"; permission java.net.SocketPermission "database.company.com:5432", "connect"; permission java.net.SocketPermission "redis.company.com:6379", "connect"; // Restrict dangerous permissions permission java.lang.RuntimePermission "setFactory", ""; permission java.lang.RuntimePermission "setIO", ""; permission java.lang.Reflect.ReflectPermission "suppressAccessChecks", ""; // Deny critical permissions permission java.io.FilePermission "<<ALL FILES>>", "read,write,execute,delete", deny; permission java.net.SocketPermission "*:0-1023", "connect,accept,listen", deny; permission java.lang.RuntimePermission "exitVM", "", deny; }; Spring Framework Hardening
1. Spring Security Configuration
Implement comprehensive security controls:
@Configuration @EnableWebSecurity public class SecurityHardeningConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http // Disable dangerous features .csrf(csrf -> csrf .ignoringRequestMatchers("/api/public/**") ) .headers(headers -> headers .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'")) .httpStrictTransportSecurity(hsts -> hsts .includeSubDomains(true) .maxAgeInSeconds(31536000)) .frameOptions(frame -> frame .deny()) .xssProtection(xss -> xss .headerValue(XXssConfig.HeaderValue.ENABLED_MODE_BLOCK)) ) // Session management .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation(sf -> sf .migrateSession()) .maximumSessions(1) ) // Authorization .authorizeHttpRequests(authz -> authz .requestMatchers("/actuator/**").hasRole("ADMIN") .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/user/**").hasRole("USER") .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) // Authentication .formLogin(form -> form .loginPage("/login") .failureHandler(authenticationFailureHandler()) ) .logout(logout -> logout .deleteCookies("JSESSIONID") .invalidateHttpSession(true) ) .build(); } @Bean public AuthenticationFailureHandler authenticationFailureHandler() { return new CustomAuthenticationFailureHandler(); } } 2. Spring Boot Application Properties Hardening
Secure application configuration:
# application-security.properties # Server security server.servlet.session.timeout=1800 server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=true server.servlet.session.cookie.same-site=strict server.port=8443 server.ssl.enabled=true server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=${KEYSTORE_PASSWORD} server.ssl.key-store-type=PKCS12 server.ssl.key-alias=tomcat # Spring security spring.security.oauth2.client.registration.provider.require-https=true spring.security.filter.order=0 # Database security spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.validation-timeout=5000 spring.datasource.hikari.leak-detection-threshold=60000 # Actuator security management.endpoints.web.exposure.include=health,info,metrics management.endpoint.health.show-details=when_authorized management.endpoints.web.base-path=/internal/actuator management.server.port=9090 # Logging security logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n logging.level.com.company.sensitive=OFF # Jackson security spring.jackson.default-property-inclusion=non_null spring.jackson.deserialization.fail-on-unknown-properties=true # File upload security spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB Tomcat Server Hardening
1. Embedded Tomcat Configuration
Harden embedded Tomcat in Spring Boot:
@Configuration public class TomcatHardeningConfig { @Bean public TomcatServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); tomcat.addConnectorCustomizers(connector -> { // HTTP to HTTPS redirect connector.setRedirectPort(8443); connector.setScheme("https"); connector.setSecure(true); // Connection hardening connector.setAttribute("maxPostSize", 10485760); // 10MB connector.setAttribute("maxSavePostSize", 4096); connector.setAttribute("allowTrace", false); }); // Add security valve tomcat.addContextValves(securityValve()); // Error page configuration tomcat.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404")); tomcat.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500")); return tomcat; } private Valve securityValve() { return new RemoteAddrValve() {{ setAllow("127.0.0.1,::1,10.0.0.0/8"); setDenyStatus(404); }}; } } 2. server.xml Hardening (External Tomcat)
<!-- server.xml --> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.security.SecurityListener" /> <Service name="Catalina"> <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" minSpareThreads="25" maxHttpHeaderSize="8192" enableLookups="false" disableUploadTimeout="true" acceptCount="100" maxPostSize="10485760" maxSavePostSize="4096" allowTrace="false" server="Unknown" xpoweredBy="false"> <SSLHostConfig certificateVerification="false" protocols="TLSv1.2,TLSv1.3" ciphers="TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,..."> </SSLHostConfig> </Connector> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="false"> <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.0\.0\.1,::1,10\.\d+\.\d+\.\d+" /> <Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" /> </Host> </Engine> </Service> </Server>
Dependency Security Hardening
1. Maven Security Configuration
Secure dependency management:
<!-- pom.xml --> <project> <properties> <!-- Enforce secure dependency versions --> <log4j2.version>2.17.1</log4j2.version> <jackson.version>2.13.4.2</jackson.version> <spring-boot.version>2.7.8</spring-boot.version> <!-- Security plugin versions --> <owasp-dependency-check.version>7.4.4</owasp-dependency-check.version> <spotbugs.version>4.7.3</spotbugs.version> </properties> <dependencyManagement> <dependencies> <!-- BOM imports for vulnerability management --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <!-- OWASP Dependency Check --> <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>${owasp-dependency-check.version}</version> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> <configuration> <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability> <suppressionFile>dependency-check-suppressions.xml</suppressionFile> </configuration> </plugin> <!-- SpotBugs Security --> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <version>${spotbugs.version}</version> <configuration> <effort>Max</effort> <threshold>Low</threshold> <includeFilterFile>spotbugs-security-include.xml</includeFilterFile> </configuration> </plugin> </plugins> </build> </project> 2. Dependency Exclusion Policy
<!-- Ban vulnerable dependencies --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>ban-vulnerable-dependencies</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <bannedDependencies> <excludes> <exclude>log4j:log4j:(,1.2.17]</exclude> <exclude>commons-collections:commons-collections:(,3.2.2]</exclude> <exclude>com.fasterxml.jackson.core:jackson-databind:[2.0.0,2.12.6.1)</exclude> </excludes> </bannedDependencies> <requireJavaVersion> <version>[17,18)</version> </requireJavaVersion> </rules> </configuration> </execution> </executions> </plugin>
Logging Security Hardening
1. Secure Logback Configuration
<!-- logback-spring.xml --> <configuration> <!-- Disable sensitive data logging --> <conversionRule conversionWord="maskedMsg" converterClass="com.company.security.MaskingPatternLayout" /> <appender name="SECURE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> <maxHistory>30</maxHistory> <totalSizeCap>3GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %maskedMsg%n</pattern> </encoder> </appender> <!-- Prevent Log4Shell-type vulnerabilities --> <logger name="org.apache.logging.log4j" level="WARN"/> <!-- Sensitive package logging --> <logger name="com.company.security" level="WARN"/> <logger name="com.company.user" level="INFO"/> <root level="INFO"> <appender-ref ref="SECURE_FILE" /> </root> </configuration> 2. Log Masking Implementation
@Component public class MaskingPatternLayout extends MessageConverter { private static final List<Pattern> SENSITIVE_PATTERNS = Arrays.asList( Pattern.compile("(\"password\"\\s*:\\s*\")([^\"]+)(\")", Pattern.CASE_INSENSITIVE), Pattern.compile("(password=)([^&]+)", Pattern.CASE_INSENSITIVE), Pattern.compile("(\\b\\d{16}\\b)"), // Credit card numbers Pattern.compile("(\\b[\\w.%-]+@[\\w.-]+\\.[A-Z]{2,4}\\b)", Pattern.CASE_INSENSITIVE) // Email ); @Override public String convert(ILoggingEvent event) { String message = event.getFormattedMessage(); return maskSensitiveData(message); } private String maskSensitiveData(String message) { String masked = message; for (Pattern pattern : SENSITIVE_PATTERNS) { masked = pattern.matcher(masked).replaceAll("$1***$3"); } return masked; } } Container Security Hardening
1. Dockerfile Security
# Use minimal base image FROM eclipse-temurin:17-jre-alpine # Add security scanning USER root RUN apk add --no-cache clamav && freshclam # Create non-root user RUN addgroup -S appgroup && adduser -S appuser -G appgroup # Security configuration COPY security.policy /app/conf/security.policy COPY java.security /app/conf/java.security # Copy application COPY target/application.jar /app/application.jar COPY entrypoint.sh /app/entrypoint.sh # Set secure permissions RUN chown -R appuser:appgroup /app && \ chmod -R 750 /app && \ chmod 400 /app/conf/*.policy && \ chmod 500 /app/entrypoint.sh # Security labels LABEL security.scan.enabled="true" \ security.policy.version="1.0" \ maintainer="[email protected]" # Switch to non-root user USER appuser # Secure entrypoint ENTRYPOINT ["/app/entrypoint.sh"] # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1
2. Secure Entrypoint Script
#!/bin/sh # entrypoint.sh set -e # Validate environment variables if [ -z "$KEYSTORE_PASSWORD" ]; then echo "KEYSTORE_PASSWORD environment variable is required" exit 1 fi # Set secure umask umask 0027 # Run application with security settings exec java \ -Djava.security.manager \ -Djava.security.policy==/app/conf/security.policy \ -Djava.security.properties==/app/conf/java.security \ -Djdk.tls.disabledAlgorithms="SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA" \ -Dlog4j2.formatMsgNoLookups=true \ -jar /app/application.jar
Runtime Security Monitoring
1. Security Event Monitoring
@Component public class SecurityEventMonitor { private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY"); @EventListener public void handleAuthenticationEvent(AbstractAuthenticationEvent event) { if (event instanceof AuthenticationSuccessEvent) { securityLogger.info("Authentication success: {}", event.getAuthentication().getName()); } else if (event instanceof AuthenticationFailureBadCredentialsEvent) { securityLogger.warn("Authentication failure: {}", ((AuthenticationFailureBadCredentialsEvent) event).getException().getMessage()); } } @EventListener public void handleAuthorizationEvent(AuthorizationEvent event) { if (!event.isGranted()) { securityLogger.warn("Authorization denied: {} for {}", event.getAuthentication().getName(), event.getSource()); } } } 2. Configuration Validation
@Component public class SecurityConfigurationValidator { @EventListener public void validateConfiguration(ApplicationReadyEvent event) { checkTlsConfiguration(); checkPasswordStrength(); checkSessionSecurity(); } private void checkTlsConfiguration() { String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms"); if (!disabledAlgorithms.contains("TLSv1") || !disabledAlgorithms.contains("TLSv1.1")) { throw new SecurityConfigurationException("Insecure TLS protocols are enabled"); } } private void checkPasswordStrength() { String password = environment.getProperty("spring.datasource.password"); if (password != null && password.length() < 12) { throw new SecurityConfigurationException("Database password is too weak"); } } } Best Practices for Java Configuration Hardening
- Principle of Least Privilege: Run applications with minimal required permissions
- Defense in Depth: Implement multiple layers of security controls
- Secure Defaults: Configure security settings explicitly rather than relying on defaults
- Continuous Validation: Regularly audit and validate security configurations
- Secret Management: Never store secrets in configuration files; use secure secret management
- Automated Security Testing: Integrate security checks into CI/CD pipelines
- Regular Updates: Keep dependencies and frameworks updated with security patches
Security Configuration Checklist
- [ ] JVM security manager enabled
- [ ] TLS 1.0/1.1 disabled
- [ ] Secure cryptographic algorithms configured
- [ ] Non-root user for application execution
- [ ] Session timeout configured
- [ ] CSRF protection enabled
- [ ] Security headers configured
- [ ] Dependency vulnerability scanning implemented
- [ ] Secure logging configured
- [ ] Error messages sanitized
- [ ] File upload restrictions configured
Conclusion
Java configuration hardening is a critical aspect of application security that addresses vulnerabilities beyond code-level issues. By systematically securing JVM settings, framework configurations, server setups, and runtime environments, organizations can significantly reduce their attack surface and build resilient applications.
The comprehensive approach to configuration hardening—covering everything from JVM security policies to container runtime settings—ensures that Java applications are protected against common configuration-based attacks. In today's threat landscape, where attackers increasingly target configuration weaknesses, implementing robust hardening practices is not just a security best practice but a business imperative for any organization running Java applications in production.
Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection
Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.
Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.
Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.
Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.
Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.
Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.
Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.