Public cloneable() Method Without Final ('Object Hijack')

Description

Public cloneable() Method Without Final is a vulnerability where a class implements a cloneable() method that is not declared as final. This design flaw allows subclasses to override the clone method, potentially enabling object instantiation without invoking the proper constructor. Attackers can leverage this to create objects that bypass initialization logic, validation, or security checks that would normally occur in the constructor, leaving objects in unexpected or compromised states.

Risk

Non-final clone methods enable "object hijacking" attacks where attackers create malicious subclasses that override the clone method to return objects in invalid states or with manipulated internal data. This bypasses constructor-based security checks, input validation, and proper initialization sequences. In security-critical applications, this can allow creation of privileged objects without proper authorization, manipulation of authentication tokens, or circumvention of access controls. The risk is particularly severe for classes representing security credentials, financial transactions, or authorization contexts.

Solution

Declare the clone() method as final to prevent subclasses from overriding it. Alternatively, make the class itself final if appropriate. When implementing clone(), ensure it properly initializes all security-sensitive fields and maintains object invariants. Consider using copy constructors or factory methods as safer alternatives to cloning. If cloning must be supported, implement defensive copying and validate the cloned object's state. Use static analysis tools to identify classes with non-final clone methods.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Alter Execution Logic - Objects may exist in unexpected states that violate application assumptions about object initialization and consistency.
Access ControlScope: Access Control

Bypass Protection Mechanism - Attackers can bypass constructor-based security checks by instantiating objects through cloning.

Example Code

Vulnerable Code

// Vulnerable: Cloneable class without final clone method
public class VulnerableBankAccount implements Cloneable {
    private String accountNumber;
    private double balance;
    private boolean verified;

    public VulnerableBankAccount(String accountNumber) {
        // Constructor performs security validation
        if (!validateAccountNumber(accountNumber)) {
            throw new SecurityException("Invalid account number");
        }
        this.accountNumber = accountNumber;
        this.balance = 0.0;
        this.verified = false;
    }

    // Vulnerable: Not declared as final
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    private boolean validateAccountNumber(String number) {
        // Validation logic
        return number.matches("\\d{10}");
    }
}

// Attacker can create malicious subclass
public class MaliciousBankAccount extends VulnerableBankAccount {

    public MaliciousBankAccount(String accountNumber) {
        super(accountNumber);
    }

    // Override clone to bypass validation
    @Override
    public Object clone() throws CloneNotSupportedException {
        MaliciousBankAccount account = new MaliciousBankAccount("0000000000");
        // Attacker can manipulate internal state
        // using reflection or other techniques
        return account;
    }
}

// Vulnerable: Authentication token without final clone
public class VulnerableAuthToken implements Cloneable {
    private String userId;
    private String token;
    private Set<String> permissions;
    private Date expiration;

    public VulnerableAuthToken(String userId) {
        // Constructor verifies user exists
        this.userId = verifyAndGetUser(userId);
        this.token = generateSecureToken();
        this.permissions = loadUserPermissions(userId);
        this.expiration = calculateExpiration();
    }

    // Vulnerable: Non-final clone method
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    private String verifyAndGetUser(String userId) {
        // Verify user exists in database
        return userId;
    }

    private String generateSecureToken() {
        // Generate cryptographically secure token
        return UUID.randomUUID().toString();
    }

    private Set<String> loadUserPermissions(String userId) {
        // Load from database
        return new HashSet<>();
    }
}
// Attacker exploits non-final clone
public class AuthTokenExploit extends VulnerableAuthToken {

    public AuthTokenExploit(String userId) {
        super(userId);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // Bypass authentication entirely
        // Return token with elevated privileges
        AuthTokenExploit fakeToken = (AuthTokenExploit) super.clone();

        // Use reflection to modify private fields
        Field permField = AuthTokenExploit.class
            .getSuperclass()
            .getDeclaredField("permissions");
        permField.setAccessible(true);
        Set<String> adminPerms = new HashSet<>();
        adminPerms.add("ADMIN");
        adminPerms.add("READ_ALL");
        adminPerms.add("WRITE_ALL");
        permField.set(fakeToken, adminPerms);

        return fakeToken;
    }
}

Fixed Code

