This is an issue when an object’s initialization code can throw an exception and the class is not protected against attacks through cleanup hooks, which can execute on partially-constructed objects. Protection mechanisms include preventing inheritance, providing empty cleanup implementations that cannot be overridden, or using construction patterns that prevent direct instantiation.
In Java, these cleanup hooks are called finalizers, and the protection mechanisms specifically refer to declaring the class as final,
or using the factory pattern. Implementing a final empty finalize() method was also a known solution, but
finalize() has been deprecated since Java 9 and is marked for removal.
When a constructor throws an exception, you might expect the object construction to fail completely and no reference to the object to exist. However, finalization or cleanup mechanisms can be exploited to obtain a reference to a partially-constructed object.
Here’s how a Finalizer attack works:
This vulnerability is particularly dangerous for security-sensitive classes where the constructor performs:
If an attacker can obtain a reference to a partially-constructed instance, they may be able to call methods on an object in an inconsistent state, potentially bypassing security controls.
Attacks exploiting cleanup mechanisms for incompletely initialized objects can lead to serious security vulnerabilities:
The simplest solution is to declare the class as final. This prevents attackers from creating malicious subclasses that override the
finalize() method.
However, some frameworks such as Spring or JPA/Hibernate require non-final classes. In such cases, use a factory method with a private
constructor to ensure the object is fully validated before any reference is exposed. Since the constructor is private, no malicious subclass
can be created, achieving the same protection as final.
This rule targets classes whose constructors can throw exceptions and that are not protected against finalizer attacks. If the class is internal to your application and never exposed to untrusted code, or if the constructor cannot actually throw, the risk is lower and the finding may be acceptable as a won’t-fix. A genuine false positive would occur when the constructor is guaranteed not to throw, making the attack path impossible.
public class SecuritySensitiveClass {
private final String credentials;
public SecuritySensitiveClass(String credentials) throws AuthenticationException {
if (!isValid(credentials)) {
throw new AuthenticationException("Invalid credentials"); // Noncompliant
}
this.credentials = credentials;
}
private boolean isValid(String credentials) {
return credentials != null && credentials.length() > 10;
}
}
public final class SecuritySensitiveClass { // Compliant: class is final
private final String credentials;
public SecuritySensitiveClass(String credentials) throws AuthenticationException {
if (!isValid(credentials)) {
throw new AuthenticationException("Invalid credentials");
}
this.credentials = credentials;
}
private boolean isValid(String credentials) {
return credentials != null && credentials.length() > 10;
}
}
When the class cannot be final because a framework requires subclassing or proxying, use a factory method with a private
constructor:
public class SecuritySensitiveClass { // Compliant: constructor is private, instances are created through factory method
private final String credentials;
private SecuritySensitiveClass(String credentials) {
this.credentials = credentials;
}
public static SecuritySensitiveClass create(String credentials) throws AuthenticationException {
if (!isValid(credentials)) {
throw new AuthenticationException("Invalid credentials");
}
return new SecuritySensitiveClass(credentials);
}
private static boolean isValid(String credentials) {
return credentials != null && credentials.length() > 10;
}
}