// Fixed: Final clone method prevents override
public class SecureBankAccount implements Cloneable {
    private final String accountNumber;
    private double balance;
    private boolean verified;

    public SecureBankAccount(String accountNumber) {
        if (!validateAccountNumber(accountNumber)) {
            throw new SecurityException("Invalid account number");
        }
        this.accountNumber = accountNumber;
        this.balance = 0.0;
        this.verified = false;
    }

    // Fixed: Declared as final - cannot be overridden
    @Override
    public final Object clone() throws CloneNotSupportedException {
        SecureBankAccount cloned = (SecureBankAccount) super.clone();
        // Ensure cloned object is in valid state
        cloned.verified = false;  // Require re-verification
        return cloned;
    }

    private boolean validateAccountNumber(String number) {
        return number.matches("\\d{10}");
    }
}

// Fixed: Final class prevents subclassing entirely
public final class SecureAuthToken implements Cloneable {
    private final String userId;
    private final String token;
    private final Set<String> permissions;
    private final Date expiration;

    public SecureAuthToken(String userId) {
        this.userId = verifyAndGetUser(userId);
        this.token = generateSecureToken();
        this.permissions = Collections.unmodifiableSet(
            loadUserPermissions(userId)
        );
        this.expiration = calculateExpiration();
    }

    // Fixed: Final class + final method = no override possible
    @Override
    public final Object clone() throws CloneNotSupportedException {
        SecureAuthToken cloned = (SecureAuthToken) super.clone();
        // Clone is safe because all fields are final/immutable
        return cloned;
    }

    private String verifyAndGetUser(String userId) {
        User user = userRepository.findById(userId);
        if (user == null) {
            throw new SecurityException("User not found");
        }
        return userId;
    }

    private String generateSecureToken() {
        return TokenGenerator.generateSecure();
    }

    private Set<String> loadUserPermissions(String userId) {
        return permissionService.getPermissions(userId);
    }

    private Date calculateExpiration() {
        return Date.from(Instant.now().plusSeconds(3600));
    }
}

// Fixed: Use copy constructor instead of clone
public final class SecureCredential {
    private final String username;
    private final byte[] hashedPassword;
    private final Set<String> roles;

    public SecureCredential(String username, byte[] hashedPassword,
                           Set<String> roles) {
        this.username = Objects.requireNonNull(username);
        this.hashedPassword = hashedPassword.clone();  // Defensive copy
        this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
    }

    // Fixed: Copy constructor instead of clone
    public SecureCredential(SecureCredential original) {
        this.username = original.username;
        this.hashedPassword = original.hashedPassword.clone();
        this.roles = original.roles;  // Already unmodifiable
    }

    // Fixed: Factory method for creating copies
    public static SecureCredential copyOf(SecureCredential original) {
        return new SecureCredential(original);
    }

    // No clone() method - use copy constructor instead
}

// Fixed: If clone is needed, use defensive approach
public class DefensiveCloneExample implements Cloneable {
    private String[] sensitiveData;
    private Map<String, Object> config;

    public DefensiveCloneExample() {
        this.sensitiveData = new String[10];
        this.config = new HashMap<>();
    }

    @Override
    public final Object clone() throws CloneNotSupportedException {
        DefensiveCloneExample cloned =
            (DefensiveCloneExample) super.clone();

        // Fixed: Deep copy mutable fields
        cloned.sensitiveData = this.sensitiveData.clone();
        cloned.config = new HashMap<>(this.config);

        // Fixed: Validate cloned state
        validateState(cloned);

        return cloned;
    }

    private void validateState(DefensiveCloneExample obj) {
        if (obj.sensitiveData == null || obj.config == null) {
            throw new IllegalStateException("Invalid clone state");
        }
    }
}

CVE Examples

No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:

  • CERT Oracle Secure Coding Standard for Java (OBJ07-J)
  • Seven Pernicious Kingdoms taxonomy

References

  1. MITRE Corporation. "CWE-491: Public cloneable() Method Without Final ('Object Hijack')." https://cwe.mitre.org/data/definitions/491.html
  2. CERT Oracle Secure Coding Standard for Java. "OBJ07-J. Sensitive classes must not let themselves be copied."
  3. Oracle. "Secure Coding Guidelines for Java SE."